data, AutoFillType fillType) {
+ LocalDateTime now = LocalDateTime.now();
+ for (FieldSchema field : schema.getFields().values()) {
+ if (field.getAutoFill() == fillType) {
+ String name = field.getName();
+ if (!data.containsKey(name)) {
+ switch (field.getType()) {
+ case DATETIME -> data.put(name, now);
+ case DATE -> data.put(name, now.toLocalDate());
+ default -> {
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java
new file mode 100644
index 0000000..e06fa06
--- /dev/null
+++ b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateBeanPostProcessor.java
@@ -0,0 +1,42 @@
+package cn.structure.infra.elasticsearch.repository;
+
+import cn.structure.infra.annotations.DelegateFor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
+
+@Slf4j
+public class ElasticsearchDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof ElasticsearchRepositoryDelegate) {
+ ElasticsearchRepositoryDelegate delegate = (ElasticsearchRepositoryDelegate) bean;
+ try {
+ ElasticsearchOperations elasticsearchOperations = applicationContext.getBean(ElasticsearchOperations.class);
+ delegate.setElasticsearchOperations(elasticsearchOperations);
+
+ DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class);
+ if (annotation != null && annotation.po() != void.class) {
+ delegate.setEntityClass(annotation.po());
+ }
+
+ log.info("Injected ElasticsearchOperations into ElasticsearchRepositoryDelegate: {}", beanName);
+ } catch (Exception e) {
+ log.warn("Failed to inject ElasticsearchOperations into ElasticsearchRepositoryDelegate {}: {}", beanName, e.getMessage());
+ }
+ }
+ return bean;
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java
new file mode 100644
index 0000000..103fa64
--- /dev/null
+++ b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchDelegateFactory.java
@@ -0,0 +1,39 @@
+package cn.structure.infra.elasticsearch.repository;
+
+import cn.structure.infra.repository.RepositoryDelegate;
+import cn.structure.infra.repository.RepositoryDelegateFactory;
+import cn.structure.infra.repository.RepositoryType;
+import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
+
+/**
+ * Elasticsearch 仓储委托工厂
+ *
+ * 自动创建 ElasticsearchRepositoryDelegate 实例
+ *
+ * @author chuck
+ * @version 1.0.1
+ * @since 2026/6/28
+ */
+public class ElasticsearchDelegateFactory implements RepositoryDelegateFactory {
+
+ private final ElasticsearchOperations elasticsearchOperations;
+
+ public ElasticsearchDelegateFactory(ElasticsearchOperations elasticsearchOperations) {
+ this.elasticsearchOperations = elasticsearchOperations;
+ }
+
+ @Override
+ public RepositoryType getType() {
+ return RepositoryType.ELASTICSEARCH;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) {
+ try {
+ return new ElasticsearchRepositoryDelegate(elasticsearchOperations, poClass);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java
new file mode 100644
index 0000000..3d0a1fe
--- /dev/null
+++ b/structure-infra-elasticsearch-starter/src/main/java/cn/structure/infra/elasticsearch/repository/ElasticsearchRepositoryDelegate.java
@@ -0,0 +1,227 @@
+package cn.structure.infra.elasticsearch.repository;
+
+import cn.structure.common.vo.ReqPage;
+import cn.structure.common.vo.ResPage;
+import cn.structure.infra.repository.RepositoryDelegate;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
+import org.springframework.data.elasticsearch.core.SearchHits;
+import org.springframework.data.elasticsearch.core.query.Criteria;
+import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
+import org.springframework.data.elasticsearch.core.query.Query;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Elasticsearch 仓储委托实现
+ *
+ * 基于 Spring Data Elasticsearch 实现的仓储委托
+ *
+ * @param 持久化对象类型(PO)
+ * @param 主键类型
+ * @author chuck
+ * @version 1.0.1
+ * @since 2026/6/28
+ */
+@Slf4j
+public class ElasticsearchRepositoryDelegate implements RepositoryDelegate {
+
+ protected ElasticsearchOperations elasticsearchOperations;
+ protected Class entityClass;
+ protected String idFieldName;
+
+ public ElasticsearchRepositoryDelegate() {
+ }
+
+ public ElasticsearchRepositoryDelegate(ElasticsearchOperations elasticsearchOperations, Class entityClass) {
+ this(elasticsearchOperations, entityClass, "id");
+ }
+
+ public ElasticsearchRepositoryDelegate(ElasticsearchOperations elasticsearchOperations, Class entityClass, String idFieldName) {
+ this.elasticsearchOperations = elasticsearchOperations;
+ this.entityClass = entityClass;
+ this.idFieldName = idFieldName;
+ log.info("ElasticsearchRepositoryDelegate initialized for entity: {}", entityClass.getSimpleName());
+ }
+
+ public void setElasticsearchOperations(ElasticsearchOperations elasticsearchOperations) {
+ this.elasticsearchOperations = elasticsearchOperations;
+ }
+
+ public void setEntityClass(Class entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ public void setIdFieldName(String idFieldName) {
+ this.idFieldName = idFieldName;
+ }
+
+ @Override
+ public T save(T entity) {
+ if (entity == null) {
+ return null;
+ }
+ T saved = elasticsearchOperations.save(entity);
+ log.debug("Saved entity: {}", saved);
+ return saved;
+ }
+
+ @Override
+ public void removeById(ID id) {
+ if (id != null) {
+ elasticsearchOperations.delete(String.valueOf(id), entityClass);
+ log.debug("Removed entity: id={}", id);
+ }
+ }
+
+ @Override
+ public T findById(ID id) {
+ if (id == null) {
+ return null;
+ }
+ T entity = elasticsearchOperations.get(String.valueOf(id), entityClass);
+ log.debug("Find by id: id={}, found={}", id, entity != null);
+ return entity;
+ }
+
+ @Override
+ public T queryById(ID id) {
+ return findById(id);
+ }
+
+ @Override
+ public Optional queryByIdOptional(ID id) {
+ return Optional.ofNullable(queryById(id));
+ }
+
+ @Override
+ public T queryOne(T condition) {
+ if (condition == null) {
+ return null;
+ }
+ Query query = buildQuery(condition);
+ SearchHits searchHits = elasticsearchOperations.search(query, entityClass);
+ return searchHits.hasSearchHits() ? searchHits.getSearchHit(0).getContent() : null;
+ }
+
+ @Override
+ public Optional queryOneOptional(T condition) {
+ return Optional.ofNullable(queryOne(condition));
+ }
+
+ @Override
+ public List queryList(T condition) {
+ if (condition == null) {
+ Query query = new CriteriaQuery(Criteria.where("*").exists());
+ SearchHits searchHits = elasticsearchOperations.search(query, entityClass);
+ return searchHits.getSearchHits().stream()
+ .map(hit -> hit.getContent())
+ .collect(Collectors.toList());
+ }
+ Query query = buildQuery(condition);
+ SearchHits searchHits = elasticsearchOperations.search(query, entityClass);
+ return searchHits.getSearchHits().stream()
+ .map(hit -> hit.getContent())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public ResPage queryPage(ReqPage reqPage) {
+ int pageNum = reqPage.getPage() != null ? reqPage.getPage() - 1 : 0;
+ int pageSize = reqPage.getSize() != null ? reqPage.getSize() : 10;
+
+ Query query = new CriteriaQuery(Criteria.where("*").exists());
+ PageRequest pageRequest = PageRequest.of(pageNum, pageSize, Sort.unsorted());
+ query.setPageable(pageRequest);
+
+ SearchHits searchHits = elasticsearchOperations.search(query, entityClass);
+
+ ResPage resPage = new ResPage<>();
+ resPage.setCurrent((long) (pageNum + 1));
+ resPage.setPages((long) (searchHits.getTotalHits() > 0 ? (searchHits.getTotalHits() + pageSize - 1) / pageSize : 0));
+ resPage.setSize((long) pageSize);
+ resPage.setTotal(searchHits.getTotalHits());
+ resPage.setRecords(searchHits.getSearchHits().stream()
+ .map(hit -> hit.getContent())
+ .collect(Collectors.toList()));
+
+ log.debug("Query page: page={}, size={}, total={}, records={}",
+ pageNum + 1, pageSize, searchHits.getTotalHits(), resPage.getRecords().size());
+ return resPage;
+ }
+
+ private Query buildQuery(T condition) {
+ Criteria criteria = new Criteria();
+ try {
+ Field[] fields = getAllFields(condition.getClass());
+ for (Field field : fields) {
+ field.setAccessible(true);
+ Object value = field.get(condition);
+ if (value != null) {
+ criteria = criteria.and(Criteria.where(field.getName()).is(value));
+ }
+ }
+ } catch (Exception e) {
+ log.warn("Error building query: {}", e.getMessage());
+ }
+ return new CriteriaQuery(criteria);
+ }
+
+ private Field[] getAllFields(Class> clazz) {
+ List fields = new java.util.ArrayList<>();
+ while (clazz != null && clazz != Object.class) {
+ fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+ clazz = clazz.getSuperclass();
+ }
+ return fields.toArray(new Field[0]);
+ }
+
+ @Override
+ public List saveBatch(List entities) {
+ if (entities == null || entities.isEmpty()) {
+ return List.of();
+ }
+ return entities.stream()
+ .map(elasticsearchOperations::save)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public void removeBatchByIds(List ids) {
+ if (ids != null && !ids.isEmpty()) {
+ ids.forEach(id -> elasticsearchOperations.delete(String.valueOf(id), entityClass));
+ }
+ }
+
+ @Override
+ public List listByIds(List ids) {
+ if (ids == null || ids.isEmpty()) {
+ return List.of();
+ }
+ return ids.stream()
+ .map(this::findById)
+ .filter(entity -> entity != null)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public long count(T condition) {
+ if (condition == null) {
+ Query query = new CriteriaQuery(Criteria.where("*").exists());
+ return elasticsearchOperations.count(query, entityClass);
+ }
+ Query query = buildQuery(condition);
+ return elasticsearchOperations.count(query, entityClass);
+ }
+
+ @Override
+ public boolean exists(T condition) {
+ return count(condition) > 0;
+ }
+}
diff --git a/structure-infra-elasticsearch-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/structure-infra-elasticsearch-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..f0f3292
--- /dev/null
+++ b/structure-infra-elasticsearch-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+cn.structure.infra.elasticsearch.configuration.ElasticsearchAutoConfiguration
+cn.structure.infra.elasticsearch.lowcode.ElasticsearchLowCodeAutoConfiguration
diff --git a/structure-infra-jpa-starter/pom.xml b/structure-infra-jpa-starter/pom.xml
new file mode 100644
index 0000000..b04d37e
--- /dev/null
+++ b/structure-infra-jpa-starter/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ cn.structured
+ structure-pro-infra
+ ${revision}
+ ../pom.xml
+
+
+ structure-pro-jpa-starter
+ structure-infra-jpa-starter
+ structure-pro-jpa-starter
+ jar
+
+
+
+ cn.structured
+ structure-infra-starter
+ ${revision}
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+
\ No newline at end of file
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/configuration/JpaAutoConfiguration.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/configuration/JpaAutoConfiguration.java
new file mode 100644
index 0000000..5ddaf2b
--- /dev/null
+++ b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/configuration/JpaAutoConfiguration.java
@@ -0,0 +1,85 @@
+package cn.structure.infra.jpa.configuration;
+
+import cn.structure.infra.jpa.repository.JpaDelegateBeanPostProcessor;
+import cn.structure.infra.jpa.repository.JpaDelegateFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import jakarta.persistence.EntityManager;
+
+/**
+ * JPA 自动配置类
+ *
+ * 当检测到 JPA 相关依赖({@link org.springframework.data.jpa.repository.JpaRepository})时自动配置,
+ * 注册 JPA 持久化所需的核心组件,使其与仓储框架无缝集成。
+ *
+ * 注册的 Bean:
+ *
+ * - {@link cn.structure.infra.jpa.repository.JpaDelegateFactory} - 仓储委托工厂,
+ * 负责根据 PO 类创建 {@link cn.structure.infra.jpa.repository.JpaRepositoryDelegate} 实例,依赖 {@link jakarta.persistence.EntityManager}
+ * - {@link cn.structure.infra.jpa.repository.JpaDelegateBeanPostProcessor} - Bean 后处理器,
+ * 为自定义的 JpaRepositoryDelegate 实现类自动注入 EntityManager 和实体类
+ *
+ *
+ * 工作机制:
+ *
+ * - 当 {@link cn.structure.infra.repository.RepositoryFacade} 需要获取 RepositoryDelegate 时,
+ * 会通过 {@link cn.structure.infra.repository.RepositoryBeanPostProcessor} 查找匹配的 Delegate
+ * - 若未找到用户自定义的 Delegate,会通过 JpaDelegateFactory 自动创建
+ * - DelegateBeanPostProcessor 确保用户自定义的 Delegate 实现能正确注入 EntityManager
+ *
+ *
+ * 额外配置:
+ *
+ * - {@link EnableJpaRepositories} - 启用 Spring Data JPA 仓库扫描
+ * - {@link EnableTransactionManagement} - 启用事务管理
+ *
+ *
+ * @author chuck
+ * @version 1.0.1
+ * @since 2026/6/28
+ */
+@AutoConfiguration
+@ConditionalOnClass(name = "org.springframework.data.jpa.repository.JpaRepository")
+@EnableJpaRepositories
+@EnableTransactionManagement
+public class JpaAutoConfiguration {
+
+ /**
+ * 创建 JPA 仓储委托工厂
+ *
+ * 负责根据 PO 类创建 JpaRepositoryDelegate 实例,通过 EntityManager 进行持久化操作。
+ * 当 RepositoryFacade 需要获取 JPA 类型的 RepositoryDelegate 时,会通过此工厂进行创建。
+ *
+ * @param entityManager JPA 实体管理器,用于执行数据库操作
+ * @return JpaDelegateFactory 实例
+ */
+ @Bean
+ @ConditionalOnBean(EntityManager.class)
+ public JpaDelegateFactory jpaDelegateFactory(EntityManager entityManager) {
+ return new JpaDelegateFactory(entityManager);
+ }
+
+ /**
+ * 创建 JPA 委托 Bean 后处理器
+ *
+ * 在 Bean 初始化完成后,自动为 JpaRepositoryDelegate 实现类注入 EntityManager 和实体类。
+ *
+ * 处理逻辑:
+ * 1. 扫描所有 Bean,筛选出 JpaRepositoryDelegate 的实例
+ * 2. 从 Spring 上下文获取 EntityManager 并注入到 Delegate 实例中
+ * 3. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解
+ * 4. 将注解中指定的 PO 类设置到 Delegate 实例中
+ *
+ * @return JpaDelegateBeanPostProcessor 实例
+ */
+ @Bean
+ @ConditionalOnClass(name = "jakarta.persistence.EntityManager")
+ public JpaDelegateBeanPostProcessor jpaDelegateBeanPostProcessor() {
+ return new JpaDelegateBeanPostProcessor();
+ }
+}
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java
new file mode 100644
index 0000000..76acb0f
--- /dev/null
+++ b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateBeanPostProcessor.java
@@ -0,0 +1,66 @@
+package cn.structure.infra.jpa.repository;
+
+import cn.structure.infra.annotations.DelegateFor;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+@Slf4j
+public class JpaDelegateBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
+
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean instanceof JpaRepositoryDelegate) {
+ JpaRepositoryDelegate delegate = (JpaRepositoryDelegate) bean;
+
+ EntityManager entityManager = getEntityManager();
+ if (entityManager != null) {
+ delegate.setEntityManager(entityManager);
+ log.info("Injected EntityManager into JpaRepositoryDelegate: {}", beanName);
+ } else {
+ log.warn("No EntityManager available to inject into JpaRepositoryDelegate: {}", beanName);
+ }
+
+ DelegateFor annotation = bean.getClass().getAnnotation(DelegateFor.class);
+ if (annotation != null && annotation.po() != void.class) {
+ delegate.setEntityClass(annotation.po());
+ log.info("Injected entityClass {} into JpaRepositoryDelegate: {}", annotation.po().getSimpleName(), beanName);
+ }
+ }
+ return bean;
+ }
+
+ private EntityManager getEntityManager() {
+ try {
+ Object bean = applicationContext.getBean("entityManager");
+ if (bean instanceof EntityManager) {
+ return (EntityManager) bean;
+ }
+ } catch (Exception e) {
+ log.debug("entityManager bean not found by name");
+ }
+
+ try {
+ EntityManagerFactory factory = applicationContext.getBean(EntityManagerFactory.class);
+ if (factory != null) {
+ return factory.createEntityManager();
+ }
+ } catch (Exception e) {
+ log.debug("EntityManagerFactory bean not found");
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java
new file mode 100644
index 0000000..ec31282
--- /dev/null
+++ b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaDelegateFactory.java
@@ -0,0 +1,40 @@
+package cn.structure.infra.jpa.repository;
+
+import cn.structure.infra.repository.RepositoryDelegate;
+import cn.structure.infra.repository.RepositoryDelegateFactory;
+import cn.structure.infra.repository.RepositoryType;
+
+import jakarta.persistence.EntityManager;
+
+/**
+ * JPA 仓储委托工厂
+ *
+ * 自动创建 JpaRepositoryDelegate 实例
+ *
+ * @author chuck
+ * @version 1.0.1
+ * @since 2026/6/28
+ */
+public class JpaDelegateFactory implements RepositoryDelegateFactory {
+
+ private final EntityManager entityManager;
+
+ public JpaDelegateFactory(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ @Override
+ public RepositoryType getType() {
+ return RepositoryType.JPA;
+ }
+
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public RepositoryDelegate, ?> createDelegate(Class> poClass, Class> idClass) {
+ try {
+ return new JpaRepositoryDelegate(entityManager, poClass);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java
new file mode 100644
index 0000000..bfc7f5d
--- /dev/null
+++ b/structure-infra-jpa-starter/src/main/java/cn/structure/infra/jpa/repository/JpaRepositoryDelegate.java
@@ -0,0 +1,217 @@
+package cn.structure.infra.jpa.repository;
+
+import cn.structure.common.vo.ReqPage;
+import cn.structure.common.vo.ResPage;
+import cn.structure.infra.repository.RepositoryDelegate;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.TypedQuery;
+import jakarta.persistence.criteria.CriteriaBuilder;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Predicate;
+import jakarta.persistence.criteria.Root;
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Slf4j
+public class JpaRepositoryDelegate implements RepositoryDelegate {
+
+ protected EntityManager entityManager;
+ protected Class entityClass;
+
+ public JpaRepositoryDelegate() {
+ }
+
+ public JpaRepositoryDelegate(EntityManager entityManager, Class entityClass) {
+ this.entityManager = entityManager;
+ this.entityClass = entityClass;
+ log.info("JpaRepositoryDelegate initialized for entity: {}", entityClass.getSimpleName());
+ }
+
+ public void setEntityManager(EntityManager entityManager) {
+ this.entityManager = entityManager;
+ }
+
+ public void setEntityClass(Class entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ @Override
+ public T save(T entity) {
+ if (entity == null || entityManager == null || entityClass == null) {
+ return null;
+ }
+ T saved = entityManager.merge(entity);
+ log.debug("Saved entity: {}", saved);
+ return saved;
+ }
+
+ @Override
+ public void removeById(ID id) {
+ if (id != null) {
+ T entity = findById(id);
+ if (entity != null) {
+ entityManager.remove(entity);
+ log.debug("Removed entity: id={}", id);
+ }
+ }
+ }
+
+ @Override
+ public T findById(ID id) {
+ if (id == null) {
+ return null;
+ }
+ T entity = entityManager.find(entityClass, id);
+ log.debug("Find by id: id={}, found={}", id, entity != null);
+ return entity;
+ }
+
+ @Override
+ public T queryById(ID id) {
+ return findById(id);
+ }
+
+ @Override
+ public Optional queryByIdOptional(ID id) {
+ return Optional.ofNullable(findById(id));
+ }
+
+ @Override
+ public T queryOne(T condition) {
+ if (condition == null) {
+ return null;
+ }
+ List results = queryList(condition);
+ return results.isEmpty() ? null : results.get(0);
+ }
+
+ @Override
+ public Optional queryOneOptional(T condition) {
+ return Optional.ofNullable(queryOne(condition));
+ }
+
+ @Override
+ public List queryList(T condition) {
+ if (condition == null) {
+ return findAll();
+ }
+ return queryByCondition(condition);
+ }
+
+ @Override
+ public ResPage queryPage(ReqPage reqPage) {
+ int pageNum = reqPage.getPage() != null ? reqPage.getPage() - 1 : 0;
+ int pageSize = reqPage.getSize() != null ? reqPage.getSize() : 10;
+
+ List allResults = findAll();
+ int start = pageNum * pageSize;
+ int end = Math.min(start + pageSize, allResults.size());
+
+ List pageContent = start < allResults.size() ? allResults.subList(start, end) : List.of();
+
+ ResPage resPage = new ResPage<>();
+ resPage.setCurrent((long) (pageNum + 1));
+ resPage.setPages((long) ((allResults.size() + pageSize - 1) / pageSize));
+ resPage.setSize((long) pageSize);
+ resPage.setTotal((long) allResults.size());
+ resPage.setRecords(pageContent);
+
+ log.debug("Query page: page={}, size={}, total={}, records={}",
+ pageNum + 1, pageSize, allResults.size(), pageContent.size());
+ return resPage;
+ }
+
+ private List findAll() {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery query = cb.createQuery(entityClass);
+ query.from(entityClass);
+ return entityManager.createQuery(query).getResultList();
+ }
+
+ private List queryByCondition(T condition) {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery query = cb.createQuery(entityClass);
+ Root root = query.from(entityClass);
+
+ Predicate[] predicates = buildPredicates(cb, root, condition);
+ if (predicates.length > 0) {
+ query.where(predicates);
+ }
+
+ return entityManager.createQuery(query).getResultList();
+ }
+
+ private Predicate[] buildPredicates(CriteriaBuilder cb, Root root, T condition) {
+ List predicates = new java.util.ArrayList<>();
+ try {
+ Field[] fields = getAllFields(condition.getClass());
+ for (Field field : fields) {
+ field.setAccessible(true);
+ Object value = field.get(condition);
+ if (value != null) {
+ predicates.add(cb.equal(root.get(field.getName()), value));
+ }
+ }
+ } catch (Exception e) {
+ log.warn("Error building predicates: {}", e.getMessage());
+ }
+ return predicates.toArray(new Predicate[0]);
+ }
+
+ private Field[] getAllFields(Class> clazz) {
+ List fields = new java.util.ArrayList<>();
+ while (clazz != null && clazz != Object.class) {
+ fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+ clazz = clazz.getSuperclass();
+ }
+ return fields.toArray(new Field[0]);
+ }
+
+ @Override
+ public List saveBatch(List entities) {
+ if (entities == null || entities.isEmpty()) {
+ return List.of();
+ }
+ return entities.stream()
+ .map(entityManager::merge)
+ .toList();
+ }
+
+ @Override
+ public void removeBatchByIds(List ids) {
+ if (ids != null) {
+ ids.forEach(this::removeById);
+ }
+ }
+
+ @Override
+ public List listByIds(List ids) {
+ if (ids == null || ids.isEmpty()) {
+ return List.of();
+ }
+ return ids.stream()
+ .map(this::findById)
+ .filter(java.util.Objects::nonNull)
+ .toList();
+ }
+
+ @Override
+ public long count(T condition) {
+ if (condition == null) {
+ return findAll().size();
+ }
+ return queryList(condition).size();
+ }
+
+ @Override
+ public boolean exists(T condition) {
+ return count(condition) > 0;
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-jpa-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/structure-infra-jpa-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..f03bf76
--- /dev/null
+++ b/structure-infra-jpa-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+cn.structure.infra.jpa.configuration.JpaAutoConfiguration
\ No newline at end of file
diff --git a/structure-infra-mongodb-starter/pom.xml b/structure-infra-mongodb-starter/pom.xml
new file mode 100644
index 0000000..ba7785e
--- /dev/null
+++ b/structure-infra-mongodb-starter/pom.xml
@@ -0,0 +1,29 @@
+
+
+ 4.0.0
+
+ cn.structured
+ structure-pro-infra
+ ${revision}
+ ../pom.xml
+
+
+ structure-infra-mongodb-starter
+ structure-infra-mongodb-starter
+ structure-infra mongodb starter
+ jar
+
+
+
+ cn.structured
+ structure-infra-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+
+
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/configuration/MongoAutoConfiguration.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/configuration/MongoAutoConfiguration.java
new file mode 100644
index 0000000..65209b9
--- /dev/null
+++ b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/configuration/MongoAutoConfiguration.java
@@ -0,0 +1,89 @@
+package cn.structure.infra.mongodb.configuration;
+
+import cn.structure.infra.mongodb.repository.MongoDelegateBeanPostProcessor;
+import cn.structure.infra.mongodb.repository.MongoDelegateFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+
+/**
+ * MongoDB 自动配置类
+ *
+ * 当检测到 MongoDB 相关依赖({@link org.springframework.data.mongodb.core.MongoTemplate})时自动配置,
+ * 注册 MongoDB 文档操作所需的核心组件,使其与仓储框架无缝集成。
+ *
+ * 注册的 Bean:
+ *
+ * - {@link cn.structure.infra.mongodb.repository.MongoDelegateFactory} - 仓储委托工厂,
+ * 负责根据 PO 类创建 {@link cn.structure.infra.mongodb.repository.MongoRepositoryDelegate} 实例,依赖 {@link org.springframework.data.mongodb.core.MongoTemplate}
+ * - {@link cn.structure.infra.mongodb.repository.MongoDelegateBeanPostProcessor} - Bean 后处理器,
+ * 为自定义的 MongoRepositoryDelegate 实现类自动注入 MongoTemplate 和实体类
+ *
+ *
+ * 工作机制:
+ *
+ * - 当 {@link cn.structure.infra.repository.RepositoryFacade} 需要获取 RepositoryDelegate 时,
+ * 会通过 {@link cn.structure.infra.repository.RepositoryBeanPostProcessor} 查找匹配的 Delegate
+ * - 若未找到用户自定义的 Delegate,会通过 MongoDelegateFactory 自动创建
+ * - DelegateBeanPostProcessor 确保用户自定义的 Delegate 实现能正确注入 MongoTemplate
+ *
+ *
+ * 配置方式:
+ *
+ * - 默认自动启用(matchIfMissing = true)
+ * - 可通过 `structure.infra.type=MONGODB` 显式指定
+ *
+ *
+ * 额外配置:
+ *
+ * - {@link EnableMongoRepositories} - 启用 Spring Data MongoDB 仓库扫描
+ *
+ *
+ * @author chuck
+ * @version 1.0.1
+ * @since 2026/6/28
+ */
+@AutoConfiguration
+@ConditionalOnClass(name = "org.springframework.data.mongodb.core.MongoTemplate")
+@ConditionalOnProperty(prefix = "structure.infra", name = "type", havingValue = "MONGODB", matchIfMissing = true)
+@EnableMongoRepositories
+public class MongoAutoConfiguration {
+
+ /**
+ * 创建 MongoDB 仓储委托工厂
+ *
+ * 负责根据 PO 类创建 MongoRepositoryDelegate 实例,通过 MongoTemplate 进行文档操作。
+ * 当 RepositoryFacade 需要获取 MongoDB 类型的 RepositoryDelegate 时,会通过此工厂进行创建。
+ *
+ * @param mongoTemplate MongoDB 操作模板,用于执行增删改查等操作
+ * @return MongoDelegateFactory 实例
+ */
+ @Bean
+ @ConditionalOnBean(MongoTemplate.class)
+ public MongoDelegateFactory mongoDelegateFactory(MongoTemplate mongoTemplate) {
+ return new MongoDelegateFactory(mongoTemplate);
+ }
+
+ /**
+ * 创建 MongoDB 委托 Bean 后处理器
+ *
+ * 在 Bean 初始化完成后,自动为 MongoRepositoryDelegate 实现类注入 MongoTemplate 和实体类。
+ *
+ * 处理逻辑:
+ * 1. 扫描所有 Bean,筛选出 MongoRepositoryDelegate 的实例
+ * 2. 从 Spring 上下文获取 MongoTemplate 并注入到 Delegate 实例中
+ * 3. 检查是否存在 {@link cn.structure.infra.annotations.DelegateFor} 注解
+ * 4. 将注解中指定的 PO 类设置到 Delegate 实例中
+ *
+ * @return MongoDelegateBeanPostProcessor 实例
+ */
+ @Bean
+ @ConditionalOnBean(MongoTemplate.class)
+ public MongoDelegateBeanPostProcessor mongoDelegateBeanPostProcessor() {
+ return new MongoDelegateBeanPostProcessor();
+ }
+}
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeAutoConfiguration.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeAutoConfiguration.java
new file mode 100644
index 0000000..f757124
--- /dev/null
+++ b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeAutoConfiguration.java
@@ -0,0 +1,32 @@
+package cn.structure.infra.mongodb.lowcode;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.mongodb.core.MongoTemplate;
+
+/**
+ * MongoDB 低代码自动配置类
+ *
+ * 当低代码功能启用且存在 MongoTemplate 时,自动注册 MongoDB 低代码仓储工厂,
+ * 使低代码路由引擎能够创建 MongoDB 类型的存储实例。
+ *
+ * @author chuck
+ * @version 1.0.0
+ * @since 2026/6/29
+ */
+@AutoConfiguration
+@ConditionalOnProperty(prefix = "structure.infra.lowcode", name = "enabled", havingValue = "true", matchIfMissing = true)
+public class MongoLowCodeAutoConfiguration {
+
+ /**
+ * 注册 MongoDB 低代码仓储工厂
+ *
+ * @param mongoTemplate MongoTemplate 实例
+ * @return MongoDB 低代码仓储工厂实例
+ */
+ @Bean
+ public MongoLowCodeRepoFactory mongoLowCodeRepoFactory(MongoTemplate mongoTemplate) {
+ return new MongoLowCodeRepoFactory(mongoTemplate);
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeRepoFactory.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeRepoFactory.java
new file mode 100644
index 0000000..3bf2ebc
--- /dev/null
+++ b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeRepoFactory.java
@@ -0,0 +1,48 @@
+package cn.structure.infra.mongodb.lowcode;
+
+import cn.structure.infra.lowcode.model.RepositoryConfig;
+import cn.structure.infra.lowcode.model.ResourceSchema;
+import cn.structure.infra.lowcode.model.StorageType;
+import cn.structure.infra.lowcode.repository.LowCodeRepoFactory;
+import cn.structure.infra.lowcode.repository.LowCodeStorage;
+import org.springframework.data.mongodb.core.MongoTemplate;
+
+/**
+ * MongoDB 低代码仓储工厂
+ *
+ * 负责创建 MongoDB 类型的低代码存储实例,内部使用 MongoTemplate + Document 执行动态操作。
+ *
+ * 核心特性:
+ *
+ * - Document 动态操作:使用 Document 代替 POJO,无需定义实体类
+ * - 自动创建集合:初始化时自动创建集合和索引
+ * - 自动填充:支持创建时间、更新时间自动填充
+ *
+ *
+ * @author chuck
+ * @version 1.0.0
+ * @since 2026/6/29
+ */
+public class MongoLowCodeRepoFactory implements LowCodeRepoFactory {
+
+ private final MongoTemplate mongoTemplate;
+
+ /**
+ * 通过 MongoTemplate 构造
+ *
+ * @param mongoTemplate MongoTemplate 实例
+ */
+ public MongoLowCodeRepoFactory(MongoTemplate mongoTemplate) {
+ this.mongoTemplate = mongoTemplate;
+ }
+
+ @Override
+ public StorageType getType() {
+ return StorageType.MONGODB;
+ }
+
+ @Override
+ public LowCodeStorage createStorage(ResourceSchema schema, RepositoryConfig config) {
+ return new MongoLowCodeStorage(schema, mongoTemplate);
+ }
+}
\ No newline at end of file
diff --git a/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeStorage.java b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeStorage.java
new file mode 100644
index 0000000..ddf7ba4
--- /dev/null
+++ b/structure-infra-mongodb-starter/src/main/java/cn/structure/infra/mongodb/lowcode/MongoLowCodeStorage.java
@@ -0,0 +1,353 @@
+package cn.structure.infra.mongodb.lowcode;
+
+import cn.structure.common.vo.ReqPage;
+import cn.structure.common.vo.ResPage;
+import cn.structure.infra.lowcode.model.AutoFillType;
+import cn.structure.infra.lowcode.model.FieldSchema;
+import cn.structure.infra.lowcode.model.FieldType;
+import cn.structure.infra.lowcode.model.ResourceSchema;
+import cn.structure.infra.lowcode.repository.LowCodeStorage;
+import lombok.extern.slf4j.Slf4j;
+import org.bson.Document;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.index.Index;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.Update;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * MongoDB 低代码仓储实现
+ *
+ * 基于 Spring Data MongoDB 的低代码存储实现,使用 Document 代替实体类,
+ * 通过 MongoTemplate 动态操作 MongoDB 集合。
+ *
+ * 核心特性:
+ *
+ * - Document 动态操作:使用 Document 代替 POJO,无需定义实体类
+ * - 自动创建集合:初始化时自动创建集合和索引
+ * - 自动填充:支持创建时间、更新时间自动填充
+ * - 动态查询:根据查询条件动态构建 MongoDB 查询
+ * - 分页查询:支持分页查询,自动处理总数统计
+ *
+ *
+ * @author chuck
+ * @version 1.0.0
+ * @since 2026/6/29
+ */
+@Slf4j
+public class MongoLowCodeStorage implements LowCodeStorage {
+
+ private final ResourceSchema schema;
+ private final MongoTemplate mongoTemplate;
+
+ /**
+ * 构造函数
+ *
+ * @param schema 资源 schema 定义
+ * @param mongoTemplate MongoTemplate 实例
+ */
+ public MongoLowCodeStorage(ResourceSchema schema, MongoTemplate mongoTemplate) {
+ this.schema = schema;
+ this.mongoTemplate = mongoTemplate;
+ }
+
+ @Override
+ public void initialize() {
+ String collectionName = schema.getTableName();
+
+ // 检查集合是否存在,不存在则创建
+ boolean collectionExists = mongoTemplate.collectionExists(collectionName);
+ if (!collectionExists) {
+ mongoTemplate.createCollection(collectionName);
+ log.info("MongoDB collection created: {}", collectionName);
+ }
+
+ // 创建索引
+ createIndexes(collectionName);
+
+ log.info("MongoDB lowcode storage initialized: {}", collectionName);
+ }
+
+ /**
+ * 创建索引
+ *
+ * 根据 schema 中的字段定义自动创建索引:
+ *
+ * - 主键字段自动创建唯一索引
+ * - 标记为 index=true 的字段创建普通索引
+ * - 标记为 unique=true 的字段创建唯一索引
+ *
+ *
+ * @param collectionName 集合名称
+ */
+ private void createIndexes(String collectionName) {
+ for (FieldSchema field : schema.getFields().values()) {
+ if (field.isPrimaryKey() || field.isIndex() || field.isUnique()) {
+ Index index = new Index()
+ .on(field.getName(), field.isUnique() ? org.springframework.data.domain.Sort.Direction.ASC
+ : org.springframework.data.domain.Sort.Direction.ASC);
+
+ if (field.isUnique()) {
+ index.unique();
+ }
+
+ mongoTemplate.indexOps(collectionName).ensureIndex(index);
+ log.debug("Created index for field: {} (unique={}, index={})",
+ field.getName(), field.isUnique(), field.isIndex());
+ }
+ }
+ }
+
+ @Override
+ public Map save(Map data) {
+ Document document = new Document(data);
+ fillAutoFields(document, AutoFillType.CREATE);
+ fillAutoFields(document, AutoFillType.CREATE_UPDATE);
+
+ String idField = schema.getIdFieldName();
+ Object idValue = document.get(idField);
+
+ if (idValue != null) {
+ // 更新操作
+ Query query = new Query(Criteria.where(idField).is(idValue));
+ Document existing = mongoTemplate.findOne(query, Document.class, schema.getTableName());
+ if (existing != null) {
+ return doUpdate(document);
+ }
+ }
+
+ return doInsert(document);
+ }
+
+ /**
+ * 执行插入操作
+ *
+ * @param document Document 对象
+ * @return 插入后的数据
+ */
+ private Map doInsert(Document document) {
+ mongoTemplate.insert(document, schema.getTableName());
+ return documentToMap(document);
+ }
+
+ /**
+ * 执行更新操作
+ *
+ * @param document Document 对象
+ * @return 更新后的数据
+ */
+ private Map doUpdate(Document document) {
+ String idField = schema.getIdFieldName();
+ Object idValue = document.get(idField);
+
+ Query query = new Query(Criteria.where(idField).is(idValue));
+
+ // 构建更新文档
+ Update update = new Update();
+ for (Map.Entry entry : document.entrySet()) {
+ if (!idField.equals(entry.getKey())) {
+ update.set(entry.getKey(), entry.getValue());
+ }
+ }
+
+ mongoTemplate.updateFirst(query, update, schema.getTableName());
+
+ return findById(idValue);
+ }
+
+ @Override
+ public void removeById(Object id) {
+ Query query = new Query(Criteria.where(schema.getIdFieldName()).is(id));
+ mongoTemplate.remove(query, schema.getTableName());
+ }
+
+ @Override
+ public Map findById(Object id) {
+ Query query = new Query(Criteria.where(schema.getIdFieldName()).is(id));
+ Document result = mongoTemplate.findOne(query, Document.class, schema.getTableName());
+ return result != null ? documentToMap(result) : null;
+ }
+
+ @Override
+ public Map queryById(Object id) {
+ return findById(id);
+ }
+
+ @Override
+ public Map queryOne(Map queryParams) {
+ List