Browse Source

扩展组件

QingChen 3 years ago
parent
commit
df3cd34b30
26 changed files with 1097 additions and 0 deletions
  1. 1 0
      yudao-framework/pom.xml
  2. 68 0
      yudao-framework/yudao-spring-boot-starter-extension/pom.xml
  3. 62 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/config/YudaoExtensionAutoConfiguration.java
  4. 142 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/BusinessScenario.java
  5. 41 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/ExtensionBootstrap.java
  6. 131 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/AbstractComponentExecutor.java
  7. 56 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContext.java
  8. 45 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContextHolder.java
  9. 28 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionExecutor.java
  10. 96 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionDefinition.java
  11. 29 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionFactory.java
  12. 86 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionRegisterFactory.java
  13. 8 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/package-info.java
  14. 11 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/point/ExtensionPoint.java
  15. 41 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/stereotype/Extension.java
  16. 8 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/package-info.java
  17. 2 0
      yudao-framework/yudao-spring-boot-starter-extension/src/main/resources/META-INF/spring.factories
  18. 19 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/Application.java
  19. 43 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/ExtensionTest.java
  20. 8 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/package-info.java
  21. 22 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/PayExtensionPoint.java
  22. 40 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/command/TransactionsCommand.java
  23. 39 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/domain/TransactionsResult.java
  24. 26 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/AlipayService.java
  25. 26 0
      yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/WechatPayService.java
  26. 19 0
      yudao-framework/yudao-spring-boot-starter-extension/《芋道 Spring Boot 扩展点组件》.md

+ 1 - 0
yudao-framework/pom.xml

@@ -28,6 +28,7 @@
         <module>yudao-spring-boot-starter-biz-operatelog</module>
         <module>yudao-spring-boot-starter-biz-dict</module>
         <module>yudao-spring-boot-starter-biz-sms</module>
+        <module>yudao-spring-boot-starter-extension</module>
     </modules>
 
     <artifactId>yudao-framework</artifactId>

+ 68 - 0
yudao-framework/yudao-spring-boot-starter-extension/pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>yudao-framework</artifactId>
+        <groupId>cn.iocoder.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>yudao-spring-boot-starter-extension</artifactId>
+    <packaging>jar</packaging>
+
+    <name>${artifactId}</name>
+    <description>扩展点组件</description>
+    <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
+
+    <properties>
+
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+        </dependency>
+
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Spring 核心 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- 测试包 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- 测试包 -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- 工具类相关 -->
+        <dependency>
+            <groupId>jakarta.validation</groupId>
+            <artifactId>jakarta.validation-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 62 - 0
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();
+    }
+}

+ 142 - 0
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, 用来标识系统中唯一的一个场景<br/>
+ * @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 + '\'' +
+                '}';
+    }
+}

+ 41 - 0
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;
+    }
+}

+ 131 - 0
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 <R>
+     * @param <T>
+     * @return
+     */
+    public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String businessId, String useCase, String scenario, Function<T, R> function) {
+        return execute(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), function);
+    }
+
+
+    /**
+     * ("用例" + "场景")执行扩展组件,并返回执行结果
+     * @param targetClazz
+     * @param useCase
+     * @param scenario
+     * @param function
+     * @param <R>
+     * @param <T>
+     * @return
+     */
+    public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String useCase, String scenario, Function<T, R> function) {
+        return execute(targetClazz, BusinessScenario.valueOf(useCase, scenario), function);
+    }
+
+    /**
+     * ("场景")执行扩展组件,并返回执行结果
+     * @param targetClazz
+     * @param scenario
+     * @param function
+     * @param <R>
+     * @param <T>
+     * @return
+     */
+    public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String scenario, Function<T, R> function) {
+        return execute(targetClazz, BusinessScenario.valueOf(scenario), function);
+    }
+
+    /**
+     * 执行扩展组件,并返回执行结果
+     * @param targetClazz
+     * @param businessScenario
+     * @param function
+     * @param <R> Response Type
+     * @param <T> Parameter Type
+     * @return
+     */
+    public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, BusinessScenario businessScenario, Function<T, R> function) {
+        T component = locateComponent(targetClazz, businessScenario);
+        return function.apply(component);
+    }
+
+    /**
+     * ("业务" + "用例" + "场景")执行扩展组件,适用于无返回值的业务
+     * @param targetClazz
+     * @param businessId
+     * @param useCase
+     * @param scenario
+     * @param consumer
+     * @param <T>
+     */
+    public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String businessId, String useCase, String scenario, Consumer<T> consumer) {
+        accept(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), consumer);
+    }
+
+    /**
+     * ("场景")执行扩展组件,适用于无返回值的业务
+     * @param targetClazz
+     * @param useCase
+     * @param scenario
+     * @param consumer
+     * @param <T>
+     */
+    public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String useCase, String scenario, Consumer<T> consumer) {
+        accept(targetClazz, BusinessScenario.valueOf(useCase, scenario), consumer);
+    }
+
+    /**
+     * ("场景")执行扩展组件,适用于无返回值的业务
+     * @param targetClazz
+     * @param scenario
+     * @param consumer
+     * @param <T>
+     */
+    public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String scenario, Consumer<T> consumer) {
+        accept(targetClazz, BusinessScenario.valueOf(scenario), consumer);
+    }
+
+    /**
+     * 执行扩展组件,适用于无返回值的业务
+     * @param targetClazz
+     * @param businessScenario
+     * @param consumer
+     * @param <T> Parameter Type
+     */
+    public <T extends ExtensionPoint> void accept(Class<T> targetClazz, BusinessScenario businessScenario, Consumer<T> consumer) {
+        T component = locateComponent(targetClazz, businessScenario);
+        consumer.accept(component);
+    }
+
+    /**
+     * 获取/定位扩展点组件
+     * @param targetClazz
+     * @param businessScenario
+     * @param <C>
+     * @return
+     */
+    protected abstract <C extends ExtensionPoint> C locateComponent(Class<C> targetClazz, BusinessScenario businessScenario);
+}

