Commit df3cd34b30a2990b8b8c65656c7e1fe4e5dad6b8

Authored by QingChen
1 parent 2bcc5505

扩展组件

Showing 26 changed files with 1097 additions and 0 deletions
yudao-framework/pom.xml
... ... @@ -28,6 +28,7 @@
28 28 <module>yudao-spring-boot-starter-biz-operatelog</module>
29 29 <module>yudao-spring-boot-starter-biz-dict</module>
30 30 <module>yudao-spring-boot-starter-biz-sms</module>
  31 + <module>yudao-spring-boot-starter-extension</module>
31 32 </modules>
32 33  
33 34 <artifactId>yudao-framework</artifactId>
... ...
yudao-framework/yudao-spring-boot-starter-extension/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <parent>
  6 + <artifactId>yudao-framework</artifactId>
  7 + <groupId>cn.iocoder.boot</groupId>
  8 + <version>${revision}</version>
  9 + </parent>
  10 + <modelVersion>4.0.0</modelVersion>
  11 +
  12 + <artifactId>yudao-spring-boot-starter-extension</artifactId>
  13 + <packaging>jar</packaging>
  14 +
  15 + <name>${artifactId}</name>
  16 + <description>扩展点组件</description>
  17 + <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
  18 +
  19 + <properties>
  20 +
  21 + </properties>
  22 +
  23 + <dependencies>
  24 + <dependency>
  25 + <groupId>cn.iocoder.boot</groupId>
  26 + <artifactId>yudao-common</artifactId>
  27 + </dependency>
  28 +
  29 + <!-- Spring 核心 -->
  30 + <dependency>
  31 + <groupId>org.springframework</groupId>
  32 + <artifactId>spring-context</artifactId>
  33 + <scope>provided</scope>
  34 + </dependency>
  35 +
  36 + <dependency>
  37 + <groupId>org.springframework</groupId>
  38 + <artifactId>spring-beans</artifactId>
  39 + <scope>provided</scope>
  40 + </dependency>
  41 +
  42 + <!-- Spring 核心 -->
  43 + <dependency>
  44 + <groupId>org.springframework.boot</groupId>
  45 + <artifactId>spring-boot-starter-aop</artifactId>
  46 + </dependency>
  47 +
  48 + <!-- 测试包 -->
  49 + <dependency>
  50 + <groupId>org.springframework.boot</groupId>
  51 + <artifactId>spring-boot-starter-test</artifactId>
  52 + <scope>test</scope>
  53 + </dependency>
  54 +
  55 + <!-- 测试包 -->
  56 + <dependency>
  57 + <groupId>junit</groupId>
  58 + <artifactId>junit</artifactId>
  59 + <scope>test</scope>
  60 + </dependency>
  61 +
  62 + <!-- 工具类相关 -->
  63 + <dependency>
  64 + <groupId>jakarta.validation</groupId>
  65 + <artifactId>jakarta.validation-api</artifactId>
  66 + </dependency>
  67 + </dependencies>
  68 +</project>
