diff --git a/yudao-framework/pom.xml b/yudao-framework/pom.xml
index 635dc7cf6..a4fb64fae 100644
--- a/yudao-framework/pom.xml
+++ b/yudao-framework/pom.xml
@@ -28,6 +28,7 @@
yudao-spring-boot-starter-biz-operatelog
yudao-spring-boot-starter-biz-dict
yudao-spring-boot-starter-biz-sms
+ yudao-spring-boot-starter-extension
yudao-framework
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/pom.xml b/yudao-framework/yudao-spring-boot-starter-extension/pom.xml
new file mode 100644
index 000000000..bc6a36e8d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/pom.xml
@@ -0,0 +1,68 @@
+
+
+
+ yudao-framework
+ cn.iocoder.boot
+ ${revision}
+
+ 4.0.0
+
+ yudao-spring-boot-starter-extension
+ jar
+
+ ${artifactId}
+ 扩展点组件
+ https://github.com/YunaiV/ruoyi-vue-pro
+
+
+
+
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
+ org.springframework
+ spring-context
+ provided
+
+
+
+ org.springframework
+ spring-beans
+ provided
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+
+
+
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/config/YudaoExtensionAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/config/YudaoExtensionAutoConfiguration.java
new file mode 100644
index 000000000..8b047ae1b
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/config/YudaoExtensionAutoConfiguration.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.framework.extension.config;
+
+import cn.iocoder.yudao.framework.extension.core.ExtensionBootstrap;
+import cn.iocoder.yudao.framework.extension.core.context.ExtensionContext;
+import cn.iocoder.yudao.framework.extension.core.context.ExtensionContextHolder;
+import cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor;
+import cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory;
+import cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @description 扩展点组件自动装配
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 21:50
+ * @class cn.iocoder.yudao.framework.extension.config.YudaoExtensionAutoConfiguration.java
+ */
+@Configuration
+public class YudaoExtensionAutoConfiguration {
+
+ /**
+ * 组件初始化
+ * @return
+ */
+ @Bean(initMethod = "init")
+ @ConditionalOnMissingBean(ExtensionBootstrap.class)
+ public ExtensionBootstrap bootstrap() {
+ return new ExtensionBootstrap();
+ }
+
+ /**
+ * 扩展点工厂
+ * @return
+ */
+ @Bean
+ @ConditionalOnMissingBean({ExtensionRegisterFactory.class, ExtensionFactory.class})
+ public ExtensionRegisterFactory registerFactory() {
+ return new ExtensionRegisterFactory();
+ }
+
+ /**
+ * 扩展组件上下文对象
+ * @return
+ */
+ @Bean
+ @ConditionalOnMissingBean({ExtensionContextHolder.class, ExtensionContext.class})
+ public ExtensionContextHolder context() {
+ return new ExtensionContextHolder();
+ }
+
+ /**
+ * 扩展组件执行器
+ * @return
+ */
+ @Bean
+ @ConditionalOnMissingBean(ExtensionExecutor.class)
+ public ExtensionExecutor executor() {
+ return new ExtensionExecutor();
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/BusinessScenario.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/BusinessScenario.java
new file mode 100644
index 000000000..732350c67
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/BusinessScenario.java
@@ -0,0 +1,142 @@
+package cn.iocoder.yudao.framework.extension.core;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.StringJoiner;
+
+/**
+ * @description 业务场景 = businessId + useCase + scenario, 用来标识系统中唯一的一个场景
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 22:19
+ * @class cn.iocoder.yudao.framework.extension.core.BusinessScenario.java
+ */
+public class BusinessScenario implements Serializable {
+
+ /**
+ * 默认业务id
+ */
+ public final static String DEFAULT_BUSINESS_ID = "#defaultBusinessId#";
+
+ /**
+ * 默认用例
+ */
+ public final static String DEFAULT_USECASE = "#defaultUseCase#";
+
+ /**
+ * 默认场景
+ */
+ public final static String DEFAULT_SCENARIO = "#defaultScenario#";
+
+ /**
+ * 分隔符
+ */
+ private final static String DOT_SEPARATOR = ".";
+
+ /**
+ * 业务Id
+ */
+ private String businessId;
+
+ /**
+ * 用例
+ */
+ private String useCase;
+
+ /**
+ * 场景
+ */
+ private String scenario;
+
+ public BusinessScenario() {
+ this.businessId = DEFAULT_BUSINESS_ID;
+ this.useCase = DEFAULT_USECASE;
+ this.scenario = DEFAULT_SCENARIO;
+ }
+
+ public BusinessScenario(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario) {
+ this.businessId = businessId;
+ this.useCase = useCase;
+ this.scenario = scenario;
+ }
+
+ public BusinessScenario(@NotNull String scenario) {
+ this();
+ this.scenario = scenario;
+ }
+
+ public BusinessScenario(@NotNull String useCase, @NotNull String scenario) {
+ this(DEFAULT_BUSINESS_ID, useCase, scenario);
+ }
+
+ public String getBusinessId() {
+ return businessId;
+ }
+
+ public void setBusinessId(String businessId) {
+ this.businessId = businessId;
+ }
+
+ public String getUseCase() {
+ return useCase;
+ }
+
+ public void setUseCase(String useCase) {
+ this.useCase = useCase;
+ }
+
+ public String getScenario() {
+ return scenario;
+ }
+
+ public void setScenario(String scenario) {
+ this.scenario = scenario;
+ }
+
+ /**
+ * 构建业务场景
+ * @param businessId
+ * @param useCase
+ * @param scenario
+ * @return
+ */
+ public static BusinessScenario valueOf(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario) {
+ return new BusinessScenario(businessId, useCase, scenario);
+ }
+
+ /**
+ * 构建业务场景
+ * @param useCase
+ * @param scenario
+ * @return
+ */
+ public static BusinessScenario valueOf(@NotNull String useCase, @NotNull String scenario) {
+ return new BusinessScenario(useCase, scenario);
+ }
+
+ /**
+ * 构建业务场景
+ * @param scenario
+ * @return
+ */
+ public static BusinessScenario valueOf(@NotNull String scenario) {
+ return new BusinessScenario(scenario);
+ }
+
+ /**
+ * 业务场景唯一标识
+ * @return
+ */
+ public String getUniqueIdentity(){
+ return new StringJoiner(DOT_SEPARATOR).add(businessId).add(useCase).add(scenario).toString();
+ }
+
+ @Override
+ public String toString() {
+ return "BusinessScenario{" +
+ "businessId='" + businessId + '\'' +
+ ", useCase='" + useCase + '\'' +
+ ", scenario='" + scenario + '\'' +
+ '}';
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/ExtensionBootstrap.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/ExtensionBootstrap.java
new file mode 100644
index 000000000..800c431be
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/ExtensionBootstrap.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.framework.extension.core;
+
+import cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @description
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-29 00:18
+ * @class cn.iocoder.yudao.framework.extension.core.ExtensionBootstrap.java
+ */
+public class ExtensionBootstrap implements ApplicationContextAware {
+
+ /**
+ * spring 容器
+ */
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private ExtensionRegisterFactory registerFactory;
+
+ /**
+ * 初始化
+ */
+ @PostConstruct
+ public void init() {
+ registerFactory.setApplicationContext(applicationContext);
+ registerFactory.register(null);
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/AbstractComponentExecutor.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/AbstractComponentExecutor.java
new file mode 100644
index 000000000..f46d05d90
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/AbstractComponentExecutor.java
@@ -0,0 +1,131 @@
+package cn.iocoder.yudao.framework.extension.core.context;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * @description 执行器通用方法
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-29 00:38
+ * @class cn.iocoder.yudao.framework.extension.core.context.AbstractComponentExecutor.java
+ */
+public abstract class AbstractComponentExecutor {
+
+ /**
+ * ("业务" + "用例" + "场景")执行扩展组件,并返回执行结果
+ * @param targetClazz
+ * @param businessId
+ * @param useCase
+ * @param scenario
+ * @param function
+ * @param
+ * @param
+ * @return
+ */
+ public R execute(Class targetClazz, String businessId, String useCase, String scenario, Function function) {
+ return execute(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), function);
+ }
+
+
+ /**
+ * ("用例" + "场景")执行扩展组件,并返回执行结果
+ * @param targetClazz
+ * @param useCase
+ * @param scenario
+ * @param function
+ * @param
+ * @param
+ * @return
+ */
+ public R execute(Class targetClazz, String useCase, String scenario, Function function) {
+ return execute(targetClazz, BusinessScenario.valueOf(useCase, scenario), function);
+ }
+
+ /**
+ * ("场景")执行扩展组件,并返回执行结果
+ * @param targetClazz
+ * @param scenario
+ * @param function
+ * @param
+ * @param
+ * @return
+ */
+ public R execute(Class targetClazz, String scenario, Function function) {
+ return execute(targetClazz, BusinessScenario.valueOf(scenario), function);
+ }
+
+ /**
+ * 执行扩展组件,并返回执行结果
+ * @param targetClazz
+ * @param businessScenario
+ * @param function
+ * @param Response Type
+ * @param Parameter Type
+ * @return
+ */
+ public R execute(Class targetClazz, BusinessScenario businessScenario, Function function) {
+ T component = locateComponent(targetClazz, businessScenario);
+ return function.apply(component);
+ }
+
+ /**
+ * ("业务" + "用例" + "场景")执行扩展组件,适用于无返回值的业务
+ * @param targetClazz
+ * @param businessId
+ * @param useCase
+ * @param scenario
+ * @param consumer
+ * @param
+ */
+ public void accept(Class targetClazz, String businessId, String useCase, String scenario, Consumer consumer) {
+ accept(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), consumer);
+ }
+
+ /**
+ * ("场景")执行扩展组件,适用于无返回值的业务
+ * @param targetClazz
+ * @param useCase
+ * @param scenario
+ * @param consumer
+ * @param
+ */
+ public void accept(Class targetClazz, String useCase, String scenario, Consumer consumer) {
+ accept(targetClazz, BusinessScenario.valueOf(useCase, scenario), consumer);
+ }
+
+ /**
+ * ("场景")执行扩展组件,适用于无返回值的业务
+ * @param targetClazz
+ * @param scenario
+ * @param consumer
+ * @param
+ */
+ public void accept(Class targetClazz, String scenario, Consumer consumer) {
+ accept(targetClazz, BusinessScenario.valueOf(scenario), consumer);
+ }
+
+ /**
+ * 执行扩展组件,适用于无返回值的业务
+ * @param targetClazz
+ * @param businessScenario
+ * @param consumer
+ * @param Parameter Type
+ */
+ public void accept(Class targetClazz, BusinessScenario businessScenario, Consumer consumer) {
+ T component = locateComponent(targetClazz, businessScenario);
+ consumer.accept(component);
+ }
+
+ /**
+ * 获取/定位扩展点组件
+ * @param targetClazz
+ * @param businessScenario
+ * @param
+ * @return
+ */
+ protected abstract C locateComponent(Class targetClazz, BusinessScenario businessScenario);
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContext.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContext.java
new file mode 100644
index 000000000..5704cc49f
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContext.java
@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.framework.extension.core.context;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+
+/**
+ * @description 上下文,包含各个扩展点的相关操作
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 22:15
+ * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionContext.java
+ */
+public interface ExtensionContext {
+
+ /**
+ * 根据业务场景唯一标识获取扩展点组件实现类
+ * @param businessId
+ * @param useCase
+ * @param scenario
+ * @param clazz
+ * @param
+ * @return
+ */
+ T getPoint(String businessId, String useCase, String scenario, Class clazz);
+
+ /**
+ * 根据("实例" + "场景")获取扩展点组件实现类,其中:业务id(businessId)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_BUSINESS_ID}
+ * @param useCase
+ * @param scenario
+ * @param clazz
+ * @param
+ * @return
+ */
+ T getPoint(String useCase, String scenario, Class clazz);
+
+ /**
+ * 根据("场景")获取扩展点组件实现类
+ * 其中:
+ * 业务id(businessId)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_BUSINESS_ID}
+ * 实例(useCase)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_USECASE}
+ * @param scenario
+ * @param clazz
+ * @param
+ * @return
+ */
+ T getPoint(String scenario, Class clazz);
+
+ /**
+ * 根据业务场景唯一标识获取扩展点组件实现类
+ * @param businessScenario
+ * @param clazz
+ * @param
+ * @return
+ */
+ T getPoint(BusinessScenario businessScenario, Class clazz);
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContextHolder.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContextHolder.java
new file mode 100644
index 000000000..6e121b70d
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContextHolder.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.framework.extension.core.context;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * @description 上下文及扩展点组件工厂的持有类
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-29 00:29
+ * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionContextHolder.java
+ */
+@Component
+@Slf4j
+public class ExtensionContextHolder implements ExtensionContext{
+
+ @Autowired
+ private ExtensionFactory factory;
+
+ @Override
+ public T getPoint(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario, Class clazz) {
+ return getPoint(BusinessScenario.valueOf(businessId, useCase, scenario), clazz);
+ }
+
+ @Override
+ public T getPoint(@NotNull String useCase, String scenario, Class clazz) {
+ return getPoint(BusinessScenario.valueOf(useCase, scenario), clazz);
+ }
+
+ @Override
+ public T getPoint(@NotNull String scenario, Class clazz) {
+ return getPoint(BusinessScenario.valueOf(scenario), clazz);
+ }
+
+ @Override
+ public T getPoint(@NotNull BusinessScenario businessScenario, Class clazz) {
+ return factory.get(businessScenario, clazz);
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionExecutor.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionExecutor.java
new file mode 100644
index 000000000..5f48652e5
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionExecutor.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.framework.extension.core.context;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @description 扩展组件执行器
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-29 00:32
+ * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor.java
+ */
+@Component
+@Slf4j
+public class ExtensionExecutor extends AbstractComponentExecutor{
+
+ @Autowired
+ private ExtensionContextHolder contextHolder;
+
+
+ @Override
+ protected C locateComponent(Class targetClazz, BusinessScenario businessScenario) {
+ return contextHolder.getPoint(businessScenario, targetClazz);
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionDefinition.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionDefinition.java
new file mode 100644
index 000000000..5feed6660
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionDefinition.java
@@ -0,0 +1,96 @@
+package cn.iocoder.yudao.framework.extension.core.factory;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * @description 扩展定义(扩展坐标),标识唯一一个业务场景实现
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 23:14
+ * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionDefinition.java
+ */
+@Setter
+@Getter
+public class ExtensionDefinition implements Serializable {
+
+ /**
+ * 业务场景唯一标识(id)
+ */
+ private String uniqueIdentify;
+
+ /**
+ * 扩展点实现类名称
+ */
+ private String extensionPointName;
+
+ /**
+ * 业务场景
+ */
+ private BusinessScenario businessScenario;
+
+ /**
+ * 扩展点实现类
+ */
+ private ExtensionPoint extensionPoint;
+
+ /**
+ * class
+ */
+ private Class extensionPointClass;
+
+ public ExtensionDefinition() {
+ }
+
+ public ExtensionDefinition(@NotNull BusinessScenario businessScenario, @NotNull ExtensionPoint extensionPoint) {
+ this.businessScenario = businessScenario;
+ this.extensionPoint = extensionPoint;
+ this.uniqueIdentify = this.businessScenario.getUniqueIdentity();
+ this.extensionPointClass = this.extensionPoint.getClass();
+ }
+
+ /**
+ * 构建definition
+ * @param businessScenario
+ * @param point
+ * @return
+ */
+ public static ExtensionDefinition valueOf(@NotNull BusinessScenario businessScenario, @NotNull ExtensionPoint point) {
+ return new ExtensionDefinition(businessScenario, point);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ExtensionDefinition that = (ExtensionDefinition) o;
+ return Objects.equals(uniqueIdentify, that.uniqueIdentify) && Objects.equals(extensionPointName, that.extensionPointName);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((uniqueIdentify == null) ? 0 : uniqueIdentify.hashCode());
+ result = prime * result + ((extensionPointName == null) ? 0 : extensionPointName.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ExtensionDefinition{" +
+ "uniqueIdentify='" + uniqueIdentify + '\'' +
+ ", extensionPointName='" + extensionPointName + '\'' +
+ '}';
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionFactory.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionFactory.java
new file mode 100644
index 000000000..c03175c13
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionFactory.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.framework.extension.core.factory;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+
+/**
+ * @description 扩展点工厂
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 23:04
+ * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory.java
+ */
+public interface ExtensionFactory {
+
+ /**
+ * 注册所有扩展点实现类
+ * @param basePackage
+ */
+ void register(String basePackage);
+
+ /**
+ * 根据业务场景获取指定类型的扩展点
+ * @param businessScenario
+ * @param clazz
+ * @param
+ * @return
+ */
+ T get(BusinessScenario businessScenario, Class clazz);
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionRegisterFactory.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionRegisterFactory.java
new file mode 100644
index 000000000..15b802436
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionRegisterFactory.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.framework.extension.core.factory;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ClassUtils;
+
+import javax.validation.constraints.NotNull;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @description 注册工厂
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 23:07
+ * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory.java
+ */
+@Component
+@Slf4j
+public class ExtensionRegisterFactory implements ExtensionFactory {
+
+ /**
+ * spring ApplicationContext
+ */
+ private ApplicationContext applicationContext;
+
+ /**
+ * 扩展点实现类集合
+ */
+ private Map registerExtensionBeans = new ConcurrentHashMap<>();
+
+ @Override
+ public void register(String basePackage) {
+ final Map beans = applicationContext.getBeansWithAnnotation(Extension.class);
+ if(beans == null || beans.isEmpty()) {
+ return;
+ }
+
+ beans.values().forEach(point -> doRegister((ExtensionPoint) point));
+ log.info("业务场景相关扩展点注册完成,注册数量: {}", registerExtensionBeans.size());
+ }
+
+ @Override
+ public T get(BusinessScenario businessScenario, Class clazz) {
+
+ final ExtensionDefinition definition = registerExtensionBeans.get(businessScenario.getUniqueIdentity());
+ if(definition == null) {
+ log.error("获取业务场景扩展点实现失败,失败原因:尚未定义该业务场景相关扩展点。{}", businessScenario);
+ throw new RuntimeException("尚未定义该业务场景相关扩展点 [" + businessScenario + "]");
+ }
+
+ return (T) definition.getExtensionPoint();
+ }
+
+ /**
+ * 注册扩展点
+ * @param point
+ */
+ private void doRegister(@NotNull ExtensionPoint point) {
+ Class> extensionClazz = point.getClass();
+
+ if (AopUtils.isAopProxy(point)) {
+ extensionClazz = ClassUtils.getUserClass(point);
+ }
+
+ Extension extension = AnnotationUtils.findAnnotation(extensionClazz, Extension.class);
+ final BusinessScenario businessScenario = BusinessScenario.valueOf(extension.businessId(), extension.useCase(), extension.scenario());
+ final ExtensionDefinition definition = ExtensionDefinition.valueOf(businessScenario, point);
+ final ExtensionDefinition exist = registerExtensionBeans.get(businessScenario.getUniqueIdentity());
+ if(exist != null && !exist.equals(definition)) {
+ throw new RuntimeException("相同的业务场景重复注册了不同类型的扩展点实现 :【" + definition + "】【" + exist + "】");
+ }
+
+ registerExtensionBeans.put(businessScenario.getUniqueIdentity(), definition);
+ }
+
+ public void setApplicationContext(ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/package-info.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/package-info.java
new file mode 100644
index 000000000..636491eca
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * @description core 核心
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 21:54
+ * @class cn.iocoder.yudao.framework.extension.core.package-info.java
+ */
+package cn.iocoder.yudao.framework.extension.core;
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/point/ExtensionPoint.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/point/ExtensionPoint.java
new file mode 100644
index 000000000..e1a86d791
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/point/ExtensionPoint.java
@@ -0,0 +1,11 @@
+package cn.iocoder.yudao.framework.extension.core.point;
+/**
+ * @description 扩展点
+ * 表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用{@linkplain cn.iocoder.yudao.framework.extension.core.stereotype.Extension}(扩展)去实现扩展点。
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 22:06
+ * @class cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint.java
+ */
+public interface ExtensionPoint {
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/stereotype/Extension.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/stereotype/Extension.java
new file mode 100644
index 000000000..eaa9f99f8
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/stereotype/Extension.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.framework.extension.core.stereotype;
+
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.*;
+
+/**
+ * @description 表示带注释的类是“扩展组件”
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 21:59
+ * @class cn.iocoder.yudao.framework.extension.core.stereotype.Extension.java
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Component
+public @interface Extension {
+
+ /**
+ * 业务
+ * 一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务
+ * @return
+ */
+ String businessId() default BusinessScenario.DEFAULT_BUSINESS_ID;
+
+ /**
+ * 用例
+ * 描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
+ * @return
+ */
+ String useCase() default BusinessScenario.DEFAULT_USECASE;
+
+ /**
+ * 场景
+ * 场景也被称为用例的实例(Instance),包括用例所有的可能情况(正常的和异常的)。比如对于"订单支付"这个用例,就有“支付宝支付”、“银行卡支付”、"微信支付"等多个场景
+ * @return
+ */
+ String scenario() default BusinessScenario.DEFAULT_SCENARIO;
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/package-info.java b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/package-info.java
new file mode 100644
index 000000000..1c4ae94ae
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * @description 扩展点组件
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-28 14:35
+ * @class cn.iocoder.yudao.framework.extension.package-info.java
+ */
+package cn.iocoder.yudao.framework.extension;
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/main/resources/META-INF/spring.factories b/yudao-framework/yudao-spring-boot-starter-extension/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..82d14b8b0
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+ cn.iocoder.yudao.framework.extension.config.YudaoExtensionAutoConfiguration
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/Application.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/Application.java
new file mode 100644
index 000000000..a589affd3
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/Application.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.framework.extension;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @description Application
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:32
+ * @class cn.iocoder.yudao.framework.extension.Application.java
+ */
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/ExtensionTest.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/ExtensionTest.java
new file mode 100644
index 000000000..aa3b900c3
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/ExtensionTest.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.framework.extension;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
+import cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor;
+import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
+import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
+import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.math.BigDecimal;
+
+/**
+ * @description
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:30
+ * @class cn.iocoder.yudao.framework.extension.ExtensionTest.java
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SpringBootTest(classes = Application.class)
+@Slf4j
+public class ExtensionTest {
+
+ @Autowired
+ private ExtensionExecutor extensionExecutor;
+
+ @Test
+ public void unifiedOrder() {
+ final BusinessScenario scenario = BusinessScenario.valueOf("pay", "jsapi", "wechat");
+ final TransactionsCommand command = new TransactionsCommand(IdUtil.objectId(), new BigDecimal(105), "Image形象店-深圳腾大-QQ公仔", "https://www.weixin.qq.com/wxpay/pay.php");
+ final TransactionsResult result = extensionExecutor.execute(PayExtensionPoint.class, scenario, extension -> extension.unifiedOrder(command));
+ log.info("result is: {}", JSONUtil.toJsonStr(result));
+ Assert.assertSame("wechat", result.getChannel());
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/package-info.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/package-info.java
new file mode 100644
index 000000000..d0bc63e1c
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * @description
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:25
+ * @class cn.iocoder.yudao.framework.extension.package-info.java
+ */
+package cn.iocoder.yudao.framework.extension;
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/PayExtensionPoint.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/PayExtensionPoint.java
new file mode 100644
index 000000000..9adc56b4c
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/PayExtensionPoint.java
@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.framework.extension.pay;
+
+import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
+import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
+import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
+
+/**
+ * @description 支付操作接口
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:35
+ * @class cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint.java
+ */
+public interface PayExtensionPoint extends ExtensionPoint {
+
+ /**
+ * 统一下单:获取"预支付交易会话标识"
+ * @param command
+ * @return
+ */
+ TransactionsResult unifiedOrder(TransactionsCommand command);
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/command/TransactionsCommand.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/command/TransactionsCommand.java
new file mode 100644
index 000000000..0ac1908dd
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/command/TransactionsCommand.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.framework.extension.pay.command;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * @description 下单请求
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:48
+ * @class cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand.java
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TransactionsCommand implements Serializable {
+ /**
+ * 订单编号
+ */
+ private String orderNo;
+
+ /**
+ * 支付金额
+ */
+ private BigDecimal amount;
+
+ /**
+ * 商品描述
+ */
+ private String productDescription;
+
+ /**
+ * 通知地址
+ */
+ private String notifyUrl;
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/domain/TransactionsResult.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/domain/TransactionsResult.java
new file mode 100644
index 000000000..35861bbd0
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/domain/TransactionsResult.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.framework.extension.pay.domain;
+
+import lombok.*;
+
+import java.io.Serializable;
+
+/**
+ * @description 下单: 预支付交易单返回结果
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:43
+ * @class cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult.java
+ */
+@Data
+@AllArgsConstructor
+public class TransactionsResult implements Serializable {
+
+ /**
+ * 预支付交易会话标识
+ */
+ private String prepayId;
+
+ /**
+ * 订单编号
+ */
+ private String orderNo;
+
+ /**
+ * 系统内部支付单号
+ */
+ private String paymentNo;
+
+ /**
+ * 支付渠道:微信 or 支付宝
+ */
+ private String channel;
+
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/AlipayService.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/AlipayService.java
new file mode 100644
index 000000000..2daac2ff6
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/AlipayService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.framework.extension.pay.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
+import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
+import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
+import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @description 微信 JSAPI 支付
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:38
+ * @class cn.iocoder.yudao.framework.extension.pay.impl.AlipayService.java
+ */
+@Extension(businessId = "pay", useCase = "jsapi", scenario = "alipay")
+@Slf4j
+public class AlipayService implements PayExtensionPoint {
+ @Override
+ public TransactionsResult unifiedOrder(TransactionsCommand command) {
+ log.info("微信 JSAPI 支付:{}", JSONUtil.toJsonStr(command));
+ return new TransactionsResult("alipay26112221580621e9b071c00d9e093b0000", command.getOrderNo(), IdUtil.objectId(), "alipay");
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/WechatPayService.java b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/WechatPayService.java
new file mode 100644
index 000000000..cae294092
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/WechatPayService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.framework.extension.pay.impl;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
+import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
+import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
+import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @description 微信 JSAPI 支付
+ * @author Qingchen
+ * @version 1.0.0
+ * @date 2021-08-30 10:37
+ * @class cn.iocoder.yudao.framework.extension.pay.impl.WechatPayService.java
+ */
+@Extension(businessId = "pay", useCase = "jsapi", scenario = "wechat")
+@Slf4j
+public class WechatPayService implements PayExtensionPoint {
+ @Override
+ public TransactionsResult unifiedOrder(TransactionsCommand command) {
+ log.info("微信 JSAPI 支付:{}", JSONUtil.toJsonStr(command));
+ return new TransactionsResult("wx26112221580621e9b071c00d9e093b0000", command.getOrderNo(), IdUtil.objectId(), "wechat");
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-extension/《芋道 Spring Boot 扩展点组件》.md b/yudao-framework/yudao-spring-boot-starter-extension/《芋道 Spring Boot 扩展点组件》.md
new file mode 100644
index 000000000..59ff2dcb3
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-extension/《芋道 Spring Boot 扩展点组件》.md
@@ -0,0 +1,19 @@
+### 作用
+
+ 为了解决同一个流程不同业务有不同处理逻辑而产生,减少代码中 if else 逻辑,降低代码的耦合性,通过统一的扩展形式来支撑业务的变化。
+
+### 原理
+
+ https://blog.csdn.net/significantfrank/article/details/100074716
+
+
+
+### 使用介绍
+
+参考测试代码 `cn.iocoder.yudao.framework.extension.ExtensionTest`
+
+
+
+
+
+