+ 56 - 0
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 <T>
+     * @return
+     */
+    <T extends ExtensionPoint> T getPoint(String businessId, String useCase, String scenario, Class<T> clazz);
+
+    /**
+     * 根据("实例" + "场景")获取扩展点组件实现类,其中:业务id(businessId)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_BUSINESS_ID}
+     * @param useCase
+     * @param scenario
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    <T extends ExtensionPoint> T getPoint(String useCase, String scenario, Class<T> clazz);
+
+    /**
+     * 根据("场景")获取扩展点组件实现类 <br/>
+     * 其中:
+     *    业务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 <T>
+     * @return
+     */
+    <T extends ExtensionPoint> T getPoint(String scenario, Class<T> clazz);
+
+    /**
+     * 根据业务场景唯一标识获取扩展点组件实现类
+     * @param businessScenario
+     * @param clazz
+     * @param <T>
+     * @return
+     */
+    <T extends ExtensionPoint> T getPoint(BusinessScenario businessScenario, Class<T> clazz);
+}

+ 45 - 0
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 extends ExtensionPoint> T getPoint(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario, Class<T> clazz) {
+        return getPoint(BusinessScenario.valueOf(businessId, useCase, scenario), clazz);
+    }
+
+    @Override
+    public <T extends ExtensionPoint> T getPoint(@NotNull String useCase, String scenario, Class<T> clazz) {
+        return getPoint(BusinessScenario.valueOf(useCase, scenario), clazz);
+    }
+
+    @Override
+    public <T extends ExtensionPoint> T getPoint(@NotNull String scenario, Class<T> clazz) {
+        return getPoint(BusinessScenario.valueOf(scenario), clazz);
+    }
+
+    @Override
+    public <T extends ExtensionPoint> T getPoint(@NotNull BusinessScenario businessScenario, Class<T> clazz) {
+        return factory.get(businessScenario, clazz);
+    }
+}

+ 28 - 0
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 extends ExtensionPoint> C locateComponent(Class<C> targetClazz, BusinessScenario businessScenario) {
+        return contextHolder.getPoint(businessScenario, targetClazz);
+    }
+}

+ 96 - 0
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 + '\'' +
+                '}';
+    }
+}

+ 29 - 0
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 <T>
+     * @return
+     */
+    <T extends ExtensionPoint> T get(BusinessScenario businessScenario, Class<T> clazz);
+}

+ 86 - 0
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<String, ExtensionDefinition> registerExtensionBeans = new ConcurrentHashMap<>();
+
+    @Override
+    public void register(String basePackage) {
+        final Map<String, Object> 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 extends ExtensionPoint> T get(BusinessScenario businessScenario, Class<T> 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;
+    }
+}

+ 8 - 0
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;

+ 11 - 0
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 扩展点 <br/>
+ * 表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用{@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 {
+}

+ 41 - 0
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 {
+
+    /**
+     * 业务 <br/>
+     * 一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务
+     * @return
+     */
+    String businessId() default BusinessScenario.DEFAULT_BUSINESS_ID;
+
+    /**
+     * 用例 <br/>
+     * 描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
+     * @return
+     */
+    String useCase() default BusinessScenario.DEFAULT_USECASE;
+
+    /**
+     * 场景 <br/>
+     * 场景也被称为用例的实例(Instance),包括用例所有的可能情况(正常的和异常的)。比如对于"订单支付"这个用例,就有“支付宝支付”、“银行卡支付”、"微信支付"等多个场景
+     * @return
+     */
+    String scenario() default BusinessScenario.DEFAULT_SCENARIO;
+}

+ 8 - 0
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;

+ 2 - 0
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

+ 19 - 0
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);
+    }
+}

+ 43 - 0
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());
+    }
+}

+ 8 - 0
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;

+ 22 - 0
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);
+}

+ 40 - 0
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;
+}

+ 39 - 0
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;
+
+
+}

+ 26 - 0
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");
+    }
+}

+ 26 - 0
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");
+    }
+}

+ 19 - 0
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`
+
+ 
+
+
+
+