0 69 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/config/YudaoExtensionAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.config;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.ExtensionBootstrap;
  4 +import cn.iocoder.yudao.framework.extension.core.context.ExtensionContext;
  5 +import cn.iocoder.yudao.framework.extension.core.context.ExtensionContextHolder;
  6 +import cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor;
  7 +import cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory;
  8 +import cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory;
  9 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  10 +import org.springframework.context.annotation.Bean;
  11 +import org.springframework.context.annotation.Configuration;
  12 +
  13 +/**
  14 + * @description 扩展点组件自动装配
  15 + * @author Qingchen
  16 + * @version 1.0.0
  17 + * @date 2021-08-28 21:50
  18 + * @class cn.iocoder.yudao.framework.extension.config.YudaoExtensionAutoConfiguration.java
  19 + */
  20 +@Configuration
  21 +public class YudaoExtensionAutoConfiguration {
  22 +
  23 + /**
  24 + * 组件初始化
  25 + * @return
  26 + */
  27 + @Bean(initMethod = "init")
  28 + @ConditionalOnMissingBean(ExtensionBootstrap.class)
  29 + public ExtensionBootstrap bootstrap() {
  30 + return new ExtensionBootstrap();
  31 + }
  32 +
  33 + /**
  34 + * 扩展点工厂
  35 + * @return
  36 + */
  37 + @Bean
  38 + @ConditionalOnMissingBean({ExtensionRegisterFactory.class, ExtensionFactory.class})
  39 + public ExtensionRegisterFactory registerFactory() {
  40 + return new ExtensionRegisterFactory();
  41 + }
  42 +
  43 + /**
  44 + * 扩展组件上下文对象
  45 + * @return
  46 + */
  47 + @Bean
  48 + @ConditionalOnMissingBean({ExtensionContextHolder.class, ExtensionContext.class})
  49 + public ExtensionContextHolder context() {
  50 + return new ExtensionContextHolder();
  51 + }
  52 +
  53 + /**
  54 + * 扩展组件执行器
  55 + * @return
  56 + */
  57 + @Bean
  58 + @ConditionalOnMissingBean(ExtensionExecutor.class)
  59 + public ExtensionExecutor executor() {
  60 + return new ExtensionExecutor();
  61 + }
  62 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/BusinessScenario.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core;
  2 +
  3 +import javax.validation.constraints.NotNull;
  4 +import java.io.Serializable;
  5 +import java.util.StringJoiner;
  6 +
  7 +/**
  8 + * @description 业务场景 = businessId + useCase + scenario, 用来标识系统中唯一的一个场景<br/>
  9 + * @author Qingchen
  10 + * @version 1.0.0
  11 + * @date 2021-08-28 22:19
  12 + * @class cn.iocoder.yudao.framework.extension.core.BusinessScenario.java
  13 + */
  14 +public class BusinessScenario implements Serializable {
  15 +
  16 + /**
  17 + * 默认业务id
  18 + */
  19 + public final static String DEFAULT_BUSINESS_ID = "#defaultBusinessId#";
  20 +
  21 + /**
  22 + * 默认用例
  23 + */
  24 + public final static String DEFAULT_USECASE = "#defaultUseCase#";
  25 +
  26 + /**
  27 + * 默认场景
  28 + */
  29 + public final static String DEFAULT_SCENARIO = "#defaultScenario#";
  30 +
  31 + /**
  32 + * 分隔符
  33 + */
  34 + private final static String DOT_SEPARATOR = ".";
  35 +
  36 + /**
  37 + * 业务Id
  38 + */
  39 + private String businessId;
  40 +
  41 + /**
  42 + * 用例
  43 + */
  44 + private String useCase;
  45 +
  46 + /**
  47 + * 场景
  48 + */
  49 + private String scenario;
  50 +
  51 + public BusinessScenario() {
  52 + this.businessId = DEFAULT_BUSINESS_ID;
  53 + this.useCase = DEFAULT_USECASE;
  54 + this.scenario = DEFAULT_SCENARIO;
  55 + }
  56 +
  57 + public BusinessScenario(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario) {
  58 + this.businessId = businessId;
  59 + this.useCase = useCase;
  60 + this.scenario = scenario;
  61 + }
  62 +
  63 + public BusinessScenario(@NotNull String scenario) {
  64 + this();
  65 + this.scenario = scenario;
  66 + }
  67 +
  68 + public BusinessScenario(@NotNull String useCase, @NotNull String scenario) {
  69 + this(DEFAULT_BUSINESS_ID, useCase, scenario);
  70 + }
  71 +
  72 + public String getBusinessId() {
  73 + return businessId;
  74 + }
  75 +
  76 + public void setBusinessId(String businessId) {
  77 + this.businessId = businessId;
  78 + }
  79 +
  80 + public String getUseCase() {
  81 + return useCase;
  82 + }
  83 +
  84 + public void setUseCase(String useCase) {
  85 + this.useCase = useCase;
  86 + }
  87 +
  88 + public String getScenario() {
  89 + return scenario;
  90 + }
  91 +
  92 + public void setScenario(String scenario) {
  93 + this.scenario = scenario;
  94 + }
  95 +
  96 + /**
  97 + * 构建业务场景
  98 + * @param businessId
  99 + * @param useCase
  100 + * @param scenario
  101 + * @return
  102 + */
  103 + public static BusinessScenario valueOf(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario) {
  104 + return new BusinessScenario(businessId, useCase, scenario);
  105 + }
  106 +
  107 + /**
  108 + * 构建业务场景
  109 + * @param useCase
  110 + * @param scenario
  111 + * @return
  112 + */
  113 + public static BusinessScenario valueOf(@NotNull String useCase, @NotNull String scenario) {
  114 + return new BusinessScenario(useCase, scenario);
  115 + }
  116 +
  117 + /**
  118 + * 构建业务场景
  119 + * @param scenario
  120 + * @return
  121 + */
  122 + public static BusinessScenario valueOf(@NotNull String scenario) {
  123 + return new BusinessScenario(scenario);
  124 + }
  125 +
  126 + /**
  127 + * 业务场景唯一标识
  128 + * @return
  129 + */
  130 + public String getUniqueIdentity(){
  131 + return new StringJoiner(DOT_SEPARATOR).add(businessId).add(useCase).add(scenario).toString();
  132 + }
  133 +
  134 + @Override
  135 + public String toString() {
  136 + return "BusinessScenario{" +
  137 + "businessId='" + businessId + '\'' +
  138 + ", useCase='" + useCase + '\'' +
  139 + ", scenario='" + scenario + '\'' +
  140 + '}';
  141 + }
  142 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/ExtensionBootstrap.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory;
  4 +import org.springframework.beans.BeansException;
  5 +import org.springframework.beans.factory.annotation.Autowired;
  6 +import org.springframework.context.ApplicationContext;
  7 +import org.springframework.context.ApplicationContextAware;
  8 +
  9 +import javax.annotation.PostConstruct;
  10 +
  11 +/**
  12 + * @description
  13 + * @author Qingchen
  14 + * @version 1.0.0
  15 + * @date 2021-08-29 00:18
  16 + * @class cn.iocoder.yudao.framework.extension.core.ExtensionBootstrap.java
  17 + */
  18 +public class ExtensionBootstrap implements ApplicationContextAware {
  19 +
  20 + /**
  21 + * spring 容器
  22 + */
  23 + private ApplicationContext applicationContext;
  24 +
  25 + @Autowired
  26 + private ExtensionRegisterFactory registerFactory;
  27 +
  28 + /**
  29 + * 初始化
  30 + */
  31 + @PostConstruct
  32 + public void init() {
  33 + registerFactory.setApplicationContext(applicationContext);
  34 + registerFactory.register(null);
  35 + }
  36 +
  37 + @Override
  38 + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  39 + this.applicationContext = applicationContext;
  40 + }
  41 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/AbstractComponentExecutor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.context;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +
  6 +import java.util.function.Consumer;
  7 +import java.util.function.Function;
  8 +
  9 +/**
  10 + * @description 执行器通用方法
  11 + * @author Qingchen
  12 + * @version 1.0.0
  13 + * @date 2021-08-29 00:38
  14 + * @class cn.iocoder.yudao.framework.extension.core.context.AbstractComponentExecutor.java
  15 + */
  16 +public abstract class AbstractComponentExecutor {
  17 +
  18 + /**
  19 + * ("业务" + "用例" + "场景")执行扩展组件,并返回执行结果
  20 + * @param targetClazz
  21 + * @param businessId
  22 + * @param useCase
  23 + * @param scenario
  24 + * @param function
  25 + * @param <R>
  26 + * @param <T>
  27 + * @return
  28 + */
  29 + public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String businessId, String useCase, String scenario, Function<T, R> function) {
  30 + return execute(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), function);
  31 + }
  32 +
  33 +
  34 + /**
  35 + * ("用例" + "场景")执行扩展组件,并返回执行结果
  36 + * @param targetClazz
  37 + * @param useCase
  38 + * @param scenario
  39 + * @param function
  40 + * @param <R>
  41 + * @param <T>
  42 + * @return
  43 + */
  44 + public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String useCase, String scenario, Function<T, R> function) {
  45 + return execute(targetClazz, BusinessScenario.valueOf(useCase, scenario), function);
  46 + }
  47 +
  48 + /**
  49 + * ("场景")执行扩展组件,并返回执行结果
  50 + * @param targetClazz
  51 + * @param scenario
  52 + * @param function
  53 + * @param <R>
  54 + * @param <T>
  55 + * @return
  56 + */
  57 + public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, String scenario, Function<T, R> function) {
  58 + return execute(targetClazz, BusinessScenario.valueOf(scenario), function);
  59 + }
  60 +
  61 + /**
  62 + * 执行扩展组件,并返回执行结果
  63 + * @param targetClazz
  64 + * @param businessScenario
  65 + * @param function
  66 + * @param <R> Response Type
  67 + * @param <T> Parameter Type
  68 + * @return
  69 + */
  70 + public <R, T extends ExtensionPoint> R execute(Class<T> targetClazz, BusinessScenario businessScenario, Function<T, R> function) {
  71 + T component = locateComponent(targetClazz, businessScenario);
  72 + return function.apply(component);
  73 + }
  74 +
  75 + /**
  76 + * ("业务" + "用例" + "场景")执行扩展组件,适用于无返回值的业务
  77 + * @param targetClazz
  78 + * @param businessId
  79 + * @param useCase
  80 + * @param scenario
  81 + * @param consumer
  82 + * @param <T>
  83 + */
  84 + public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String businessId, String useCase, String scenario, Consumer<T> consumer) {
  85 + accept(targetClazz, BusinessScenario.valueOf(businessId, useCase, scenario), consumer);
  86 + }
  87 +
  88 + /**
  89 + * ("场景")执行扩展组件,适用于无返回值的业务
  90 + * @param targetClazz
  91 + * @param useCase
  92 + * @param scenario
  93 + * @param consumer
  94 + * @param <T>
  95 + */
  96 + public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String useCase, String scenario, Consumer<T> consumer) {
  97 + accept(targetClazz, BusinessScenario.valueOf(useCase, scenario), consumer);
  98 + }
  99 +
  100 + /**
  101 + * ("场景")执行扩展组件,适用于无返回值的业务
  102 + * @param targetClazz
  103 + * @param scenario
  104 + * @param consumer
  105 + * @param <T>
  106 + */
  107 + public <T extends ExtensionPoint> void accept(Class<T> targetClazz, String scenario, Consumer<T> consumer) {
  108 + accept(targetClazz, BusinessScenario.valueOf(scenario), consumer);
  109 + }
  110 +
  111 + /**
  112 + * 执行扩展组件,适用于无返回值的业务
  113 + * @param targetClazz
  114 + * @param businessScenario
  115 + * @param consumer
  116 + * @param <T> Parameter Type
  117 + */
  118 + public <T extends ExtensionPoint> void accept(Class<T> targetClazz, BusinessScenario businessScenario, Consumer<T> consumer) {
  119 + T component = locateComponent(targetClazz, businessScenario);
  120 + consumer.accept(component);
  121 + }
  122 +
  123 + /**
  124 + * 获取/定位扩展点组件
  125 + * @param targetClazz
  126 + * @param businessScenario
  127 + * @param <C>
  128 + * @return
  129 + */
  130 + protected abstract <C extends ExtensionPoint> C locateComponent(Class<C> targetClazz, BusinessScenario businessScenario);
  131 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContext.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.context;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +
  6 +/**
  7 + * @description 上下文,包含各个扩展点的相关操作
  8 + * @author Qingchen
  9 + * @version 1.0.0
  10 + * @date 2021-08-28 22:15
  11 + * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionContext.java
  12 + */
  13 +public interface ExtensionContext {
  14 +
  15 + /**
  16 + * 根据业务场景唯一标识获取扩展点组件实现类
  17 + * @param businessId
  18 + * @param useCase
  19 + * @param scenario
  20 + * @param clazz
  21 + * @param <T>
  22 + * @return
  23 + */
  24 + <T extends ExtensionPoint> T getPoint(String businessId, String useCase, String scenario, Class<T> clazz);
  25 +
  26 + /**
  27 + * 根据("实例" + "场景")获取扩展点组件实现类,其中:业务id(businessId)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_BUSINESS_ID}
  28 + * @param useCase
  29 + * @param scenario
  30 + * @param clazz
  31 + * @param <T>
  32 + * @return
  33 + */
  34 + <T extends ExtensionPoint> T getPoint(String useCase, String scenario, Class<T> clazz);
  35 +
  36 + /**
  37 + * 根据("场景")获取扩展点组件实现类 <br/>
  38 + * 其中:
  39 + * 业务id(businessId)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_BUSINESS_ID}
  40 + * 实例(useCase)= {@linkplain cn.iocoder.yudao.framework.extension.core.BusinessScenario.DEFAULT_USECASE}
  41 + * @param scenario
  42 + * @param clazz
  43 + * @param <T>
  44 + * @return
  45 + */
  46 + <T extends ExtensionPoint> T getPoint(String scenario, Class<T> clazz);
  47 +
  48 + /**
  49 + * 根据业务场景唯一标识获取扩展点组件实现类
  50 + * @param businessScenario
  51 + * @param clazz
  52 + * @param <T>
  53 + * @return
  54 + */
  55 + <T extends ExtensionPoint> T getPoint(BusinessScenario businessScenario, Class<T> clazz);
  56 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionContextHolder.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.context;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory;
  5 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import javax.validation.constraints.NotNull;
  11 +
  12 +/**
  13 + * @description 上下文及扩展点组件工厂的持有类
  14 + * @author Qingchen
  15 + * @version 1.0.0
  16 + * @date 2021-08-29 00:29
  17 + * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionContextHolder.java
  18 + */
  19 +@Component
  20 +@Slf4j
  21 +public class ExtensionContextHolder implements ExtensionContext{
  22 +
  23 + @Autowired
  24 + private ExtensionFactory factory;
  25 +
  26 + @Override
  27 + public <T extends ExtensionPoint> T getPoint(@NotNull String businessId, @NotNull String useCase, @NotNull String scenario, Class<T> clazz) {
  28 + return getPoint(BusinessScenario.valueOf(businessId, useCase, scenario), clazz);
  29 + }
  30 +
  31 + @Override
  32 + public <T extends ExtensionPoint> T getPoint(@NotNull String useCase, String scenario, Class<T> clazz) {
  33 + return getPoint(BusinessScenario.valueOf(useCase, scenario), clazz);
  34 + }
  35 +
  36 + @Override
  37 + public <T extends ExtensionPoint> T getPoint(@NotNull String scenario, Class<T> clazz) {
  38 + return getPoint(BusinessScenario.valueOf(scenario), clazz);
  39 + }
  40 +
  41 + @Override
  42 + public <T extends ExtensionPoint> T getPoint(@NotNull BusinessScenario businessScenario, Class<T> clazz) {
  43 + return factory.get(businessScenario, clazz);
  44 + }
  45 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/context/ExtensionExecutor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.context;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.beans.factory.annotation.Autowired;
  7 +import org.springframework.stereotype.Component;
  8 +
  9 +/**
  10 + * @description 扩展组件执行器
  11 + * @author Qingchen
  12 + * @version 1.0.0
  13 + * @date 2021-08-29 00:32
  14 + * @class cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor.java
  15 + */
  16 +@Component
  17 +@Slf4j
  18 +public class ExtensionExecutor extends AbstractComponentExecutor{
  19 +
  20 + @Autowired
  21 + private ExtensionContextHolder contextHolder;
  22 +
  23 +
  24 + @Override
  25 + protected <C extends ExtensionPoint> C locateComponent(Class<C> targetClazz, BusinessScenario businessScenario) {
  26 + return contextHolder.getPoint(businessScenario, targetClazz);
  27 + }
  28 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionDefinition.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.factory;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +import lombok.Getter;
  6 +import lombok.Setter;
  7 +
  8 +import javax.validation.constraints.NotNull;
  9 +import java.io.Serializable;
  10 +import java.util.Objects;
  11 +
  12 +/**
  13 + * @description 扩展定义(扩展坐标),标识唯一一个业务场景实现
  14 + * @author Qingchen
  15 + * @version 1.0.0
  16 + * @date 2021-08-28 23:14
  17 + * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionDefinition.java
  18 + */
  19 +@Setter
  20 +@Getter
  21 +public class ExtensionDefinition implements Serializable {
  22 +
  23 + /**
  24 + * 业务场景唯一标识(id)
  25 + */
  26 + private String uniqueIdentify;
  27 +
  28 + /**
  29 + * 扩展点实现类名称
  30 + */
  31 + private String extensionPointName;
  32 +
  33 + /**
  34 + * 业务场景
  35 + */
  36 + private BusinessScenario businessScenario;
  37 +
  38 + /**
  39 + * 扩展点实现类
  40 + */
  41 + private ExtensionPoint extensionPoint;
  42 +
  43 + /**
  44 + * class
  45 + */
  46 + private Class extensionPointClass;
  47 +
  48 + public ExtensionDefinition() {
  49 + }
  50 +
  51 + public ExtensionDefinition(@NotNull BusinessScenario businessScenario, @NotNull ExtensionPoint extensionPoint) {
  52 + this.businessScenario = businessScenario;
  53 + this.extensionPoint = extensionPoint;
  54 + this.uniqueIdentify = this.businessScenario.getUniqueIdentity();
  55 + this.extensionPointClass = this.extensionPoint.getClass();
  56 + }
  57 +
  58 + /**
  59 + * 构建definition
  60 + * @param businessScenario
  61 + * @param point
  62 + * @return
  63 + */
  64 + public static ExtensionDefinition valueOf(@NotNull BusinessScenario businessScenario, @NotNull ExtensionPoint point) {
  65 + return new ExtensionDefinition(businessScenario, point);
  66 + }
  67 +
  68 + @Override
  69 + public boolean equals(Object o) {
  70 + if (this == o) {
  71 + return true;
  72 + }
  73 + if (o == null || getClass() != o.getClass()) {
  74 + return false;
  75 + }
  76 + ExtensionDefinition that = (ExtensionDefinition) o;
  77 + return Objects.equals(uniqueIdentify, that.uniqueIdentify) && Objects.equals(extensionPointName, that.extensionPointName);
  78 + }
  79 +
  80 + @Override
  81 + public int hashCode() {
  82 + final int prime = 31;
  83 + int result = 1;
  84 + result = prime * result + ((uniqueIdentify == null) ? 0 : uniqueIdentify.hashCode());
  85 + result = prime * result + ((extensionPointName == null) ? 0 : extensionPointName.hashCode());
  86 + return result;
  87 + }
  88 +
  89 + @Override
  90 + public String toString() {
  91 + return "ExtensionDefinition{" +
  92 + "uniqueIdentify='" + uniqueIdentify + '\'' +
  93 + ", extensionPointName='" + extensionPointName + '\'' +
  94 + '}';
  95 + }
  96 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionFactory.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.factory;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +
  6 +/**
  7 + * @description 扩展点工厂
  8 + * @author Qingchen
  9 + * @version 1.0.0
  10 + * @date 2021-08-28 23:04
  11 + * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionFactory.java
  12 + */
  13 +public interface ExtensionFactory {
  14 +
  15 + /**
  16 + * 注册所有扩展点实现类
  17 + * @param basePackage
  18 + */
  19 + void register(String basePackage);
  20 +
  21 + /**
  22 + * 根据业务场景获取指定类型的扩展点
  23 + * @param businessScenario
  24 + * @param clazz
  25 + * @param <T>
  26 + * @return
  27 + */
  28 + <T extends ExtensionPoint> T get(BusinessScenario businessScenario, Class<T> clazz);
  29 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/factory/ExtensionRegisterFactory.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.factory;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  5 +import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.springframework.aop.support.AopUtils;
  8 +import org.springframework.context.ApplicationContext;
  9 +import org.springframework.core.annotation.AnnotationUtils;
  10 +import org.springframework.stereotype.Component;
  11 +import org.springframework.util.ClassUtils;
  12 +
  13 +import javax.validation.constraints.NotNull;
  14 +import java.util.Map;
  15 +import java.util.concurrent.ConcurrentHashMap;
  16 +
  17 +/**
  18 + * @description 注册工厂
  19 + * @author Qingchen
  20 + * @version 1.0.0
  21 + * @date 2021-08-28 23:07
  22 + * @class cn.iocoder.yudao.framework.extension.core.factory.ExtensionRegisterFactory.java
  23 + */
  24 +@Component
  25 +@Slf4j
  26 +public class ExtensionRegisterFactory implements ExtensionFactory {
  27 +
  28 + /**
  29 + * spring ApplicationContext
  30 + */
  31 + private ApplicationContext applicationContext;
  32 +
  33 + /**
  34 + * 扩展点实现类集合
  35 + */
  36 + private Map<String, ExtensionDefinition> registerExtensionBeans = new ConcurrentHashMap<>();
  37 +
  38 + @Override
  39 + public void register(String basePackage) {
  40 + final Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Extension.class);
  41 + if(beans == null || beans.isEmpty()) {
  42 + return;
  43 + }
  44 +
  45 + beans.values().forEach(point -> doRegister((ExtensionPoint) point));
  46 + log.info("业务场景相关扩展点注册完成,注册数量: {}", registerExtensionBeans.size());
  47 + }
  48 +
  49 + @Override
  50 + public <T extends ExtensionPoint> T get(BusinessScenario businessScenario, Class<T> clazz) {
  51 +
  52 + final ExtensionDefinition definition = registerExtensionBeans.get(businessScenario.getUniqueIdentity());
  53 + if(definition == null) {
  54 + log.error("获取业务场景扩展点实现失败,失败原因:尚未定义该业务场景相关扩展点。{}", businessScenario);
  55 + throw new RuntimeException("尚未定义该业务场景相关扩展点 [" + businessScenario + "]");
  56 + }
  57 +
  58 + return (T) definition.getExtensionPoint();
  59 + }
  60 +
  61 + /**
  62 + * 注册扩展点
  63 + * @param point
  64 + */
  65 + private void doRegister(@NotNull ExtensionPoint point) {
  66 + Class<?> extensionClazz = point.getClass();
  67 +
  68 + if (AopUtils.isAopProxy(point)) {
  69 + extensionClazz = ClassUtils.getUserClass(point);
  70 + }
  71 +
  72 + Extension extension = AnnotationUtils.findAnnotation(extensionClazz, Extension.class);
  73 + final BusinessScenario businessScenario = BusinessScenario.valueOf(extension.businessId(), extension.useCase(), extension.scenario());
  74 + final ExtensionDefinition definition = ExtensionDefinition.valueOf(businessScenario, point);
  75 + final ExtensionDefinition exist = registerExtensionBeans.get(businessScenario.getUniqueIdentity());
  76 + if(exist != null && !exist.equals(definition)) {
  77 + throw new RuntimeException("相同的业务场景重复注册了不同类型的扩展点实现 :【" + definition + "】【" + exist + "】");
  78 + }
  79 +
  80 + registerExtensionBeans.put(businessScenario.getUniqueIdentity(), definition);
  81 + }
  82 +
  83 + public void setApplicationContext(ApplicationContext applicationContext) {
  84 + this.applicationContext = applicationContext;
  85 + }
  86 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/package-info.java 0 → 100644
  1 +/**
  2 + * @description core 核心
  3 + * @author Qingchen
  4 + * @version 1.0.0
  5 + * @date 2021-08-28 21:54
  6 + * @class cn.iocoder.yudao.framework.extension.core.package-info.java
  7 + */
  8 +package cn.iocoder.yudao.framework.extension.core;
0 9 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/point/ExtensionPoint.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.point;
  2 +/**
  3 + * @description 扩展点 <br/>
  4 + * 表示一块逻辑在不同的业务有不同的实现,使用扩展点做接口申明,然后用{@linkplain cn.iocoder.yudao.framework.extension.core.stereotype.Extension}(扩展)去实现扩展点。
  5 + * @author Qingchen
  6 + * @version 1.0.0
  7 + * @date 2021-08-28 22:06
  8 + * @class cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint.java
  9 + */
  10 +public interface ExtensionPoint {
  11 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/core/stereotype/Extension.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.core.stereotype;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  4 +import org.springframework.stereotype.Component;
  5 +
  6 +import java.lang.annotation.*;
  7 +
  8 +/**
  9 + * @description 表示带注释的类是“扩展组件”
  10 + * @author Qingchen
  11 + * @version 1.0.0
  12 + * @date 2021-08-28 21:59
  13 + * @class cn.iocoder.yudao.framework.extension.core.stereotype.Extension.java
  14 + */
  15 +@Inherited
  16 +@Retention(RetentionPolicy.RUNTIME)
  17 +@Target({ElementType.TYPE})
  18 +@Component
  19 +public @interface Extension {
  20 +
  21 + /**
  22 + * 业务 <br/>
  23 + * 一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务
  24 + * @return
  25 + */
  26 + String businessId() default BusinessScenario.DEFAULT_BUSINESS_ID;
  27 +
  28 + /**
  29 + * 用例 <br/>
  30 + * 描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
  31 + * @return
  32 + */
  33 + String useCase() default BusinessScenario.DEFAULT_USECASE;
  34 +
  35 + /**
  36 + * 场景 <br/>
  37 + * 场景也被称为用例的实例(Instance),包括用例所有的可能情况(正常的和异常的)。比如对于"订单支付"这个用例,就有“支付宝支付”、“银行卡支付”、"微信支付"等多个场景
  38 + * @return
  39 + */
  40 + String scenario() default BusinessScenario.DEFAULT_SCENARIO;
  41 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/java/cn/iocoder/yudao/framework/extension/package-info.java 0 → 100644
  1 +/**
  2 + * @description 扩展点组件
  3 + * @author Qingchen
  4 + * @version 1.0.0
  5 + * @date 2021-08-28 14:35
  6 + * @class cn.iocoder.yudao.framework.extension.package-info.java
  7 + */
  8 +package cn.iocoder.yudao.framework.extension;
0 9 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.framework.extension.config.YudaoExtensionAutoConfiguration
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/Application.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +/**
  7 + * @description Application
  8 + * @author Qingchen
  9 + * @version 1.0.0
  10 + * @date 2021-08-30 10:32
  11 + * @class cn.iocoder.yudao.framework.extension.Application.java
  12 + */
  13 +@SpringBootApplication
  14 +public class Application {
  15 +
  16 + public static void main(String[] args) {
  17 + SpringApplication.run(Application.class, args);
  18 + }
  19 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/ExtensionTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.extension.core.BusinessScenario;
  6 +import cn.iocoder.yudao.framework.extension.core.context.ExtensionExecutor;
  7 +import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
  8 +import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
  9 +import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.junit.Assert;
  12 +import org.junit.Test;
  13 +import org.junit.runner.RunWith;
  14 +import org.springframework.beans.factory.annotation.Autowired;
  15 +import org.springframework.boot.test.context.SpringBootTest;
  16 +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
  17 +
  18 +import java.math.BigDecimal;
  19 +
  20 +/**
  21 + * @description
  22 + * @author Qingchen
  23 + * @version 1.0.0
  24 + * @date 2021-08-30 10:30
  25 + * @class cn.iocoder.yudao.framework.extension.ExtensionTest.java
  26 + */
  27 +@RunWith(SpringJUnit4ClassRunner.class)
  28 +@SpringBootTest(classes = Application.class)
  29 +@Slf4j
  30 +public class ExtensionTest {
  31 +
  32 + @Autowired
  33 + private ExtensionExecutor extensionExecutor;
  34 +
  35 + @Test
  36 + public void unifiedOrder() {
  37 + final BusinessScenario scenario = BusinessScenario.valueOf("pay", "jsapi", "wechat");
  38 + final TransactionsCommand command = new TransactionsCommand(IdUtil.objectId(), new BigDecimal(105), "Image形象店-深圳腾大-QQ公仔", "https://www.weixin.qq.com/wxpay/pay.php");
  39 + final TransactionsResult result = extensionExecutor.execute(PayExtensionPoint.class, scenario, extension -> extension.unifiedOrder(command));
  40 + log.info("result is: {}", JSONUtil.toJsonStr(result));
  41 + Assert.assertSame("wechat", result.getChannel());
  42 + }
  43 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/package-info.java 0 → 100644
  1 +/**
  2 + * @description
  3 + * @author Qingchen
  4 + * @version 1.0.0
  5 + * @date 2021-08-30 10:25
  6 + * @class cn.iocoder.yudao.framework.extension.package-info.java
  7 + */
  8 +package cn.iocoder.yudao.framework.extension;
0 9 \ No newline at end of file
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/PayExtensionPoint.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.pay;
  2 +
  3 +import cn.iocoder.yudao.framework.extension.core.point.ExtensionPoint;
  4 +import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
  5 +import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
  6 +
  7 +/**
  8 + * @description 支付操作接口
  9 + * @author Qingchen
  10 + * @version 1.0.0
  11 + * @date 2021-08-30 10:35
  12 + * @class cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint.java
  13 + */
  14 +public interface PayExtensionPoint extends ExtensionPoint {
  15 +
  16 + /**
  17 + * 统一下单:获取"预支付交易会话标识"
  18 + * @param command
  19 + * @return
  20 + */
  21 + TransactionsResult unifiedOrder(TransactionsCommand command);
  22 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/command/TransactionsCommand.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.pay.command;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +import java.io.Serializable;
  8 +import java.math.BigDecimal;
  9 +
  10 +/**
  11 + * @description 下单请求
  12 + * @author Qingchen
  13 + * @version 1.0.0
  14 + * @date 2021-08-30 10:48
  15 + * @class cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand.java
  16 + */
  17 +@Data
  18 +@NoArgsConstructor
  19 +@AllArgsConstructor
  20 +public class TransactionsCommand implements Serializable {
  21 + /**
  22 + * 订单编号
  23 + */
  24 + private String orderNo;
  25 +
  26 + /**
  27 + * 支付金额
  28 + */
  29 + private BigDecimal amount;
  30 +
  31 + /**
  32 + * 商品描述
  33 + */
  34 + private String productDescription;
  35 +
  36 + /**
  37 + * 通知地址
  38 + */
  39 + private String notifyUrl;
  40 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/domain/TransactionsResult.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.pay.domain;
  2 +
  3 +import lombok.*;
  4 +
  5 +import java.io.Serializable;
  6 +
  7 +/**
  8 + * @description 下单: 预支付交易单返回结果
  9 + * @author Qingchen
  10 + * @version 1.0.0
  11 + * @date 2021-08-30 10:43
  12 + * @class cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult.java
  13 + */
  14 +@Data
  15 +@AllArgsConstructor
  16 +public class TransactionsResult implements Serializable {
  17 +
  18 + /**
  19 + * 预支付交易会话标识
  20 + */
  21 + private String prepayId;
  22 +
  23 + /**
  24 + * 订单编号
  25 + */
  26 + private String orderNo;
  27 +
  28 + /**
  29 + * 系统内部支付单号
  30 + */
  31 + private String paymentNo;
  32 +
  33 + /**
  34 + * 支付渠道:微信 or 支付宝
  35 + */
  36 + private String channel;
  37 +
  38 +
  39 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/AlipayService.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.pay.impl;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
  6 +import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
  7 +import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
  8 +import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +/**
  12 + * @description 微信 JSAPI 支付
  13 + * @author Qingchen
  14 + * @version 1.0.0
  15 + * @date 2021-08-30 10:38
  16 + * @class cn.iocoder.yudao.framework.extension.pay.impl.AlipayService.java
  17 + */
  18 +@Extension(businessId = "pay", useCase = "jsapi", scenario = "alipay")
  19 +@Slf4j
  20 +public class AlipayService implements PayExtensionPoint {
  21 + @Override
  22 + public TransactionsResult unifiedOrder(TransactionsCommand command) {
  23 + log.info("微信 JSAPI 支付:{}", JSONUtil.toJsonStr(command));
  24 + return new TransactionsResult("alipay26112221580621e9b071c00d9e093b0000", command.getOrderNo(), IdUtil.objectId(), "alipay");
  25 + }
  26 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/src/test/java/cn/iocoder/yudao/framework/extension/pay/impl/WechatPayService.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.extension.pay.impl;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.extension.core.stereotype.Extension;
  6 +import cn.iocoder.yudao.framework.extension.pay.PayExtensionPoint;
  7 +import cn.iocoder.yudao.framework.extension.pay.command.TransactionsCommand;
  8 +import cn.iocoder.yudao.framework.extension.pay.domain.TransactionsResult;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +/**
  12 + * @description 微信 JSAPI 支付
  13 + * @author Qingchen
  14 + * @version 1.0.0
  15 + * @date 2021-08-30 10:37
  16 + * @class cn.iocoder.yudao.framework.extension.pay.impl.WechatPayService.java
  17 + */
  18 +@Extension(businessId = "pay", useCase = "jsapi", scenario = "wechat")
  19 +@Slf4j
  20 +public class WechatPayService implements PayExtensionPoint {
  21 + @Override
  22 + public TransactionsResult unifiedOrder(TransactionsCommand command) {
  23 + log.info("微信 JSAPI 支付:{}", JSONUtil.toJsonStr(command));
  24 + return new TransactionsResult("wx26112221580621e9b071c00d9e093b0000", command.getOrderNo(), IdUtil.objectId(), "wechat");
  25 + }
  26 +}
... ...
yudao-framework/yudao-spring-boot-starter-extension/《芋道 Spring Boot 扩展点组件》.md 0 → 100644
  1 +### 作用
  2 +
  3 +​ 为了解决同一个流程不同业务有不同处理逻辑而产生,减少代码中 if else 逻辑,降低代码的耦合性,通过统一的扩展形式来支撑业务的变化。
  4 +
  5 +### 原理
  6 +
  7 +​ https://blog.csdn.net/significantfrank/article/details/100074716
  8 +
  9 +
  10 +
  11 +### 使用介绍
  12 +
  13 +参考测试代码 `cn.iocoder.yudao.framework.extension.ExtensionTest`
  14 +
  15 +
  16 +
  17 +
  18 +
  19 +
... ...