修改的ops新版本,插件暂时有点问题
This commit is contained in:
parent
a1837f7917
commit
302ef3f90a
497
dependencies/.flattened-pom.xml
vendored
Normal file
497
dependencies/.flattened-pom.xml
vendored
Normal file
@ -0,0 +1,497 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>dependencies</artifactId>
|
||||
<version>2.0.0-jdk17</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>dependencies</name>
|
||||
<description>管理整个项目的依赖版本</description>
|
||||
<properties>
|
||||
<tika-core.version>2.9.2</tika-core.version>
|
||||
<podam.version>8.0.0.RELEASE</podam.version>
|
||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
|
||||
<mapstruct.version>1.6.2</mapstruct.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<sqlite.version>3.47.1.0</sqlite.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||
<commons-compress.version>1.27.1</commons-compress.version>
|
||||
<rocketmq-spring.version>2.3.1</rocketmq-spring.version>
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
|
||||
<redisson.version>3.36.0</redisson.version>
|
||||
<pf4j-spring.version>0.9.0</pf4j-spring.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<spring.boot.version>3.3.4</spring.boot.version>
|
||||
<springdoc.version>2.3.0</springdoc.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<commons-io.version>2.17.0</commons-io.version>
|
||||
<hutool-6.version>6.0.0-M16</hutool-6.version>
|
||||
<jsoup.version>1.18.1</jsoup.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<lombok.version>1.18.34</lombok.version>
|
||||
<skywalking.version>9.0.0</skywalking.version>
|
||||
<mockito-inline.version>5.2.0</mockito-inline.version>
|
||||
<velocity.version>2.4</velocity.version>
|
||||
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||
<anwena.version>2.0.8.3</anwena.version>
|
||||
<pf4j.version>3.12.1</pf4j.version>
|
||||
<logback.version>1.2.13</logback.version>
|
||||
<mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
|
||||
<hutool-5.version>5.8.32</hutool-5.version>
|
||||
<revision>2.0.0-jdk17</revision>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<postgresql.version>42.7.4</postgresql.version>
|
||||
<spring-boot-admin.version>3.3.3</spring-boot-admin.version>
|
||||
<oracle.version>23.5.0.24.07</oracle.version>
|
||||
<netty.version>4.1.113.Final</netty.version>
|
||||
<oshi-version>6.6.5</oshi-version>
|
||||
<jedis-mock.version>1.1.4</jedis-mock.version>
|
||||
<guava.version>33.3.1-jre</guava.version>
|
||||
<aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>
|
||||
<commons-net.version>3.11.1</commons-net.version>
|
||||
<jimureport.version>1.7.8</jimureport.version>
|
||||
<easyexcel.verion>4.0.3</easyexcel.verion>
|
||||
<druid.version>1.2.23</druid.version>
|
||||
<easy-trans.version>3.0.6</easy-trans.version>
|
||||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-biz-data-permission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-biz-ip</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-biz-tenant</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-excel</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-job</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mongo</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-monitor</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mq</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<version>${rocketmq-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mybatis</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-protection</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-redis</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.mouzt</groupId>
|
||||
<artifactId>bizlog-sdk</artifactId>
|
||||
<version>${bizlog-sdk.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>${mybatis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-generator</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
<version>${dynamic-datasource.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.yulichang</groupId>
|
||||
<artifactId>mybatis-plus-join-boot-starter</artifactId>
|
||||
<version>${mybatis-plus-join.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-anno</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>${redisson.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
<version>${lock4j.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-logback-1.x</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-opentracing</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-api</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-util</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-noop</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-server</artifactId>
|
||||
<version>${spring-boot-admin.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-server-cloud</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
<version>${spring-boot-admin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>${mockito-inline.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.fppt</groupId>
|
||||
<artifactId>jedis-mock</artifactId>
|
||||
<version>${jedis-mock.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>uk.co.jemos.podam</groupId>
|
||||
<artifactId>podam</artifactId>
|
||||
<version>${podam.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool-5.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${hutool-6.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>${easyexcel.verion}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>${commons-compress.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tika</groupId>
|
||||
<artifactId>tika-core</artifactId>
|
||||
<version>${tika-core.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<artifactId>velocity-engine-core</artifactId>
|
||||
<version>${velocity.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
<version>${transmittable-thread-local.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>${commons-net.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>${jsch.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>${aws-java-sdk-s3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
<artifactId>ip2region</artifactId>
|
||||
<version>${ip2region.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>${oshi-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j</artifactId>
|
||||
<version>${pf4j.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j-spring</artifactId>
|
||||
<version>${pf4j-spring.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.dameng</groupId>
|
||||
<artifactId>DmJdbcDriver18</artifactId>
|
||||
<version>${dm8.jdbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>${oracle.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.opengauss</groupId>
|
||||
<artifactId>opengauss-jdbc</artifactId>
|
||||
<version>${opengauss.jdbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.com.kingbase</groupId>
|
||||
<artifactId>kingbase8</artifactId>
|
||||
<version>${kingbase.jdbc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>${sqlite.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.gitee.anwena</groupId>
|
||||
<artifactId>mongo-plus-boot-starter</artifactId>
|
||||
<version>${anwena.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
40
dependencies/pom.xml
vendored
40
dependencies/pom.xml
vendored
@ -30,6 +30,11 @@
|
||||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
|
||||
<sqlite.version>3.47.1.0</sqlite.version>
|
||||
<oracle.version>23.5.0.24.07</oracle.version>
|
||||
<postgresql.version>42.7.4</postgresql.version>
|
||||
<!-- <anwena.version>2.1.0</anwena.version>-->
|
||||
<anwena.version>2.0.8.3</anwena.version>
|
||||
<!-- 消息队列 -->
|
||||
<rocketmq-spring.version>2.3.1</rocketmq-spring.version>
|
||||
<!-- 服务保障相关 -->
|
||||
@ -120,6 +125,11 @@
|
||||
<artifactId>spring-boot-starter-job</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mongo</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-monitor</artifactId>
|
||||
@ -172,12 +182,7 @@
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
|
||||
<!---->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.mouzt</groupId>
|
||||
<artifactId>bizlog-sdk</artifactId>
|
||||
@ -524,6 +529,18 @@
|
||||
<version>${dm8.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>${oracle.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.opengauss</groupId>
|
||||
<artifactId>opengauss-jdbc</artifactId>
|
||||
@ -535,6 +552,19 @@
|
||||
<artifactId>kingbase8</artifactId>
|
||||
<version>${kingbase.jdbc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>${sqlite.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.gitee.anwena</groupId>
|
||||
<artifactId>mongo-plus-boot-starter</artifactId>
|
||||
<version>${anwena.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--数据库驱动-->
|
||||
|
||||
</dependencies>
|
||||
|
@ -96,6 +96,5 @@ public class IpUtil {
|
||||
String bathPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
|
||||
// log.info("当前域名:[{}]", bathPath);
|
||||
return bathPath;
|
||||
// return "http://wuwenbin.me/";
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
<module>spring-boot-starter-test</module>
|
||||
<module>spring-boot-starter-web</module>
|
||||
<module>spring-boot-starter-websocket</module>
|
||||
<module>spring-boot-starter-mongo</module>
|
||||
<module>spring-boot-starter-plugin</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>framework</artifactId>
|
||||
|
40
framework/spring-boot-starter-mongo/pom.xml
Normal file
40
framework/spring-boot-starter-mongo/pom.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>framework</artifactId>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-boot-starter-mongo</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<description>spring-boot-starter-mongo</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.gitee.anwena</groupId>
|
||||
<artifactId>mongo-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.framework.mongo.core.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.mongodb.MongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.MongoTransactionManager;
|
||||
|
||||
/**
|
||||
* @author mianbin
|
||||
* @date 2023-08-08 9:39
|
||||
* @description
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class TransactionConfig {
|
||||
|
||||
@Bean
|
||||
public MongoTransactionManager transactionManager(MongoDatabaseFactory factory) {
|
||||
return new MongoTransactionManager(factory);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package cd.casic.framework.mongo.core.constant;
|
||||
|
||||
public enum ECompare {
|
||||
EQ,
|
||||
NE,
|
||||
LE,
|
||||
LT,
|
||||
GE,
|
||||
GT,
|
||||
BW,
|
||||
IN,
|
||||
NIN,
|
||||
ISNULL
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package cd.casic.framework.mongo.core.constant;
|
||||
|
||||
public enum EConditionType {
|
||||
AND,
|
||||
OR,
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package cd.casic.framework.mongo.core.constant;
|
||||
|
||||
public enum ESortType {
|
||||
ASC,
|
||||
DESC
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package cd.casic.framework.mongo.core.entity;
|
||||
|
||||
|
||||
import cd.casic.framework.mongo.core.constant.ECompare;
|
||||
import cd.casic.framework.mongo.core.constant.EConditionType;
|
||||
import cd.casic.framework.mongo.core.wrapper.ConditionWrapper;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class Condition {
|
||||
/**
|
||||
* 嵌套比较类型
|
||||
* */
|
||||
private EConditionType conditionType = EConditionType.AND;
|
||||
/**
|
||||
* 比较类型
|
||||
* */
|
||||
private ECompare type;
|
||||
|
||||
/**
|
||||
* 集合对应的列
|
||||
* */
|
||||
private String column;
|
||||
|
||||
/**
|
||||
* 比较值
|
||||
* */
|
||||
private Map<String,Object> args;
|
||||
|
||||
private ConditionWrapper conditionWrapper;
|
||||
|
||||
public Condition(ECompare type, String column, Map<String,Object> args){
|
||||
this.type = type;
|
||||
this.column = column;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Condition(ECompare type, String column){
|
||||
this.type = type;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.framework.mongo.core.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Page<T>{
|
||||
/**
|
||||
* 查询数据列表
|
||||
*/
|
||||
private List<T> records = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private long total;
|
||||
/**
|
||||
* 当前页
|
||||
*/
|
||||
private int pageNum = 1;
|
||||
/**
|
||||
* 每页大小,默认10
|
||||
*/
|
||||
private int pageSize = 10;
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package cd.casic.framework.mongo.core.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class SelectField {
|
||||
|
||||
private String column;
|
||||
|
||||
public SelectField(String column){
|
||||
this.column = column;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.framework.mongo.core.entity;
|
||||
|
||||
import cd.casic.framework.mongo.core.constant.ESortType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SortCondition {
|
||||
|
||||
/**
|
||||
* 排序类型
|
||||
*/
|
||||
private ESortType sortType = ESortType.ASC;
|
||||
|
||||
private String column;
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package cd.casic.framework.mongo.core.impl;
|
||||
|
||||
import cd.casic.framework.mongo.core.entity.Page;
|
||||
import cd.casic.framework.mongo.core.sdk.MongoService;
|
||||
import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper;
|
||||
import cd.casic.framework.mongo.utils.ClassUtils;
|
||||
import cd.casic.framework.mongo.utils.QueryBuildUtils;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public class MongoServiceImpl<T> implements MongoService<T> {
|
||||
|
||||
private final Class<T> targetClass = (Class<T>) ClassUtils.getClass(this);
|
||||
|
||||
@Autowired
|
||||
protected MongoTemplate mongoTemplate;
|
||||
|
||||
@Override
|
||||
public T getOne(LambdaQueryWrapper<T> queryWrapper) {
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
return mongoTemplate.findOne(query, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean save(T entity) {
|
||||
return Objects.nonNull(mongoTemplate.save(entity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveBatch(Collection<T> entityList) {
|
||||
mongoTemplate.insertAll(entityList);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeById(Serializable id) {
|
||||
|
||||
Criteria criteria = Criteria.where("_id").is(id);
|
||||
Query query = new Query(criteria);
|
||||
DeleteResult deleteResult = mongoTemplate.remove(query, targetClass);
|
||||
return deleteResult.getDeletedCount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long remove(LambdaQueryWrapper<T> queryWrapper) {
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
DeleteResult deleteResult = mongoTemplate.remove(query, targetClass);
|
||||
return deleteResult.getDeletedCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateById(T entity) {
|
||||
|
||||
Criteria criteria = Criteria.where("_id").is(ClassUtils.getId(entity));
|
||||
Query query = new Query(criteria);
|
||||
Update update = getUpdate(entity);
|
||||
UpdateResult updateResult = mongoTemplate.updateFirst(query,update, targetClass);
|
||||
return updateResult.getModifiedCount() > 0;
|
||||
}
|
||||
|
||||
private Update getUpdate(T entity){
|
||||
|
||||
Update update = new Update();
|
||||
for (Field field : entity.getClass().getDeclaredFields()){
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
Object result = field.get(entity);
|
||||
if (Objects.nonNull(result)){
|
||||
update.set(field.getName(), result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long update(T entity, LambdaQueryWrapper<T> queryWrapper) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
Update update = getUpdate(entity);
|
||||
UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass);
|
||||
return updateResult.getModifiedCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long update(Update update, LambdaQueryWrapper<T> queryWrapper) {
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
UpdateResult updateResult = mongoTemplate.updateFirst(query,update,targetClass);
|
||||
return updateResult.getModifiedCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean upsert(T entity, LambdaQueryWrapper<T> queryWrapper) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
Update update = getUpdate(entity);
|
||||
UpdateResult updateResult = mongoTemplate.upsert(query,update,targetClass);
|
||||
return updateResult.getModifiedCount() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getById(Serializable id) {
|
||||
|
||||
Criteria criteria = Criteria.where("_id").is(id);
|
||||
Query query = new Query(criteria);
|
||||
return mongoTemplate.findOne(query, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(LambdaQueryWrapper<T> queryWrapper) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
return mongoTemplate.count(query, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> list(LambdaQueryWrapper<T> queryWrapper) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
return mongoTemplate.find(query, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page page(LambdaQueryWrapper<T> queryWrapper, int pageNum, int pageSize) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
Page<T> page = new Page<>();
|
||||
page.setPageNum(pageNum);
|
||||
page.setPageSize(pageSize);
|
||||
long total = mongoTemplate.count(query, targetClass);
|
||||
page.setTotal(total);
|
||||
if (total <= 0){
|
||||
return page;
|
||||
}
|
||||
query.skip((long) (pageNum - 1) * pageSize).limit(pageSize);
|
||||
List<T> list = mongoTemplate.find(query, targetClass);
|
||||
page.setRecords(list);
|
||||
return page;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exist(LambdaQueryWrapper<T> queryWrapper) {
|
||||
|
||||
Query query = QueryBuildUtils.buildQuery(queryWrapper);
|
||||
return mongoTemplate.exists(query, targetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> listByIds(Collection<? extends Serializable> idList) {
|
||||
|
||||
Criteria criteria = Criteria.where("_id").in(idList);
|
||||
Query query = new Query(criteria);
|
||||
return mongoTemplate.find(query, targetClass);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package cd.casic.framework.mongo.core.sdk;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Compare<LambdaQueryWrapper, R> extends Serializable {
|
||||
|
||||
default LambdaQueryWrapper eq(R column, Object value){
|
||||
return eq(true, column, value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper ne(R column, Object value){
|
||||
return ne(true,column,value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper le(R column, Object value){
|
||||
return le(true,column,value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper lt(R column, Object value){
|
||||
return lt(true,column,value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper ge(R column, Object value){
|
||||
return ge(true,column,value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper gt(R column, Object value){
|
||||
return gt(true,column,value);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper between(R column, Object leftValue, Object rightValue){
|
||||
return between(true,column,leftValue,rightValue);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper in(R column, Collection<?> values){
|
||||
return in(true,column,values);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper notIn(R column, Collection<?> values){
|
||||
return notIn(true,column,values);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper isNull(R column){
|
||||
return isNull(true, column);
|
||||
}
|
||||
|
||||
LambdaQueryWrapper eq(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper ne(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper le(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper lt(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper ge(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper gt(Boolean condition,R column, Object value);
|
||||
|
||||
LambdaQueryWrapper between(Boolean condition,R column, Object leftValue, Object rightValue);
|
||||
|
||||
LambdaQueryWrapper in(Boolean condition, R column, Collection<?> values);
|
||||
|
||||
LambdaQueryWrapper notIn(Boolean condition,R column, Collection<?> values);
|
||||
|
||||
LambdaQueryWrapper isNull(Boolean condition, R column);
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.framework.mongo.core.sdk;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface Function<T, LambdaQueryWrapper,R extends SFunction<T, ?>> extends Serializable {
|
||||
|
||||
default LambdaQueryWrapper orderByAsc(R column){
|
||||
return orderByAsc(true,column);
|
||||
}
|
||||
|
||||
default LambdaQueryWrapper orderByDesc(R column){
|
||||
return orderByDesc(true,column);
|
||||
}
|
||||
|
||||
LambdaQueryWrapper orderByAsc(boolean condition, R column);
|
||||
|
||||
LambdaQueryWrapper orderByDesc(boolean condition, R column);
|
||||
|
||||
LambdaQueryWrapper skip(Long skip);
|
||||
|
||||
LambdaQueryWrapper limit(Integer limit);
|
||||
|
||||
LambdaQueryWrapper select(R... columns);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package cd.casic.framework.mongo.core.sdk;
|
||||
|
||||
import cd.casic.framework.mongo.core.entity.Page;
|
||||
import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface MongoService<T> {
|
||||
|
||||
T getOne(LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
boolean save(T entity);
|
||||
|
||||
boolean saveBatch(Collection<T> entityList);
|
||||
|
||||
boolean removeById(Serializable id);
|
||||
|
||||
long remove(LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
boolean updateById(T entity);
|
||||
|
||||
long update(T entity, LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
long update(Update update, LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
boolean upsert(T entity, LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
T getById(Serializable id);
|
||||
|
||||
Collection<T> listByIds(Collection<? extends Serializable> idList);
|
||||
|
||||
long count(LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
List<T> list(LambdaQueryWrapper<T> queryWrapper);
|
||||
|
||||
Page<T> page(LambdaQueryWrapper<T> queryWrapper, int pageNum, int pageSize);
|
||||
|
||||
boolean exist(LambdaQueryWrapper<T> queryWrapper);
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package cd.casic.framework.mongo.core.sdk;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface Nested<Param, LambdaQueryWrapper> extends Serializable {
|
||||
|
||||
default LambdaQueryWrapper and(Function<Param,Param> function){
|
||||
return and(true,function);
|
||||
}
|
||||
|
||||
LambdaQueryWrapper or();
|
||||
|
||||
LambdaQueryWrapper and(boolean condition, Function<Param,Param> function);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cd.casic.framework.mongo.core.sdk;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SFunction <T, R> extends Serializable {
|
||||
|
||||
R apply(T t);
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.framework.mongo.core.wrapper;
|
||||
|
||||
import cd.casic.framework.mongo.core.entity.Condition;
|
||||
import cd.casic.framework.mongo.core.entity.SelectField;
|
||||
import cd.casic.framework.mongo.core.entity.SortCondition;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ConditionWrapper {
|
||||
private List<SelectField> fields = Collections.emptyList();
|
||||
|
||||
private List<Condition> conditions = Collections.emptyList();
|
||||
|
||||
private List<SortCondition> sortConditions = Collections.emptyList();
|
||||
|
||||
private Long skip;
|
||||
|
||||
private Integer limit;
|
||||
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
package cd.casic.framework.mongo.core.wrapper;
|
||||
|
||||
import cd.casic.framework.mongo.core.constant.ECompare;
|
||||
import cd.casic.framework.mongo.core.constant.EConditionType;
|
||||
import cd.casic.framework.mongo.core.constant.ESortType;
|
||||
import cd.casic.framework.mongo.core.entity.Condition;
|
||||
import cd.casic.framework.mongo.core.entity.SelectField;
|
||||
import cd.casic.framework.mongo.core.entity.SortCondition;
|
||||
import cd.casic.framework.mongo.core.sdk.Compare;
|
||||
import cd.casic.framework.mongo.core.sdk.Function;
|
||||
import cd.casic.framework.mongo.core.sdk.Nested;
|
||||
import cd.casic.framework.mongo.core.sdk.SFunction;
|
||||
import cd.casic.framework.mongo.utils.ConvertUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LambdaQueryWrapper<T>
|
||||
implements
|
||||
Compare<LambdaQueryWrapper<T>, SFunction<T, ?>>,
|
||||
Function<T, LambdaQueryWrapper<T>, SFunction<T, ?>>,
|
||||
Nested<LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> {
|
||||
|
||||
private final List<SelectField> fields = new ArrayList<>(5);
|
||||
|
||||
private final List<Condition> conditions = new ArrayList<>(5);
|
||||
|
||||
private final List<SortCondition> sortConditions = new ArrayList<>(5);
|
||||
|
||||
private Long skip;
|
||||
|
||||
private Integer limit;
|
||||
|
||||
public ConditionWrapper getCondition(){
|
||||
return new ConditionWrapper(fields,conditions,sortConditions,skip,limit);
|
||||
}
|
||||
|
||||
private String getFieldMeta(SFunction<T, ?> column){
|
||||
return ConvertUtils.convertToFieldName(column);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> eq(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("eq",value);
|
||||
conditions.add(new Condition(ECompare.EQ, getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> ne(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("ne",value);
|
||||
conditions.add(new Condition(ECompare.NE,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> le(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("le",value);
|
||||
conditions.add(new Condition(ECompare.LE,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> lt(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("lt",value);
|
||||
conditions.add(new Condition(ECompare.LT,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> ge(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("ge",value);
|
||||
conditions.add(new Condition(ECompare.GE,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> gt(Boolean condition, SFunction<T, ?> column, Object value) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("gt",value);
|
||||
conditions.add(new Condition(ECompare.GT,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> between(Boolean condition, SFunction<T, ?> column, Object leftValue, Object rightValue) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("bw1",leftValue);
|
||||
map.put("bw2",rightValue);
|
||||
conditions.add(new Condition(ECompare.BW,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> in(Boolean condition, SFunction<T, ?> column, Collection<?> values) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("in",values);
|
||||
conditions.add(new Condition(ECompare.IN,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> notIn(Boolean condition, SFunction<T, ?> column, Collection<?> values) {
|
||||
if(condition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("notIn", values);
|
||||
conditions.add(new Condition(ECompare.NIN,getFieldMeta(column), map));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> isNull(Boolean condition, SFunction<T, ?> column) {
|
||||
if (condition){
|
||||
conditions.add(new Condition(ECompare.ISNULL,getFieldMeta(column)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> or() {
|
||||
if (conditions.size() == 0){
|
||||
throw new RuntimeException("not first use or");
|
||||
}
|
||||
Condition lastCondition = conditions.get(conditions.size() - 1);
|
||||
ConditionWrapper conditionWrapper = lastCondition.getConditionWrapper();
|
||||
if (Objects.isNull(conditionWrapper)){
|
||||
conditionWrapper = new ConditionWrapper(fields,conditions,sortConditions,skip,limit);
|
||||
}
|
||||
List<Condition> sub = conditionWrapper.getConditions();
|
||||
Condition condition = sub.get(sub.size() - 1);
|
||||
condition.setConditionType(EConditionType.OR);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> and(boolean condition, java.util.function.Function<LambdaQueryWrapper<T>, LambdaQueryWrapper<T>> function) {
|
||||
if (condition){
|
||||
LambdaQueryWrapper<T> apply = function.apply(new LambdaQueryWrapper<>());
|
||||
if (this.conditions.size() == 0){
|
||||
throw new RuntimeException("not first use and");
|
||||
}
|
||||
ConditionWrapper conditionWrapper = apply.getCondition();
|
||||
Condition c = new Condition();
|
||||
c.setConditionWrapper(conditionWrapper);
|
||||
this.conditions.add(c);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> orderByAsc(boolean condition, SFunction<T, ?> column) {
|
||||
if (condition){
|
||||
sortConditions.add(new SortCondition(ESortType.ASC,getFieldMeta(column)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> orderByDesc(boolean condition, SFunction<T, ?> column) {
|
||||
if (condition){
|
||||
sortConditions.add(new SortCondition(ESortType.DESC,getFieldMeta(column)));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> skip(Long skip) {
|
||||
this.skip = skip;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LambdaQueryWrapper<T> limit(Integer limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@Override
|
||||
public final LambdaQueryWrapper<T> select(SFunction<T, ?>... columns) {
|
||||
if (Objects.nonNull(columns) && columns.length > 0){
|
||||
List<SelectField> fields = Arrays.stream(columns).map(column -> new SelectField(getFieldMeta(column))).collect(Collectors.toList());
|
||||
this.fields.addAll(fields);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package cd.casic.framework.mongo.core.wrapper;
|
||||
|
||||
public final class Wrappers {
|
||||
|
||||
public static <T> LambdaQueryWrapper<T> lambdaQuery(){
|
||||
return new LambdaQueryWrapper<>();
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
package cd.casic.framework.mongo.utils;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ClassUtils {
|
||||
|
||||
private ClassUtils(){
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存目标类型上的泛型值
|
||||
*/
|
||||
private static final Map<Class<?>, Class<?>> CACHE = new ConcurrentHashMap<>(64);
|
||||
|
||||
/**
|
||||
* 缓存mongo实体的主键字段
|
||||
*/
|
||||
private static final Map<Class<?>, Field> FIELD_CACHE = new ConcurrentHashMap<>(64);
|
||||
|
||||
|
||||
/**
|
||||
* 获取集合实体的主键值
|
||||
*/
|
||||
public static Serializable getId(Object object){
|
||||
|
||||
Field field = getIdField(object);
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
return (Serializable) field.get(object);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("not exist id value");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取集合实体的主键字段
|
||||
*/
|
||||
public static Field getIdField(Object object){
|
||||
|
||||
Class<?> clazz = object.getClass();
|
||||
Field result = FIELD_CACHE.get(clazz);
|
||||
if (Objects.nonNull(result)){
|
||||
return result;
|
||||
}
|
||||
for (Field field : clazz.getDeclaredFields()){
|
||||
Id annotation = field.getAnnotation(Id.class);
|
||||
if (Objects.nonNull(annotation)){
|
||||
FIELD_CACHE.put(clazz,field);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("no exist id");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象上的泛型
|
||||
*/
|
||||
public static Class<?> getClass(Object object){
|
||||
|
||||
if (Objects.isNull(object)){
|
||||
return null;
|
||||
}
|
||||
Class<?> clazz = object.getClass();
|
||||
Class<?> result = CACHE.get(clazz);
|
||||
if (Objects.nonNull(result)){
|
||||
return result;
|
||||
}
|
||||
Type aClass = object.getClass().getGenericSuperclass();
|
||||
Type subType = ((ParameterizedType) aClass).getActualTypeArguments()[0];
|
||||
result = (Class<?>) subType;
|
||||
CACHE.put(clazz,result);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package cd.casic.framework.mongo.utils;
|
||||
|
||||
import cd.casic.framework.mongo.core.sdk.SFunction;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ConvertUtils {
|
||||
|
||||
public static final String GET = "get";
|
||||
|
||||
public static final String IS = "is";
|
||||
|
||||
private static final Map<Class<?>,String> CLASS_FIELD_META_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
private ConvertUtils(){
|
||||
}
|
||||
|
||||
public static <T> String convertToFieldName(SFunction<T, ?> function){
|
||||
|
||||
SerializedLambda lambda = getSerializedLambda(function);
|
||||
String cacheDate = CLASS_FIELD_META_MAP.get(function.getClass());
|
||||
if (Objects.nonNull(cacheDate)){
|
||||
return cacheDate;
|
||||
}
|
||||
String methodName = lambda.getImplMethodName();
|
||||
if (methodName.startsWith(GET)){
|
||||
methodName = methodName.substring(3);
|
||||
}
|
||||
else if (methodName.startsWith(IS)){
|
||||
methodName = methodName.substring(2);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("无效的getter方法" + methodName);
|
||||
}
|
||||
try{
|
||||
String fieldMeta = StringUtils.firstToLowerCase(methodName);
|
||||
CLASS_FIELD_META_MAP.put(function.getClass(),fieldMeta);
|
||||
return fieldMeta;
|
||||
}catch (Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SerializedLambda getSerializedLambda(Serializable serializable){
|
||||
|
||||
SerializedLambda serializedLambda;
|
||||
try{
|
||||
Method method = serializable.getClass().getDeclaredMethod("writeReplace");
|
||||
method.setAccessible(Boolean.TRUE);
|
||||
serializedLambda = (SerializedLambda) method.invoke(serializable);
|
||||
}catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return serializedLambda;
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package cd.casic.framework.mongo.utils;
|
||||
|
||||
|
||||
import cd.casic.framework.mongo.core.constant.ECompare;
|
||||
import cd.casic.framework.mongo.core.constant.EConditionType;
|
||||
import cd.casic.framework.mongo.core.constant.ESortType;
|
||||
import cd.casic.framework.mongo.core.entity.Condition;
|
||||
import cd.casic.framework.mongo.core.entity.SortCondition;
|
||||
import cd.casic.framework.mongo.core.wrapper.ConditionWrapper;
|
||||
import cd.casic.framework.mongo.core.wrapper.LambdaQueryWrapper;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.mongodb.core.query.Field;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class QueryBuildUtils {
|
||||
|
||||
private static final Map<ECompare, Function<Condition, Criteria>> HANDLERS = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
HANDLERS.put(ECompare.EQ,QueryBuildUtils::eqHandle);
|
||||
HANDLERS.put(ECompare.NE,QueryBuildUtils::neHandle);
|
||||
HANDLERS.put(ECompare.LE,QueryBuildUtils::leHandle);
|
||||
HANDLERS.put(ECompare.LT,QueryBuildUtils::ltHandle);
|
||||
HANDLERS.put(ECompare.GE,QueryBuildUtils::geHandle);
|
||||
HANDLERS.put(ECompare.GT,QueryBuildUtils::gtHandle);
|
||||
HANDLERS.put(ECompare.BW,QueryBuildUtils::bwHandle);
|
||||
HANDLERS.put(ECompare.IN,QueryBuildUtils::inHandle);
|
||||
HANDLERS.put(ECompare.NIN,QueryBuildUtils::ninHandle);
|
||||
HANDLERS.put(ECompare.ISNULL,QueryBuildUtils::isNullHandle);
|
||||
}
|
||||
|
||||
public static Query buildQuery(LambdaQueryWrapper<?> queryWrapper){
|
||||
return buildQuery(queryWrapper.getCondition());
|
||||
}
|
||||
|
||||
public static Query buildQuery(ConditionWrapper wrapper){
|
||||
Criteria criteria = new Criteria();
|
||||
|
||||
//构建查询参数
|
||||
Map<String,Object> map = buildCondition(wrapper);
|
||||
Query query = new Query((CriteriaDefinition) map.get("criteria"));
|
||||
|
||||
//构建排序参数
|
||||
List<SortCondition> sortConditions = wrapper.getSortConditions();
|
||||
if (Objects.nonNull(sortConditions)){
|
||||
query.with(Sort.by(buildSort(sortConditions)));
|
||||
}
|
||||
|
||||
//构建分页参数
|
||||
if (Objects.nonNull(wrapper.getSkip())){
|
||||
query.skip(wrapper.getSkip());
|
||||
}
|
||||
if (Objects.nonNull(wrapper.getLimit())){
|
||||
query.limit(wrapper.getLimit());
|
||||
}
|
||||
|
||||
//构建查询列
|
||||
if(Objects.nonNull(wrapper.getFields()) && wrapper.getFields().size() > 0){
|
||||
Field fields = query.fields();
|
||||
wrapper.getFields().forEach(item ->fields.include(item.getColumn()));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
public static Map<String,Object> buildCondition(ConditionWrapper wrapper){
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Criteria criteria = new Criteria();
|
||||
if (Objects.isNull(wrapper) || Objects.isNull(wrapper.getConditions()) || wrapper.getConditions().size() ==0){
|
||||
map.put("criteria",criteria);
|
||||
map.put("critters",new Criteria[]{criteria});
|
||||
return map;
|
||||
}
|
||||
List<Condition> conditions = wrapper.getConditions();
|
||||
|
||||
boolean isOr = false;
|
||||
Criteria[] critters = new Criteria[conditions.size()];
|
||||
for (int i = 0; i < conditions.size(); i++) {
|
||||
Condition condition = conditions.get(i);
|
||||
if (Objects.nonNull(condition.getConditionWrapper()) && Objects.isNull(condition.getColumn()) && condition.getConditionWrapper().getConditions().size() > 0){
|
||||
Criteria curCriteria = new Criteria();
|
||||
Condition first = condition.getConditionWrapper().getConditions().get(0);
|
||||
Map<String,Object> map1 = buildCondition(condition.getConditionWrapper());
|
||||
if (first.getConditionType() == EConditionType.OR){
|
||||
curCriteria.orOperator((Criteria[]) map1.get("critters"));
|
||||
}else {
|
||||
curCriteria.andOperator((Criteria[]) map1.get("critters"));
|
||||
}
|
||||
critters[i] = curCriteria;
|
||||
continue;
|
||||
}
|
||||
ECompare type = condition.getType();
|
||||
if (condition.getConditionType() == EConditionType.OR){
|
||||
isOr = true;
|
||||
}
|
||||
Function<Condition, Criteria> handler = HANDLERS.get(type);
|
||||
if (Objects.isNull(handler)){
|
||||
throw new RuntimeException("buildQuery error not have queryType" + type);
|
||||
}
|
||||
Criteria curCriteria = handler.apply(condition);
|
||||
critters[i] = curCriteria;
|
||||
}
|
||||
if (isOr){
|
||||
criteria.orOperator(critters);
|
||||
}else {
|
||||
criteria.andOperator(critters);
|
||||
}
|
||||
map.put("criteria",criteria);
|
||||
map.put("critters",critters);
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<Sort.Order> buildSort(List<SortCondition> sortConditions){
|
||||
return sortConditions.stream().map(sortCondition -> {
|
||||
if (sortCondition.getSortType() == ESortType.ASC){
|
||||
return Sort.Order.asc(sortCondition.getColumn());
|
||||
}else {
|
||||
return Sort.Order.desc(sortCondition.getColumn());
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static Criteria eqHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).is(condition.getArgs().get("eq"));
|
||||
}
|
||||
|
||||
public static Criteria neHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).ne(condition.getArgs().get("ne"));
|
||||
}
|
||||
|
||||
public static Criteria leHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).lte(condition.getArgs().get("le"));
|
||||
}
|
||||
|
||||
public static Criteria ltHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).lt(condition.getArgs().get("lt"));
|
||||
}
|
||||
|
||||
public static Criteria geHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).gte(condition.getArgs().get("ge"));
|
||||
}
|
||||
|
||||
public static Criteria gtHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).gt(condition.getArgs().get("gt"));
|
||||
}
|
||||
|
||||
public static Criteria bwHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).gte(condition.getArgs().get("bw1")).lte(condition.getArgs().get("bw2"));
|
||||
}
|
||||
|
||||
public static Criteria inHandle(Condition condition){
|
||||
Map<String,Object> map = condition.getArgs();
|
||||
Collection<Object> in = (Collection<Object>) map.get("in");
|
||||
return Criteria.where(condition.getColumn()).in(in);
|
||||
}
|
||||
|
||||
public static Criteria ninHandle(Condition condition){
|
||||
Map<String,Object> map = condition.getArgs();
|
||||
List<Object> notIn = (List<Object>) map.get("notIn");
|
||||
return Criteria.where(condition.getColumn()).nin(notIn);
|
||||
}
|
||||
|
||||
public static Criteria isNullHandle(Condition condition){
|
||||
return Criteria.where(condition.getColumn()).isNull();
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package cd.casic.framework.mongo.utils;
|
||||
|
||||
public class StringUtils {
|
||||
|
||||
public static String firstToLowerCase(final String str){
|
||||
if (null == str || str.length() == 0){
|
||||
return "";
|
||||
}
|
||||
return str.substring(0,1).toLowerCase() + str.substring(1);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package cd.casic.framework.mongo.utils;
|
||||
|
||||
import cd.casic.framework.mongo.core.sdk.SFunction;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
/**
|
||||
* @author songqiang
|
||||
* @date 2023-08-01 11:21
|
||||
* @description 构造update
|
||||
*/
|
||||
public class UpdateBuilder<T> {
|
||||
|
||||
private Update update;
|
||||
|
||||
private UpdateBuilder() {
|
||||
|
||||
}
|
||||
|
||||
public static <T> UpdateBuilder<T> getInstance() {
|
||||
UpdateBuilder<T> builder = new UpdateBuilder<>();
|
||||
builder.setUpdate(new Update());
|
||||
return builder;
|
||||
}
|
||||
|
||||
public UpdateBuilder<T> set(SFunction<T, ?> column, Object value) {
|
||||
this.update.set(getFieldMeta(column), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public UpdateBuilder<T> incr(SFunction<T, ?> column, Number value) {
|
||||
this.update.inc(getFieldMeta(column), value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public UpdateBuilder<T> decr(SFunction<T, ?> column, Integer value) {
|
||||
this.update.inc(getFieldMeta(column), -value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setUpdate(Update update) {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
public Update build() {
|
||||
return update;
|
||||
}
|
||||
|
||||
private String getFieldMeta(SFunction<T, ?> column){
|
||||
return ConvertUtils.convertToFieldName(column);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
cd.casic.framework.mongo.core.config.TransactionConfig
|
65
framework/spring-boot-starter-plugin/pom.xml
Normal file
65
framework/spring-boot-starter-plugin/pom.xml
Normal file
@ -0,0 +1,65 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>framework</artifactId>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>spring-boot-starter-plugin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-biz-data-permission</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mongo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pf4j</groupId>
|
||||
<artifactId>pf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>cd.casic.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.pf4j.Plugin;
|
||||
import org.pf4j.PluginWrapper;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:BasePlugin
|
||||
* @Date:2024/03/18 9:25
|
||||
* @Filename:BasePlugin
|
||||
* @description:插件基类
|
||||
*/
|
||||
public abstract class BasePlugin extends Plugin {
|
||||
|
||||
public BasePlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据插件主类获取插件包
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String scanPackage() {
|
||||
return this.getClass().getPackage().getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class BufferedPluginBundleResource implements DisposableBean {
|
||||
|
||||
private final AtomicReference<FileSystemResource> jsBundle = new AtomicReference<>();
|
||||
private final AtomicReference<FileSystemResource> cssBundle = new AtomicReference<>();
|
||||
|
||||
private final ReadWriteLock jsLock = new ReentrantReadWriteLock();
|
||||
private final ReadWriteLock cssLock = new ReentrantReadWriteLock();
|
||||
|
||||
private Path tempDir;
|
||||
|
||||
public FileSystemResource getJsBundle(String version, List<DataBuffer> dataBuffers) {
|
||||
String fileName = tempFileName(version, ".js");
|
||||
jsLock.readLock().lock();
|
||||
try {
|
||||
FileSystemResource jsBundleResource = jsBundle.get();
|
||||
if (getResourceIfNotChange(fileName, jsBundleResource) != null) {
|
||||
return jsBundleResource;
|
||||
}
|
||||
} finally {
|
||||
jsLock.readLock().unlock();
|
||||
}
|
||||
|
||||
jsLock.writeLock().lock();
|
||||
try {
|
||||
FileSystemResource oldJsBundle = jsBundle.get();
|
||||
FileSystemResource newJsBundle = writeBundle(fileName, dataBuffers);
|
||||
jsBundle.compareAndSet(oldJsBundle, newJsBundle);
|
||||
return newJsBundle;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
jsLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public FileSystemResource getCssBundle(String version, List<DataBuffer> dataBuffers) {
|
||||
String fileName = tempFileName(version, ".css");
|
||||
try {
|
||||
cssLock.readLock().lock();
|
||||
FileSystemResource cssBundleResource = cssBundle.get();
|
||||
if (getResourceIfNotChange(fileName, cssBundleResource) != null) {
|
||||
return cssBundleResource;
|
||||
}
|
||||
} finally {
|
||||
cssLock.readLock().unlock();
|
||||
}
|
||||
cssLock.writeLock().lock();
|
||||
try {
|
||||
FileSystemResource oldCssBundle = cssBundle.get();
|
||||
FileSystemResource newCssBundle = writeBundle(fileName, dataBuffers);
|
||||
cssBundle.compareAndSet(oldCssBundle, newCssBundle);
|
||||
return newCssBundle;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
cssLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Resource getResourceIfNotChange(String fileName, Resource resource) {
|
||||
if (resource != null && resource.exists() && fileName.equals(resource.getFilename())) {
|
||||
return resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private FileSystemResource writeBundle(String fileName, List<DataBuffer> dataBuffers) throws IOException {
|
||||
Path filePath = createTempFileToStore(fileName);
|
||||
// 打开文件输出流
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath.toFile());
|
||||
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {
|
||||
|
||||
// 遍历 DataBuffer 列表并写入文件
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
// 将 DataBuffer 转换为字节数组
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
// 写入文件
|
||||
bufferedOutputStream.write(bytes);
|
||||
// 释放 DataBuffer
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
}
|
||||
}
|
||||
return new FileSystemResource(filePath);
|
||||
}
|
||||
|
||||
|
||||
private Path createTempFileToStore(String fileName) {
|
||||
try {
|
||||
if (tempDir == null || !Files.exists(tempDir)) {
|
||||
this.tempDir = Files.createTempDirectory("ops-plugin-bundle");
|
||||
}
|
||||
Path path = tempDir.resolve(fileName);
|
||||
log.info("Create temp file: {}", path);
|
||||
Files.deleteIfExists(path);
|
||||
return Files.createFile(path);
|
||||
} catch (IOException e) {
|
||||
throw new ServerWebInputException("Failed to create temp file.", null, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成临时文件名
|
||||
*
|
||||
* @param v
|
||||
* @param suffix
|
||||
* @return
|
||||
*/
|
||||
private String tempFileName(String v, String suffix) {
|
||||
Assert.notNull(v, "Version must not be null");
|
||||
Assert.notNull(suffix, "Suffix must not be null");
|
||||
return v + suffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (tempDir != null && Files.exists(tempDir)) {
|
||||
FileSystemUtils.deleteRecursively(tempDir);
|
||||
}
|
||||
this.jsBundle.set(null);
|
||||
this.cssBundle.set(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.utils.FrontResourceUtils;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 前端资源处理器,将各个插件的css和js资源合并
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class FrontResourceProcessor {
|
||||
|
||||
@Autowired
|
||||
private PluginManager pluginManager;
|
||||
|
||||
public List<DataBuffer> uglifyJsBundle() {
|
||||
List<DataBuffer> dataBuffers = new ArrayList<>();
|
||||
List<PluginWrapper> startedPlugins = pluginManager.getStartedPlugins();
|
||||
String plugins = String.format("this.enabledPluginNames = [%s];",
|
||||
startedPlugins.stream()
|
||||
.map(PluginWrapper::getPluginId)
|
||||
.collect(Collectors.joining("','", "'", "'")));
|
||||
|
||||
for (PluginWrapper pluginWrapper : startedPlugins) {
|
||||
String pluginName = pluginWrapper.getPluginId();
|
||||
Resource jsBundleResource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName,
|
||||
FrontResourceUtils.JS_BUNDLE);
|
||||
|
||||
if (jsBundleResource != null) {
|
||||
try (InputStream inputStream = jsBundleResource.getInputStream()) {
|
||||
byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
DataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
|
||||
dataBuffers.add(dataBuffer);
|
||||
// Add a new line after each plugin bundle to avoid syntax error
|
||||
byte[] newLineBytes = "\n".getBytes(StandardCharsets.UTF_8);
|
||||
dataBuffers.add(DefaultDataBufferFactory.sharedInstance.wrap(newLineBytes));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read plugin bundle resource", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the plugins JavaScript object at the end
|
||||
byte[] pluginsBytes = plugins.getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer pluginsDataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(pluginsBytes);
|
||||
dataBuffers.add(pluginsDataBuffer);
|
||||
|
||||
|
||||
return dataBuffers;
|
||||
}
|
||||
|
||||
public List<DataBuffer> uglifyCssBundle() {
|
||||
List<DataBuffer> cssDataBuffers = new ArrayList<>();
|
||||
for (PluginWrapper pluginWrapper : pluginManager.getStartedPlugins()) {
|
||||
String pluginName = pluginWrapper.getPluginId();
|
||||
Resource resource = FrontResourceUtils.getJsBundleResource(pluginManager, pluginName, FrontResourceUtils.CSS_BUNDLE);
|
||||
|
||||
if (resource != null) {
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
byte[] cssBytes = IoUtil.readBytes(inputStream); // Assuming you have Apache Commons IO for this
|
||||
|
||||
DataBuffer dataBuffer = new DefaultDataBufferFactory().wrap(cssBytes);
|
||||
cssDataBuffers.add(dataBuffer);
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to read plugin css bundle resource for plugin: {}", pluginName, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cssDataBuffers;
|
||||
}
|
||||
|
||||
public String generateJsBundleVersion() {
|
||||
if (RuntimeMode.DEVELOPMENT.equals(pluginManager.getRuntimeMode())) {
|
||||
return String.valueOf(System.currentTimeMillis());
|
||||
}
|
||||
var compactVersion = pluginManager.getStartedPlugins()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(PluginWrapper::getPluginId))
|
||||
.map(pluginWrapper -> pluginWrapper.getPluginId() + ":"
|
||||
+ pluginWrapper.getDescriptor().getVersion()
|
||||
)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
return DigestUtil.sha256Hex(compactVersion);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.pf4j.RuntimeMode;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 插件系统属性接口,定义了插件系统的一些核心配置和操作
|
||||
*/
|
||||
public interface IPluginSystemProperties {
|
||||
|
||||
/**
|
||||
* 检查插件系统是否启用
|
||||
*
|
||||
* @return 如果插件系统启用则返回true,否则返回false
|
||||
*/
|
||||
Boolean enable();
|
||||
|
||||
/**
|
||||
* 获取REST API路径前缀
|
||||
*
|
||||
* @return REST API路径前缀字符串
|
||||
*/
|
||||
String restPathPrefix();
|
||||
|
||||
/**
|
||||
* 检查插件ID是否作为REST路径前缀启用
|
||||
*
|
||||
* @return 如果启用插件ID作为REST路径前缀则返回true,否则返回false
|
||||
*/
|
||||
Boolean enablePluginIdAsRestPrefix();
|
||||
|
||||
/**
|
||||
* 获取运行时模式
|
||||
*
|
||||
* @return 当前运行时模式
|
||||
*/
|
||||
RuntimeMode runtimeMode();
|
||||
|
||||
/**
|
||||
* 获取插件根目录路径
|
||||
*
|
||||
* @return 插件根目录路径字符串
|
||||
*/
|
||||
String pluginsRoot();
|
||||
|
||||
/**
|
||||
* 获取已启用插件的ID集合
|
||||
*
|
||||
* @return 包含已启用插件ID的集合
|
||||
*/
|
||||
Set<String> enabledPlugins();
|
||||
|
||||
/**
|
||||
* 获取已禁用插件的ID集合
|
||||
*
|
||||
* @return 包含已禁用插件ID的集合
|
||||
*/
|
||||
Set<String> disabledPlugins();
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginApplicationContextHolder
|
||||
* @Date:2024/03/18 10:04
|
||||
* @Filename:PluginApplicationContextHolder
|
||||
* @description:插件的上下文管理器
|
||||
*/
|
||||
public class PluginApplicationContextManager {
|
||||
|
||||
private final static Map<String, AnnotationConfigApplicationContext> pluginApplicationMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void addPluginApplicationContext(String pluginId, AnnotationConfigApplicationContext applicationContext) {
|
||||
pluginApplicationMap.put(pluginId, applicationContext);
|
||||
}
|
||||
|
||||
public static void removePluginApplicationContext(String pluginId) {
|
||||
pluginApplicationMap.remove(pluginId);
|
||||
}
|
||||
|
||||
public static AnnotationConfigApplicationContext getApplicationContext(String pluginId) {
|
||||
return pluginApplicationMap.get(pluginId);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginBean
|
||||
* @Date:2024/03/18 14:48
|
||||
* @Filename:PluginBean
|
||||
* @description:Ops 插件的bean
|
||||
*/
|
||||
@Data
|
||||
public class PluginBean implements Serializable {
|
||||
private static final long serialVersionUID = 7817277417501722377L;
|
||||
// @ApiModelProperty(value="插件ID",name="id")
|
||||
private Long id;
|
||||
|
||||
// @ApiModelProperty(value="插件名",name="name")
|
||||
private String name;
|
||||
|
||||
// @ApiModelProperty(value="插件路径",name="path")
|
||||
private String path;
|
||||
|
||||
// @ApiModelProperty(value="插件描述",name="desc")
|
||||
private String desc;
|
||||
|
||||
// @ApiModelProperty(value="插件版本",name="version")
|
||||
private String version;
|
||||
|
||||
// @ApiModelProperty(value="插件作者",name="author")
|
||||
private String author;
|
||||
|
||||
// @ApiModelProperty(value="创建时间",name="createTime")
|
||||
private Date createTime;
|
||||
|
||||
// @ApiModelProperty(value="更新时间",name="updateTime")
|
||||
private Date updateTime;
|
||||
|
||||
// @ApiModelProperty(value="插件状态",name="status")
|
||||
private Integer status = 0;
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginCache
|
||||
* @Date:2024/03/18 9:34
|
||||
* @Filename:PluginCache
|
||||
* @description:已安装插件的存储
|
||||
*/
|
||||
public class PluginCache {
|
||||
private static final Map<String, PluginInfo> pluginMaps = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public static void put(String pluginId, PluginInfo plugin) {
|
||||
pluginMaps.putIfAbsent(pluginId, plugin);
|
||||
}
|
||||
|
||||
public static PluginInfo getPlugin(String pluginId) {
|
||||
return pluginMaps.get(pluginId);
|
||||
}
|
||||
|
||||
public static void remove(String pluginId) {
|
||||
pluginMaps.remove(pluginId);
|
||||
}
|
||||
|
||||
public static Map<String, PluginInfo> getAllPlugin() {
|
||||
return pluginMaps;
|
||||
}
|
||||
|
||||
public static Boolean isExist(String pluginId) {
|
||||
return pluginMaps.containsKey(pluginId);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PluginClassLoaderCache {
|
||||
private static final Map<String, ClassLoader> loaderMap = Collections.synchronizedMap(new HashMap<>());
|
||||
public static void put(String pluginId, ClassLoader loader) {
|
||||
loaderMap.putIfAbsent(pluginId, loader);
|
||||
}
|
||||
|
||||
public static ClassLoader getPlugin(String pluginId) {
|
||||
return loaderMap.get(pluginId);
|
||||
}
|
||||
|
||||
public static void remove(String pluginId) {
|
||||
loaderMap.remove(pluginId);
|
||||
}
|
||||
|
||||
public static Map<String, ClassLoader> getAllPlugin() {
|
||||
return loaderMap;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Data
|
||||
public class PluginConfigWrapper {
|
||||
|
||||
/**
|
||||
* 插件中的配置文件名称
|
||||
*/
|
||||
private final String fileName;
|
||||
|
||||
/**
|
||||
* 配置文件实现类的Class定义
|
||||
*/
|
||||
private final Class<?> configClass;
|
||||
|
||||
|
||||
|
||||
public PluginConfigWrapper(String fileName, Class<?> configClass) {
|
||||
this.fileName = fileName;
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o){
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof PluginConfigWrapper)){
|
||||
return false;
|
||||
}
|
||||
PluginConfigWrapper that = (PluginConfigWrapper) o;
|
||||
return getFileName().equals(that.getFileName()) &&
|
||||
getConfigClass().equals(that.getConfigClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getFileName(), getConfigClass());
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PluginDescriptorCache {
|
||||
private static final Map<String, PluginDescriptorStorage> pluginMaps = Collections.synchronizedMap(new HashMap<>());
|
||||
public static void put(String pluginId, PluginDescriptorStorage pluginDescriptorStorage) {
|
||||
pluginMaps.putIfAbsent(pluginId, pluginDescriptorStorage);
|
||||
}
|
||||
|
||||
public static PluginDescriptorStorage getPlugin(String pluginId) {
|
||||
return pluginMaps.get(pluginId);
|
||||
}
|
||||
|
||||
public static void remove(String pluginId) {
|
||||
pluginMaps.remove(pluginId);
|
||||
}
|
||||
|
||||
public static Map<String, PluginDescriptorStorage> getAllPlugin() {
|
||||
return pluginMaps;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.pf4j.Plugin;
|
||||
import org.pf4j.PluginDependency;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PF4J要用到的DefaultDescriptorStorage里一堆protected方法,所以另外弄了个类
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@TableName("sys_plugins")
|
||||
@Accessors(chain = true)
|
||||
public class PluginDescriptorStorage {
|
||||
@TableId
|
||||
private String pluginId;
|
||||
private String pluginDescription;
|
||||
private String pluginClass = Plugin.class.getName();
|
||||
private String version;
|
||||
private String requires = "*"; // SemVer format
|
||||
private String provider;
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<PluginDependency> dependencies;
|
||||
private String license;
|
||||
private String mapperXmlDir;
|
||||
private String staticDir;
|
||||
|
||||
private Integer enable;
|
||||
|
||||
private String path;
|
||||
|
||||
@TableField(exist = false)
|
||||
private String configFileName;
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<String> configFileActive;
|
||||
|
||||
private String pluginDirPath;
|
||||
|
||||
@Getter
|
||||
public enum EnableStatus {
|
||||
/**
|
||||
* 启用
|
||||
*/
|
||||
ENABLE(1, "启用"),
|
||||
/**
|
||||
* 禁用
|
||||
*/
|
||||
DISABLE(0, "禁用");
|
||||
private final Integer code;
|
||||
private final String value;
|
||||
|
||||
EnableStatus(Integer code, String value) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.utils.PluginsUtils;
|
||||
import cn.hutool.setting.dialect.Props;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginDetail
|
||||
* @Date:2024/03/18 9:34
|
||||
* @Filename:PluginDetail
|
||||
* @description:插件信息类
|
||||
*/
|
||||
@Slf4j
|
||||
@Data
|
||||
public class PluginInfo {
|
||||
|
||||
// jar或zip的list集合
|
||||
private List<Class<?>> classList;
|
||||
private ApplicationContext mainApplicationContext;
|
||||
private Boolean applicationContextIsRefresh = false;
|
||||
private AnnotationConfigApplicationContext pluginApplicationContext;
|
||||
private PluginWrapper pluginWrapper;
|
||||
private List<Class<?>> adminGroupsClassList = new ArrayList<>();
|
||||
private List<String> websocketPaths = new ArrayList<>();
|
||||
private String pluginId;
|
||||
private String mapperXmlDir;
|
||||
private final BasePlugin basePlugin;
|
||||
private List<HandlerInterceptor> handlerInterceptorList = new ArrayList<>();
|
||||
private Set<String> staticClassPathLocations = new HashSet<>();
|
||||
private Set<String> staticFileLocations = new HashSet<>();
|
||||
private List<Class<?>> controllers = new ArrayList<>();
|
||||
private Set<Object> pluginConfigObjects = new HashSet<>();
|
||||
private Map<String, String> webSocketPathMap = new ConcurrentHashMap<>();
|
||||
// private ConcurrentHashMap<Class<?>, Object> beanCache = new ConcurrentHashMap<>();
|
||||
|
||||
// TODO 这个map用于替代前面的ClassList
|
||||
private Map<String, List<Class<?>>> classGroups = new ConcurrentHashMap<>();
|
||||
|
||||
public PluginInfo(PluginWrapper pluginWrapper, ApplicationContext applicationContext) {
|
||||
this.classList = new ArrayList<>();
|
||||
this.pluginWrapper = pluginWrapper;
|
||||
this.pluginId = pluginWrapper.getPluginId();
|
||||
this.pluginApplicationContext = getContext();
|
||||
this.mainApplicationContext = applicationContext;
|
||||
this.basePlugin = (BasePlugin) pluginWrapper.getPlugin();
|
||||
this.pluginApplicationContext.setParent(mainApplicationContext);
|
||||
Props setting = PluginsUtils.getSetting(pluginWrapper.getPluginId());
|
||||
if (!setting.isEmpty()) {
|
||||
this.mapperXmlDir = setting.getStr("mybatis.mapper.location", null);
|
||||
String locations = setting.getStr("static.locations", null);
|
||||
if (StringUtils.isNotBlank(locations)) {
|
||||
loadResources(locations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AnnotationConfigApplicationContext getContext() {
|
||||
AnnotationConfigApplicationContext pluginApplicationContext =
|
||||
PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId());
|
||||
if (pluginApplicationContext == null)
|
||||
pluginApplicationContext = new AnnotationConfigApplicationContext();
|
||||
pluginApplicationContext.setClassLoader(pluginWrapper.getPluginClassLoader());
|
||||
PluginApplicationContextManager.addPluginApplicationContext(pluginWrapper.getPluginId(), pluginApplicationContext);
|
||||
return PluginApplicationContextManager.getApplicationContext(pluginWrapper.getPluginId());
|
||||
}
|
||||
|
||||
private void loadResources(String locations) {
|
||||
String[] staticLocations = locations.split(",");
|
||||
for (String staticLocation : staticLocations) {
|
||||
if (staticLocation.contains("classpath:")) {
|
||||
staticLocation = staticLocation.replace("classpath:", "");
|
||||
if (StringUtils.isNotBlank(staticLocation) && staticLocation.startsWith("/")) {
|
||||
staticLocation = staticLocation.substring(1);
|
||||
}
|
||||
this.staticClassPathLocations.add(staticLocation);
|
||||
} else {
|
||||
this.staticFileLocations.add(staticLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getMapperXmlDir() {
|
||||
if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("classpath:")) {
|
||||
mapperXmlDir = mapperXmlDir.replace("classpath:", "");
|
||||
}
|
||||
if (StringUtils.isNotBlank(mapperXmlDir) && mapperXmlDir.startsWith("/")) {
|
||||
mapperXmlDir = mapperXmlDir.substring(1);
|
||||
}
|
||||
return mapperXmlDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理ApplicationContext
|
||||
*/
|
||||
public void clearApplicationContext() {
|
||||
Optional.of(this.getPluginId())
|
||||
.ifPresent(var -> PluginApplicationContextManager.removePluginApplicationContext(var.trim()));
|
||||
Optional.of(this.getPluginApplicationContext())
|
||||
.ifPresent(var -> {
|
||||
var.getDefaultListableBeanFactory().destroySingletons();
|
||||
var.close();
|
||||
});
|
||||
this.applicationContextIsRefresh = false;
|
||||
this.pluginApplicationContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param c class
|
||||
* @return java.util.List<java.lang.Class < ?>>
|
||||
* @description 获取插件内实现指定类的bean
|
||||
* @author dolphin
|
||||
*/
|
||||
public <T> T getPluginBean(Class<T> c) {
|
||||
try {
|
||||
return pluginApplicationContext.getBean(c);
|
||||
} catch (Exception e) {
|
||||
log.error("要删除的bean:{}不存在" , c.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void addController(Class<?> controller) {
|
||||
this.controllers.add(controller);
|
||||
}
|
||||
|
||||
public void addPluginConfigObject(Object config) {
|
||||
this.pluginConfigObjects.add(config);
|
||||
}
|
||||
|
||||
public void addGroupClass(String groupName, Class<?> clazz) {
|
||||
classGroups.computeIfAbsent(groupName, k -> new ArrayList<>()).add(clazz);
|
||||
}
|
||||
|
||||
public List<Class<?>> getGroupClass(String groupName) {
|
||||
List<Class<?>> result = new ArrayList<>();
|
||||
List<Class<?>> classes = classGroups.get(groupName);
|
||||
if (classes != null) {
|
||||
result.addAll(classes);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginLifecycle
|
||||
* @Date:2024/06/15 17:31
|
||||
* @Filename:PluginLifecycle
|
||||
* @description:插件生命周期的操作
|
||||
*/
|
||||
public interface PluginLifecycle {
|
||||
|
||||
/**
|
||||
* 插件启动前的操作,比如需要单独对插件进行某些特定操作,
|
||||
*
|
||||
*/
|
||||
public void beforeWork();
|
||||
|
||||
|
||||
/**
|
||||
* 料理后世方的操作
|
||||
*/
|
||||
public void AfterWork();
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,392 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.event.PluginLoadedEvent;
|
||||
import cd.casic.plugin.event.PluginUnloadedEvent;
|
||||
import cd.casic.plugin.pf4j.OpsJarPluginRepository;
|
||||
import cd.casic.plugin.pf4j.OpsPluginStatusProvider;
|
||||
import cd.casic.plugin.pf4j.OpsPropertiesPluginDescriptorFinder;
|
||||
import cd.casic.plugin.pf4j.OpsYamlPluginDescriptorFinder;
|
||||
import cd.casic.plugin.register.StartPluginManagerHandler;
|
||||
import cd.casic.plugin.utils.PluginsUtils;
|
||||
import cd.casic.plugin.utils.SM4EncryptUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.pf4j.*;
|
||||
import org.pf4j.util.FileUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginManager
|
||||
* @Date:2024/03/18 15:03
|
||||
* @Filename:PluginManager
|
||||
* @description:插件核心管理类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PluginManager extends DefaultPluginManager implements PluginManagerService, ApplicationContextAware, PluginLifecycle {
|
||||
|
||||
@Getter
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@Getter
|
||||
private final PluginProperties pluginProperties;
|
||||
|
||||
@Autowired
|
||||
private StartPluginManagerHandler startPluginManagerHandler;
|
||||
|
||||
@Autowired
|
||||
private BufferedPluginBundleResource bufferedPluginBundleResource;
|
||||
|
||||
@Autowired
|
||||
private FrontResourceProcessor frontResourceProcessor;
|
||||
|
||||
public PluginManager(@Autowired PluginProperties pluginProperties) {
|
||||
this.pluginsRoots.add(Paths.get(pluginProperties.getPluginsRoot()));
|
||||
this.pluginProperties = pluginProperties;
|
||||
this.setRuntimeMode(pluginProperties.getRuntimeMode());
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
// 这里不需要任何操作,只是覆盖父类的initialize方法
|
||||
// 如果不重写的话,在调用构造函数的时候会调用父类的无参构造函数,导致父类initialize方法被调用。
|
||||
// 父类initialize方法中会调用createPluginStatusProvider方法,该方法需要使用到OpsPluginSystemProperties,而此时还未被注入
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PluginInfo install(Path path) throws Exception {
|
||||
|
||||
// TODO 在install的时候生成插件目录结构
|
||||
/*
|
||||
工作目录
|
||||
│
|
||||
├─plugins
|
||||
│ ├─example-mybatis-plugin@0.1 插件目录,id@version,存放jar、database、resource
|
||||
│ │ ├─ops-module-plugin-example-mybatis-plugin.jar 插件jar包,jar包命名不影响
|
||||
│ │ ├─database 存放sqlite数据库文件
|
||||
│ │ └─resource 存放静态资源
|
||||
│ └─example-redis-plugin@1.1.4
|
||||
│ ├─ops-module-plugin-example-redis-plugin.jar
|
||||
│ ├─database
|
||||
│ └─resource
|
||||
*/
|
||||
|
||||
String pluginId = null;
|
||||
try {
|
||||
this.beforeWork();
|
||||
pluginId = this.loadPlugin(path);
|
||||
super.startPlugin(pluginId);
|
||||
log.info("install plugin [{}] success", pluginId);
|
||||
return PluginCache.getPlugin(pluginId);
|
||||
} catch (Exception e) {
|
||||
log.error("安装插件出现异常:{}", e.getMessage());
|
||||
if (StringUtils.isNotBlank(pluginId)) {
|
||||
PluginCache.remove(pluginId);
|
||||
}
|
||||
throw new Exception(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installAfter(String pluginId) {
|
||||
try {
|
||||
super.stopPlugin(pluginId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("install plugin after error:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unInstall(String pluginId, boolean isUpdate) throws Exception {
|
||||
// TODO unInstall的时候除掉插件资源
|
||||
log.info("准备卸载插件:" + pluginId);
|
||||
PluginInfo plugin = PluginCache.getPlugin(pluginId);
|
||||
plugin.clearApplicationContext();
|
||||
PluginCache.remove(pluginId);
|
||||
//删除插件
|
||||
PluginsUtils.forceDelete(plugin.getPluginWrapper().getPluginPath().toFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPlugins(List<PluginBean> plugins) throws Exception {
|
||||
// 初始化插件处理器
|
||||
startPluginManagerHandler.initialize();
|
||||
for (PluginBean plugin : plugins) {
|
||||
File file = new File(pluginsRoots.get(0) + File.separator + plugin.getPath());
|
||||
if (!file.exists()) {
|
||||
continue;
|
||||
}
|
||||
String pluginId = this.loadPlugin(file.toPath().toAbsolutePath());
|
||||
PluginInfo pluginInfo = PluginCache.getPlugin(pluginId);
|
||||
if (plugin.getStatus() == 1) {
|
||||
this.startPlugin(pluginInfo.getPluginId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PluginWrapper> getInstallPlugins() {
|
||||
return getPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载插件
|
||||
*
|
||||
* @param pluginPath 插件路径
|
||||
* @return String
|
||||
*/
|
||||
@Override
|
||||
public String loadPlugin(Path pluginPath) {
|
||||
Optional.ofNullable(pluginPath)
|
||||
.filter(path -> Files.exists(path))
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("plugin %s 不存在", pluginPath)));
|
||||
|
||||
log.debug("Loading plugin from '{}'", pluginPath);
|
||||
|
||||
PluginWrapper pluginWrapper = loadPluginFromPath(pluginPath);
|
||||
PluginInfo pluginInfo = new PluginInfo(pluginWrapper, applicationContext);
|
||||
try {
|
||||
resolvePlugins();
|
||||
sendPluginEvenet(new PluginLoadedEvent(this, pluginInfo));
|
||||
} catch (Exception e) {
|
||||
log.error("加载插件出现异常:{}", e.getMessage());
|
||||
sendPluginEvenet(new PluginUnloadedEvent(this, pluginInfo));
|
||||
}
|
||||
return pluginWrapper.getDescriptor().getPluginId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginWrapper loadPluginFromPath(Path pluginPath) {
|
||||
try {
|
||||
pluginPath = FileUtils.expandIfZip(pluginPath);
|
||||
} catch (Exception var3) {
|
||||
log.info(MessageFormat.format("路径:{0}的插件包解压缩失败,失败原因{1} ", pluginPath, var3));
|
||||
return null;
|
||||
}
|
||||
return super.loadPluginFromPath(pluginPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlugins() {
|
||||
log.debug("Lookup plugins in '{}'", pluginsRoots);
|
||||
// check for plugins roots
|
||||
if (pluginsRoots.isEmpty()) {
|
||||
log.warn("No plugins roots configured");
|
||||
return;
|
||||
}
|
||||
pluginsRoots.forEach(path -> {
|
||||
if (Files.notExists(path) || !Files.isDirectory(path)) {
|
||||
log.warn("No '{}' root", path);
|
||||
}
|
||||
});
|
||||
|
||||
// get all plugin paths from repository
|
||||
List<Path> pluginPaths = pluginRepository.getPluginPaths();
|
||||
|
||||
// check for no plugins
|
||||
if (pluginPaths.isEmpty()) {
|
||||
log.info("No plugins");
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Found {} possible plugins: {}", pluginPaths.size(), pluginPaths);
|
||||
|
||||
// load plugins from plugin paths
|
||||
for (Path pluginPath : pluginPaths) {
|
||||
try {
|
||||
PluginWrapper pluginWrapper = this.loadPluginFromPath(pluginPath);
|
||||
if (ObjectUtils.isNotEmpty(pluginWrapper)) {
|
||||
String license = pluginWrapper.getDescriptor().getLicense();
|
||||
if (SM4EncryptUtil.checkLicense(pluginWrapper.getDescriptor().getLicense(), pluginWrapper.getPluginId())) {
|
||||
PluginInfo plugin = new PluginInfo(pluginWrapper, applicationContext);
|
||||
// startPluginManagerHandler.registry(plugin);
|
||||
sendPluginEvenet(new PluginLoadedEvent(this, plugin));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// resolve plugins
|
||||
try {
|
||||
resolvePlugins();
|
||||
} catch (PluginRuntimeException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unloadPlugin(String pluginId) {
|
||||
if (pluginId == null) {
|
||||
throw new IllegalArgumentException(String.format("plugin %s 不存在", pluginId));
|
||||
}
|
||||
log.info("unloading plugin {}", pluginId);
|
||||
try {
|
||||
super.unloadPlugin(pluginId, true);
|
||||
} catch (Exception e) {
|
||||
log.info("卸载插件出现异常,{}", e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
sendPluginEvenet(new PluginUnloadedEvent(this, PluginCache.getPlugin(pluginId)));
|
||||
System.gc();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void unloadPlugins() {
|
||||
for (PluginWrapper pluginWrapper : new ArrayList<>(resolvedPlugins)) {
|
||||
this.unloadPlugin(pluginWrapper.getPluginId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginState startPlugin(String pluginId) {
|
||||
PluginState pluginState = null;
|
||||
try {
|
||||
PluginInfo plugin = PluginCache.getPlugin(pluginId);
|
||||
if (plugin.getPluginWrapper().getPluginState().equals(PluginState.DISABLED)) return PluginState.STOPPED;
|
||||
|
||||
if (ObjectUtils.isEmpty((plugin.getPluginApplicationContext()))) {
|
||||
plugin.setPluginApplicationContext(plugin.getContext());
|
||||
plugin.getPluginApplicationContext().setParent(applicationContext);
|
||||
}
|
||||
|
||||
startPluginManagerHandler.registry(plugin);
|
||||
pluginState = super.startPlugin(pluginId);
|
||||
|
||||
// todo 这里有uglify存在循环遍历的问题,后面改
|
||||
bufferedPluginBundleResource.getJsBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyJsBundle());
|
||||
bufferedPluginBundleResource.getCssBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyCssBundle());
|
||||
// bufferedPluginBundleResource.getHtmlBundle(String.valueOf(System.currentTimeMillis()), frontResourceProcessor.uglifyHtmlBundle());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("plugin start error : {}", e.getMessage());
|
||||
}
|
||||
return pluginState;
|
||||
}
|
||||
|
||||
protected PluginState stopPlugin(String pluginId, boolean stopDependents) {
|
||||
checkPluginId(pluginId);
|
||||
|
||||
PluginWrapper pluginWrapper = getPlugin(pluginId);
|
||||
PluginDescriptor pluginDescriptor = pluginWrapper.getDescriptor();
|
||||
PluginState pluginState = pluginWrapper.getPluginState();
|
||||
if (PluginState.STOPPED == pluginState) {
|
||||
log.debug("Already stopped plugin '{}'", getPluginLabel(pluginDescriptor));
|
||||
return PluginState.STOPPED;
|
||||
}
|
||||
|
||||
// test for disabled plugin
|
||||
if (PluginState.DISABLED == pluginState) {
|
||||
// do nothing
|
||||
return pluginState;
|
||||
}
|
||||
|
||||
if (stopDependents) {
|
||||
List<String> dependents = dependencyResolver.getDependents(pluginId);
|
||||
while (!dependents.isEmpty()) {
|
||||
String dependent = dependents.remove(0);
|
||||
stopPlugin(dependent, false);
|
||||
dependents.addAll(0, dependencyResolver.getDependents(dependent));
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Stop plugin '{}'", getPluginLabel(pluginDescriptor));
|
||||
pluginWrapper.getPlugin().stop();
|
||||
pluginWrapper.setPluginState(PluginState.STOPPED);
|
||||
startedPlugins.remove(pluginWrapper);
|
||||
|
||||
try {
|
||||
// todo 没得到plugin
|
||||
PluginInfo plugin = PluginCache.getPlugin(pluginId);
|
||||
// todo 停止事件
|
||||
startPluginManagerHandler.unRegistry(plugin);
|
||||
plugin.clearApplicationContext();
|
||||
} catch (Exception e) {
|
||||
log.error("plugin stop error : {}", e.getMessage());
|
||||
}
|
||||
|
||||
firePluginStateEvent(new PluginStateEvent(this, pluginWrapper, pluginState));
|
||||
this.AfterWork();
|
||||
return pluginWrapper.getPluginState();
|
||||
}
|
||||
|
||||
public void setRuntimeMode(RuntimeMode runtimeMode) {
|
||||
this.runtimeMode = runtimeMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PluginDescriptorFinder createPluginDescriptorFinder() {
|
||||
return new CompoundPluginDescriptorFinder()
|
||||
.add(new OpsPropertiesPluginDescriptorFinder())
|
||||
.add(new OpsYamlPluginDescriptorFinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PluginRepository createPluginRepository() {
|
||||
// return new CompoundPluginRepository()
|
||||
// .add(new DevelopmentPluginRepository(getPluginsRoots()), this::isDevelopment)
|
||||
// .add(new OpsJarPluginRepository(getPluginsRoots()), this::isNotDevelopment);
|
||||
// .add(new DefaultPluginRepository(getPluginsRoots()), this::isNotDevelopment);
|
||||
return new OpsJarPluginRepository(getPluginsRoots());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PluginLoader createPluginLoader() {
|
||||
return new CompoundPluginLoader()
|
||||
.add(new JarPluginLoader(this))
|
||||
.add(new DefaultPluginLoader(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDevelopment() {
|
||||
return this.getRuntimeMode().equals(RuntimeMode.DEVELOPMENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PluginStatusProvider createPluginStatusProvider() {
|
||||
if (OpsPluginStatusProvider.isPropertySet(pluginProperties)) {
|
||||
return new OpsPluginStatusProvider(pluginProperties);
|
||||
}
|
||||
return super.createPluginStatusProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeWork() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void AfterWork() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.framework.commons.util.spring.SpringUtils;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin
|
||||
* @Project:ops
|
||||
* @name:PluginManagerService
|
||||
* @Date:2024/03/18 14:46
|
||||
* @Filename:PluginManagerService
|
||||
* @description:插件基础安装类
|
||||
*/
|
||||
public interface PluginManagerService {
|
||||
/**
|
||||
* @param path 插件路径
|
||||
* @description 安装插件
|
||||
*/
|
||||
PluginInfo install(Path path) throws Exception;
|
||||
|
||||
/**
|
||||
* 安装插件后置操作
|
||||
*
|
||||
* @param pluginId pluginId
|
||||
*/
|
||||
void installAfter(String pluginId);
|
||||
|
||||
/**
|
||||
* @description 卸载插件
|
||||
*/
|
||||
void unInstall(String pluginId, boolean isUpdate) throws Exception;
|
||||
|
||||
/**
|
||||
* 启动插件
|
||||
*
|
||||
* @param pluginId
|
||||
* @return
|
||||
*/
|
||||
PluginState startPlugin(String pluginId);
|
||||
|
||||
/**
|
||||
* 停止插件
|
||||
*
|
||||
* @param pluginId pluginId
|
||||
* @return PluginState
|
||||
*/
|
||||
PluginState stopPlugin(String pluginId);
|
||||
|
||||
/**
|
||||
* @description 插件初始化
|
||||
*/
|
||||
void initPlugins(List<PluginBean> plugins) throws Exception;
|
||||
|
||||
/**
|
||||
* @description 获取所有插件
|
||||
*/
|
||||
List<PluginWrapper> getInstallPlugins();
|
||||
|
||||
|
||||
default void sendPluginEvenet(ApplicationEvent applicationEvent) {
|
||||
SpringUtils.publishEvent(applicationEvent);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import lombok.Data;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "cd.casic.ops.plugin")
|
||||
public class PluginProperties implements InitializingBean {
|
||||
/**
|
||||
* 是否启用插件系统
|
||||
*/
|
||||
private Boolean enable;
|
||||
/**
|
||||
* 插件根目录
|
||||
*/
|
||||
private String pluginsRoot;
|
||||
/**
|
||||
* 运行模式,可以是 development 或者 deployment
|
||||
*/
|
||||
private RuntimeMode runtimeMode;
|
||||
/**
|
||||
* 插件接口RESTful API的路径前缀
|
||||
*/
|
||||
private String restPathPrefix;
|
||||
/**
|
||||
* 是否在RESTful API前缀中包含插件ID,默认为true
|
||||
*/
|
||||
private boolean enablePluginIdAsRestPrefix = true;
|
||||
/**
|
||||
* 已启用的插件集合
|
||||
*/
|
||||
private Set<String> enabledPlugins = new HashSet<>();
|
||||
/**
|
||||
* 已禁用的插件集合
|
||||
*/
|
||||
private Set<String> disabledPlugins = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Path pluginsRootPath = Paths.get(this.pluginsRoot);
|
||||
if (!pluginsRootPath.toFile().exists()) {
|
||||
FileUtil.mkdir(pluginsRootPath.toFile());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.utils.YamlUtils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.TreeTraversingParser;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class PluginYamlConfigurationParser {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final YAMLFactory yamlFactory;
|
||||
|
||||
public PluginYamlConfigurationParser(){
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.yamlFactory = new YAMLFactory();
|
||||
}
|
||||
|
||||
public Object parse(PluginInfo pluginInfo, PluginConfigWrapper pluginConfigWrapper) throws Exception {
|
||||
Class<?> configClass = pluginConfigWrapper.getConfigClass();
|
||||
if(configClass == null){
|
||||
throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " +
|
||||
"configClass can not be null");
|
||||
}
|
||||
String yamlFileName = pluginConfigWrapper.getFileName();
|
||||
if(StrUtil.isEmpty(yamlFileName)){
|
||||
throw new IllegalArgumentException("pluginConfigDefinition : " + pluginConfigWrapper + " " +
|
||||
"fileName can not be empty");
|
||||
}
|
||||
|
||||
Path yamlPath = YamlUtils.getYamlPath(pluginInfo.getPluginWrapper().getPluginPath(), yamlFileName);
|
||||
Resource resource = new FileSystemResource(yamlPath);
|
||||
Object o = convert(resource, configClass);
|
||||
if(o == null){
|
||||
return configClass.getConstructor().newInstance();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
private Object convert(Resource resource, Class<?> configClass) throws Exception {
|
||||
InputStream inputStream = null;
|
||||
YAMLParser yamlParser = null;
|
||||
TreeTraversingParser treeTraversingParser = null;
|
||||
try {
|
||||
inputStream = resource.getInputStream();
|
||||
yamlParser = yamlFactory.createParser(inputStream);
|
||||
final JsonNode node = objectMapper.readTree(yamlParser);
|
||||
if(node == null){
|
||||
return configClass.getConstructor().newInstance();
|
||||
}
|
||||
treeTraversingParser = new TreeTraversingParser(node);
|
||||
return objectMapper.readValue(treeTraversingParser, configClass);
|
||||
} finally {
|
||||
if(treeTraversingParser != null){
|
||||
treeTraversingParser.close();
|
||||
}
|
||||
if(yamlParser != null){
|
||||
yamlParser.close();
|
||||
}
|
||||
if(inputStream != null){
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package cd.casic.plugin;
|
||||
|
||||
import cd.casic.plugin.utils.PluginDescriptorUtils;
|
||||
import org.springframework.beans.factory.config.YamlProcessor;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static cd.casic.plugin.utils.PluginDescriptorUtils.*;
|
||||
|
||||
|
||||
public class PluginYamlProcessor extends YamlProcessor {
|
||||
/**
|
||||
* 检查这些必须有的属性是否存在
|
||||
*/
|
||||
private static final DocumentMatcher DEFAULT_DOCUMENT_MATCHER = properties -> {
|
||||
if (properties.containsKey(PLUGIN_ID)
|
||||
&& properties.containsKey(PLUGIN_VERSION)
|
||||
&& properties.containsKey(PLUGIN_CLASS)){
|
||||
return MatchStatus.FOUND;
|
||||
}
|
||||
return MatchStatus.NOT_FOUND;
|
||||
};
|
||||
|
||||
public PluginYamlProcessor(Resource... resources) {
|
||||
setResources(resources);
|
||||
setDocumentMatchers(DEFAULT_DOCUMENT_MATCHER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取yaml文件
|
||||
* @return PluginDescriptorStorage 插件的相关配置信息以及后续用于插件信息持久化存储的持久化对象
|
||||
*/
|
||||
public PluginDescriptorStorage loadYaml() {
|
||||
List<PluginDescriptorStorage> pluginDescriptor = new ArrayList<>();
|
||||
this.process(((properties, map) -> {
|
||||
pluginDescriptor.add(PluginDescriptorUtils.propertiesToStorage(properties));
|
||||
}));
|
||||
|
||||
return pluginDescriptor.get(0);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
|
||||
import cd.casic.plugin.constants.OpsConstants;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:AdminGroup
|
||||
* @Date:2024/03/18 17:06
|
||||
* @Filename:AdminGroup
|
||||
* @description:自定义菜单Group注解
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.ANNOTATION_TYPE})
|
||||
@Documented
|
||||
public @interface AdminGroup {
|
||||
/** 菜单名称 */
|
||||
String name();
|
||||
|
||||
/** 菜单组id */
|
||||
String groupId();
|
||||
|
||||
/** 菜单图标 */
|
||||
String icon() default "fa-circle-o";
|
||||
|
||||
/** 菜单url */
|
||||
String url() default "";
|
||||
|
||||
/** 菜单角色 */
|
||||
String[] role() default {OpsConstants.ROLE_ADMIN};
|
||||
|
||||
/** 菜单序号 */
|
||||
int seq() default 99;
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:AdminGroups
|
||||
* @Date:2024/03/18 17:05
|
||||
* @Filename:AdminGroups
|
||||
* @description:自定义菜单groups注解
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Documented
|
||||
public @interface AdminGroups {
|
||||
/** 菜单组 */
|
||||
AdminGroup[] groups();
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.annotation
|
||||
* @Project:ops
|
||||
* @name:InterceptPath
|
||||
* @Date:2024/03/18 16:09
|
||||
* @Filename:InterceptPath
|
||||
* @description:插件拦截器
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
public @interface InterceptPath {
|
||||
/**
|
||||
* 拦截的路径
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package cd.casic.plugin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 用于标记插件内部的独立配置类
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface PluginConfiguration {
|
||||
String fileName() default ""; // 插件配置文件的文件名
|
||||
|
||||
String deploySuffix() default ""; // 插件配置文件在deployment模式下的后缀
|
||||
|
||||
String devSuffix() default ""; // 插件配置文件在development模式下的后缀
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cd.casic.plugin.config;
|
||||
|
||||
import cd.casic.plugin.PluginManager;
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 配置类:用于定义和管理插件系统的Bean
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({PluginProperties.class})
|
||||
public class OpsPluginConfig {
|
||||
/**
|
||||
* 定义并创建一个PluginManager Bean
|
||||
* <p>
|
||||
* 此Bean负责管理和操作插件系统,仅在没有已定义的PluginManager Bean时创建
|
||||
*
|
||||
* @param pluginProperties 插件系统的属性配置,用于初始化PluginManager
|
||||
* @return 初始化并配置好的PluginManager实例
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PluginManager pluginManager(PluginProperties pluginProperties) {
|
||||
return new PluginManager(pluginProperties);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package cd.casic.plugin.constants;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.constants
|
||||
* @Project:ops
|
||||
* @name:OpsConstants
|
||||
* @Date:2024/03/18 9:30
|
||||
* @Filename:OpsConstants
|
||||
* @description:Todo
|
||||
*/
|
||||
public class OpsConstants {
|
||||
/**
|
||||
* 插件目录
|
||||
*/
|
||||
public static final String PLUGIN_PATH = "/plugins";
|
||||
/**
|
||||
* 插件路径
|
||||
*/
|
||||
public static final String PLUGINS_DIR = "resources/plugins";
|
||||
/**
|
||||
* 插件静态资源目录
|
||||
*/
|
||||
public static final String PLUGINS_RESOURCES_DIR = "resources/pluginResources";
|
||||
//public static final String PLUGINS_RESOURCES_DIR = "ops-module-plugins/ops-module-plugins-example-web/src/main/resources/static";
|
||||
|
||||
/**
|
||||
* 角色:管理员
|
||||
*/
|
||||
public static final String ROLE_ADMIN = "admin";
|
||||
/**
|
||||
* 角色:用户
|
||||
*/
|
||||
public static final String ROLE_USER = "user";
|
||||
/**
|
||||
* 角色:编辑
|
||||
*/
|
||||
public static final String ROLE_EDITOR = "editor";
|
||||
|
||||
|
||||
/**
|
||||
* todo 区分插件类型,目前只支持两种,一种为zip ,一种为jar包形式,主要使用zip形式,别面jar包被人轻易破解
|
||||
*/
|
||||
public static class Suffix {
|
||||
|
||||
private Suffix() {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public static final String JAR = "jar";
|
||||
public static final String ZIP = "zip";
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.plugin.constants;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.constants
|
||||
* @Project:ops
|
||||
* @name:PluginBuildTypeEnum
|
||||
* @Date:2024/03/21 19:53
|
||||
* @Filename:PluginBuildTypeEnum
|
||||
* @description:插件构建方式区分
|
||||
*/
|
||||
public enum PluginBuildTypeEnum {
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*/
|
||||
BUILD,
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
REGISTER,
|
||||
/**
|
||||
* 卸载
|
||||
*/
|
||||
UNREGISTER
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:OpsMainAppReadyEvent
|
||||
* @Date:2024/03/22 10:29
|
||||
* @Filename:OpsMainAppReadyEvent
|
||||
* @description:主程序启动完毕,此事件发布到插件应用程序,此时,插件还未启动
|
||||
*/
|
||||
public class OpsMainAppReadyEvent extends ApplicationEvent {
|
||||
|
||||
public OpsMainAppReadyEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:OpsMainAppStartedEvent
|
||||
* @Date:2024/03/22 10:31
|
||||
* @Filename:OpsMainAppStartedEvent
|
||||
* @description:主程序启动完毕,此事件发布到插件应用程序,此时,原神启动
|
||||
*/
|
||||
public class OpsMainAppStartedEvent extends ApplicationEvent {
|
||||
|
||||
public OpsMainAppStartedEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Getter
|
||||
public class PluginDirCheckEvent extends ApplicationEvent {
|
||||
|
||||
public PluginDirCheckEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.PluginCache;
|
||||
import cd.casic.plugin.PluginClassLoaderCache;
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.PluginManager;
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Getter
|
||||
public class PluginLoadedEvent extends ApplicationEvent {
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final PluginInfo pluginInfo;
|
||||
|
||||
public PluginLoadedEvent(Object source, PluginInfo pluginInfo) {
|
||||
super(source);
|
||||
this.pluginManager = (PluginManager) source;
|
||||
this.pluginInfo = pluginInfo;
|
||||
|
||||
if (!PluginCache.isExist(pluginInfo.getPluginId())) {
|
||||
PluginCache.put(pluginInfo.getPluginId(), pluginInfo);
|
||||
}
|
||||
if (PluginClassLoaderCache.getPlugin(pluginInfo.getPluginId()) == null) {
|
||||
PluginClassLoaderCache.put(pluginInfo.getPluginId(), pluginInfo.getPluginWrapper().getPluginClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginRestartedEvent
|
||||
* @Date:2024/03/22 10:51
|
||||
* @Filename:PluginRestartedEvent
|
||||
* @description:插件重启,在插件管理器中重启插件,和同目录上面的OpsMian****的事件不同
|
||||
*/
|
||||
public class PluginRestartedEvent extends ApplicationEvent {
|
||||
|
||||
public PluginRestartedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStartedEvent
|
||||
* @Date:2024/03/22 10:57
|
||||
* @Filename:PluginStartedEvent
|
||||
* @description:发布到插件管理器
|
||||
*/
|
||||
public class PluginStartedEvent extends ApplicationEvent {
|
||||
|
||||
public PluginStartedEvent(ApplicationContext pluginApplicationContext) {
|
||||
super(pluginApplicationContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStateChangedEvent
|
||||
* @Date:2024/03/22 11:02
|
||||
* @Filename:PluginStateChangedEvent
|
||||
* @description:插件状态,这个是考虑到插件的更新
|
||||
*/
|
||||
public class PluginStateChangedEvent extends ApplicationEvent {
|
||||
public PluginStateChangedEvent(ApplicationContext mainApplicationContext) {
|
||||
super(mainApplicationContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.event
|
||||
* @Project:ops
|
||||
* @name:PluginStoppedEvent
|
||||
* @Date:2024/03/22 11:05
|
||||
* @Filename:PluginStoppedEvent
|
||||
* @description:插件停止,停止后应该删除一切内容
|
||||
*/
|
||||
public class PluginStoppedEvent extends ApplicationEvent {
|
||||
public PluginStoppedEvent(ApplicationContext pluginApplicationContext) {
|
||||
super(pluginApplicationContext);
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package cd.casic.plugin.event;
|
||||
|
||||
import cd.casic.plugin.*;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class PluginUnloadedEvent extends ApplicationEvent {
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
private final PluginInfo pluginInfo;
|
||||
|
||||
public PluginUnloadedEvent(Object source, PluginInfo pluginInfo) {
|
||||
super(source);
|
||||
this.pluginManager = (PluginManager) source;
|
||||
this.pluginInfo = pluginInfo;
|
||||
|
||||
String startErrorPluginId = this.pluginInfo.getPluginId();
|
||||
PluginCache.remove(startErrorPluginId);
|
||||
PluginClassLoaderCache.remove(startErrorPluginId);
|
||||
PluginApplicationContextManager.removePluginApplicationContext(startErrorPluginId);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cd.casic.plugin.execption;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.execption
|
||||
* @Project:ops
|
||||
* @name:PluginException
|
||||
* @Date:2024/03/21 19:54
|
||||
* @Filename:PluginException
|
||||
* @description:毫无疑问,看这个名字就知道是插件异常类
|
||||
*/
|
||||
public class PluginException extends RuntimeException {
|
||||
|
||||
public PluginException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PluginException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public PluginException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PluginException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package cd.casic.plugin.pf4j;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.JarPluginRepository;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class OpsJarPluginRepository extends JarPluginRepository {
|
||||
|
||||
public OpsJarPluginRepository(Path... pluginsRoots) {
|
||||
this(Arrays.asList(pluginsRoots));
|
||||
}
|
||||
|
||||
public OpsJarPluginRepository(List<Path> pluginsRoots) {
|
||||
super(pluginsRoots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Path> getPluginPaths() {
|
||||
List<Path> pluginPaths = new ArrayList<>();
|
||||
for (Path pluginRoot : pluginsRoots){
|
||||
File rootDir = pluginRoot.toFile();
|
||||
|
||||
scanForJars(rootDir.toPath(), pluginPaths);
|
||||
|
||||
}
|
||||
return pluginPaths;
|
||||
}
|
||||
|
||||
private void scanForJars(Path directory, List<Path> pluginPaths) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
|
||||
for (Path entry : stream) {
|
||||
if (Files.isDirectory(entry)) {
|
||||
// 如果是目录,递归进入下一层
|
||||
scanForJars(entry, pluginPaths);
|
||||
} else if (entry.getFileName().toString().endsWith(".jar")) {
|
||||
// 如果是.jar文件,添加到结果列表
|
||||
pluginPaths.add(entry);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error scanning directory " + directory + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.pf4j;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.pf4j.DefaultPluginDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
public class OpsPluginDescriptor extends DefaultPluginDescriptor {
|
||||
private final String configFileName;
|
||||
private final List<String> configFileActive;
|
||||
|
||||
public OpsPluginDescriptor(String pluginId, String pluginDescription, String pluginClass,
|
||||
String version, String requires, String provider, String license,
|
||||
String configFileName, List<String> configFileActive){
|
||||
super(pluginId, pluginDescription, pluginClass, version, requires, provider, license);
|
||||
this.configFileActive = configFileActive;
|
||||
this.configFileName = configFileName;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package cd.casic.plugin.pf4j;
|
||||
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import org.pf4j.PluginStatusProvider;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpsPluginStatusProvider implements PluginStatusProvider {
|
||||
|
||||
private final Set<String> enabledPlugins = new HashSet<>();
|
||||
private final Set<String> disabledPlugins = new HashSet<>();
|
||||
|
||||
public OpsPluginStatusProvider(PluginProperties pluginProperties) {
|
||||
this.enabledPlugins.addAll(pluginProperties.getEnabledPlugins());
|
||||
this.disabledPlugins.addAll(pluginProperties.getDisabledPlugins());
|
||||
}
|
||||
|
||||
public static boolean isPropertySet(PluginProperties pluginProperties) {
|
||||
return CollectionUtil.isNotEmpty(pluginProperties.getEnabledPlugins())
|
||||
|| CollectionUtil.isNotEmpty(pluginProperties.getDisabledPlugins());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPluginDisabled(String pluginId) {
|
||||
if (disabledPlugins.contains(pluginId)) {
|
||||
return true;
|
||||
}
|
||||
return !enabledPlugins.isEmpty() && !enabledPlugins.contains(pluginId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disablePlugin(String pluginId) {
|
||||
if (isPluginDisabled(pluginId)) {
|
||||
return;
|
||||
}
|
||||
disabledPlugins.add(pluginId);
|
||||
enabledPlugins.remove(pluginId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enablePlugin(String pluginId) {
|
||||
if (!isPluginDisabled(pluginId)) {
|
||||
return;
|
||||
}
|
||||
enabledPlugins.add(pluginId);
|
||||
disabledPlugins.remove(pluginId);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package cd.casic.plugin.pf4j;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorCache;
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import cd.casic.plugin.utils.PluginDescriptorUtils;
|
||||
import org.pf4j.DevelopmentPluginClasspath;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.PropertiesPluginDescriptorFinder;
|
||||
import org.pf4j.util.FileUtils;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
public class OpsPropertiesPluginDescriptorFinder extends PropertiesPluginDescriptorFinder {
|
||||
static final DevelopmentPluginClasspath PLUGIN_CLASSPATH = new DevelopmentPluginClasspath();
|
||||
|
||||
public OpsPropertiesPluginDescriptorFinder(){
|
||||
super();
|
||||
}
|
||||
|
||||
public OpsPropertiesPluginDescriptorFinder(String propertiesFileName){
|
||||
super(propertiesFileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Path getPropertiesPath(Path pluginPath, String propertiesFileName) {
|
||||
if (Files.isDirectory(pluginPath)) {
|
||||
for (String location : PLUGIN_CLASSPATH.getClassesDirectories()) {
|
||||
Path path = pluginPath.resolve(location).resolve(propertiesFileName);
|
||||
Resource propertyResource = new FileSystemResource(path);
|
||||
if (propertyResource.exists()) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
throw new PluginRuntimeException(
|
||||
"Unable to find plugin descriptor file: " + DEFAULT_PROPERTIES_FILE_NAME);
|
||||
}
|
||||
|
||||
// it's a zip or jar file
|
||||
try {
|
||||
return FileUtils.getPath(pluginPath, propertiesFileName);
|
||||
} catch (IOException e) {
|
||||
throw new PluginRuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginDescriptor find(Path pluginPath) {
|
||||
Properties properties = readProperties(pluginPath);
|
||||
PluginDescriptorStorage storage = PluginDescriptorUtils.propertiesToStorage(properties);
|
||||
PluginDescriptorCache.put(storage.getPluginId(), storage);
|
||||
return PluginDescriptorUtils.storageToDescriptor(storage);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package cd.casic.plugin.pf4j;
|
||||
|
||||
import cd.casic.plugin.PluginDescriptorCache;
|
||||
import cd.casic.plugin.PluginDescriptorStorage;
|
||||
import cd.casic.plugin.PluginYamlProcessor;
|
||||
import cd.casic.plugin.utils.PluginDescriptorUtils;
|
||||
import cd.casic.plugin.utils.YamlUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.pf4j.PluginDescriptorFinder;
|
||||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.util.FileUtils;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Slf4j
|
||||
public class OpsYamlPluginDescriptorFinder implements PluginDescriptorFinder {
|
||||
public static final String DEFAULT_YAML_FILE_NAME = "plugin.yaml";
|
||||
|
||||
private final String yamlFileName;
|
||||
|
||||
public OpsYamlPluginDescriptorFinder() {
|
||||
this(DEFAULT_YAML_FILE_NAME);
|
||||
}
|
||||
|
||||
public OpsYamlPluginDescriptorFinder(String yamlFileName) {
|
||||
this.yamlFileName = yamlFileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Path pluginPath) {
|
||||
return Files.exists(pluginPath)
|
||||
&& (Files.isDirectory(pluginPath)
|
||||
|| FileUtils.isJarFile(pluginPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PluginDescriptor find(Path pluginPath) {
|
||||
Path yamlPath = null;
|
||||
try {
|
||||
yamlPath = YamlUtils.getYamlPath(pluginPath, yamlFileName);
|
||||
if (yamlPath == null) {
|
||||
throw new PluginRuntimeException("Cannot find the plugin manifest path");
|
||||
}
|
||||
log.debug("Lookup plugin descriptor in '{}'", yamlPath);
|
||||
if (Files.notExists(yamlPath)) {
|
||||
throw new PluginRuntimeException("Cannot find '{}' path", yamlPath);
|
||||
}
|
||||
|
||||
Resource yamlResource = new FileSystemResource(yamlPath);
|
||||
PluginYamlProcessor pluginYamlProcessor = new PluginYamlProcessor(yamlResource);
|
||||
PluginDescriptorStorage pluginDescriptor = pluginYamlProcessor.loadYaml();
|
||||
PluginDescriptorCache.put(pluginDescriptor.getPluginId(), pluginDescriptor);
|
||||
OpsPluginDescriptor defaultPluginDescriptor = PluginDescriptorUtils.storageToDescriptor(pluginDescriptor);
|
||||
pluginDescriptor.getDependencies().forEach(defaultPluginDescriptor::addDependency);
|
||||
return defaultPluginDescriptor;
|
||||
} finally {
|
||||
FileUtils.closePath(yamlPath);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.BasePlugin;
|
||||
import cd.casic.plugin.PluginClassLoaderCache;
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.annotation.AdminGroups;
|
||||
import cd.casic.plugin.annotation.InterceptPath;
|
||||
import cd.casic.plugin.register.group.AnnotationUtils;
|
||||
import cd.casic.plugin.register.group.ClassGroupHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.pf4j.util.FileUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:AnnotationHandler
|
||||
* @Date:2024/03/18 15:59
|
||||
* @Filename:AnnotationHandler
|
||||
* @deprecated 不再使用,改用{@link ClassGroupHandler} 实现扫包后分组存储。
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated
|
||||
public class AnnotationHandler implements BasePluginHandler {
|
||||
private final static Class<?>[] REGISTER_ANNO = {
|
||||
Bean.class,
|
||||
Mapper.class,
|
||||
Service.class,
|
||||
Component.class,
|
||||
Repository.class,
|
||||
Controller.class,
|
||||
Configuration.class,
|
||||
RestController.class,
|
||||
InterceptPath.class,
|
||||
};
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<Class<?>> classList = new ArrayList<>();
|
||||
List<Class<?>> adminGroupsClassList = new ArrayList<>();
|
||||
Set<String> classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper());
|
||||
for (String packageName : classPackageName) {
|
||||
ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId());
|
||||
log.info("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId());
|
||||
Class<?> clazz = loader.loadClass(packageName);
|
||||
// Class<?> clazz = plugin.getPluginWrapper().getPluginClassLoader().loadClass(packageName);
|
||||
// log.info("Load class {} using classloader {} for plugin {}", packageName, plugin.getPluginWrapper().getPluginClassLoader(), plugin.getPluginId());
|
||||
if (!BasePlugin.class.isAssignableFrom(clazz)) {
|
||||
classList.add(clazz);
|
||||
}
|
||||
AdminGroups annotation = clazz.getAnnotation(AdminGroups.class);
|
||||
if (annotation != null) {
|
||||
adminGroupsClassList.add(clazz);
|
||||
}
|
||||
}
|
||||
plugin.setClassList(classList);
|
||||
plugin.setAdminGroupsClassList(adminGroupsClassList);
|
||||
List<Class<?>> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
|
||||
if (!pluginClassList.isEmpty()) {
|
||||
List<Class<?>> registryClassList = new ArrayList<>();
|
||||
for (Class<?> aClass : pluginClassList) {
|
||||
// 原本这里使用Collections.disjoint()实现,是有问题的,结果总是true。
|
||||
// 因为aClass.getAnnotations()获得的是Proxy数组,而REGIS_ANNO是Class<Annotation>数组
|
||||
if(AnnotationUtils.hasAnnotations(aClass, false, Bean.class,
|
||||
Mapper.class,
|
||||
Service.class,
|
||||
Component.class,
|
||||
Repository.class,
|
||||
Controller.class,
|
||||
Configuration.class,
|
||||
RestController.class,
|
||||
InterceptPath.class)){
|
||||
registryClassList.add(aClass);
|
||||
}
|
||||
}
|
||||
if (!registryClassList.isEmpty()) {
|
||||
plugin.getPluginApplicationContext().register(registryClassList.toArray(new Class[0]));
|
||||
// plugin.getBeanCache().putAll(BeanUtils.getTempBeans(registryClassList));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 不做操作,直接通过关闭PluginApplicationContext完成注销
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描jar包中的类。
|
||||
*
|
||||
* @param basePackage 包名
|
||||
* @param pluginWrapper jar的PluginWrapper
|
||||
* @return 类全路径
|
||||
* @throws IOException 扫描异常
|
||||
*/
|
||||
public static Set<String> scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
|
||||
Path pluginPath = pluginWrapper.getPluginPath();
|
||||
Set<String> classPackageNames = new HashSet<>();
|
||||
File jarFile = null;
|
||||
if(Files.isDirectory(pluginPath)){
|
||||
List<File> jars = FileUtils.getJars(pluginPath);
|
||||
jarFile = jars.get(0);
|
||||
}else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){
|
||||
jarFile = pluginPath.toFile();
|
||||
}else {
|
||||
throw new RuntimeException("不正确的pluginPath");
|
||||
}
|
||||
try (JarFile jar = new JarFile(jarFile)) {
|
||||
Enumeration<JarEntry> jarEntries = jar.entries();
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
JarEntry entry = jarEntries.nextElement();
|
||||
String jarEntryName = entry.getName();
|
||||
if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) {
|
||||
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
|
||||
classPackageNames.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classPackageNames;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ApplicationContextPluginHandler
|
||||
* @Date:2024/03/19 14:48
|
||||
* @Filename:ApplicationContextPluginHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
public class ApplicationContextPluginHandler implements BasePluginHandler {
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
if (plugin.getApplicationContextIsRefresh()) {
|
||||
return;
|
||||
}
|
||||
plugin.getPluginApplicationContext().setClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
|
||||
plugin.getPluginApplicationContext().getDefaultListableBeanFactory()
|
||||
.registerSingleton(plugin.getPluginWrapper().getPluginId().trim(),
|
||||
plugin.getPluginWrapper().getPlugin());
|
||||
plugin.getPluginApplicationContext().refresh();
|
||||
plugin.setApplicationContextIsRefresh(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 获取插件ApplicationContext的DefaultListableBeanFactory实例
|
||||
DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
|
||||
|
||||
// 根据插件ID获取Bean的名称
|
||||
// String beanName = plugin.getPluginWrapper().getPluginId().trim();
|
||||
//
|
||||
// // 删除已注册的Bean
|
||||
// if (beanFactory.containsBeanDefinition(beanName)) {
|
||||
// beanFactory.removeBeanDefinition(beanName);
|
||||
// }
|
||||
|
||||
String[] beanNames = beanFactory.getBeanNamesForType(plugin.getPluginWrapper().getPlugin().getClass());
|
||||
Arrays.stream(beanNames)
|
||||
.filter(beanName -> beanName.equals(plugin.getPluginWrapper().getPluginId().trim()))
|
||||
.forEach(beanFactory::destroySingleton);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:BasePluginHandler
|
||||
* @Date:2024/03/18 15:48
|
||||
* @Filename:BasePluginHandler
|
||||
* @description:基础插件注册类
|
||||
*/
|
||||
public interface BasePluginHandler {
|
||||
|
||||
/**
|
||||
* 插件组件初始化
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
void initialize() throws Exception;
|
||||
|
||||
/**
|
||||
* 插件组件注册
|
||||
*
|
||||
* @param plugin
|
||||
* @throws Exception
|
||||
*/
|
||||
void registry(PluginInfo plugin) throws Exception;
|
||||
|
||||
/**
|
||||
* 插件组件卸载注册
|
||||
*
|
||||
* @param plugin
|
||||
* @throws Exception
|
||||
*/
|
||||
void unRegistry(PluginInfo plugin) throws Exception;
|
||||
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import cd.casic.plugin.register.group.filter.impl.ControllerFilter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ControllerHandler
|
||||
* @Date:2024/03/19 14:22
|
||||
* @Filename:ControllerHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
@Slf4j
|
||||
public class ControllerHandler implements BasePluginHandler {
|
||||
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
|
||||
Method getMappingForMethod;
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
// 这里反射获取 getMappingForMethod
|
||||
requestMappingHandlerMapping = SpringUtil.getBean(RequestMappingHandlerMapping.class);
|
||||
getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, "getMappingForMethod", Method.class, Class.class);
|
||||
getMappingForMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
ApplicationContext applicationContext = plugin.getMainApplicationContext();
|
||||
PluginProperties pluginProperties = applicationContext.getBean(PluginProperties.class);
|
||||
for (Class<?> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
|
||||
setPathPrefix(plugin.getPluginId(), aClass, pluginProperties);
|
||||
plugin.addController(aClass);
|
||||
Object bean = plugin.getPluginApplicationContext().getBean(aClass);
|
||||
Method[] methods = aClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getAnnotation(RequestMapping.class) != null
|
||||
|| method.getAnnotation(GetMapping.class) != null
|
||||
|| method.getAnnotation(PostMapping.class) != null
|
||||
|| method.getAnnotation(DeleteMapping.class) != null
|
||||
|| method.getAnnotation(PutMapping.class) != null
|
||||
|| method.getAnnotation(PatchMapping.class) != null) {
|
||||
RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
|
||||
requestMappingHandlerMapping.registerMapping(requestMappingInfo, bean, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
for (RequestMappingInfo requestMappingInfo : getRequestMappingInfo(plugin)) {
|
||||
requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
|
||||
}
|
||||
}
|
||||
|
||||
List<RequestMappingInfo> getRequestMappingInfo(PluginInfo plugin) throws Exception {
|
||||
List<RequestMappingInfo> requestMappingInfoList = new ArrayList<>();
|
||||
for (Class<?> aClass : plugin.getGroupClass(ControllerFilter.GROUP_NAME)) {
|
||||
Method[] methods = aClass.getMethods();
|
||||
for (Method method : methods) {
|
||||
RequestMappingInfo requestMappingInfo = (RequestMappingInfo) getMappingForMethod.invoke(requestMappingHandlerMapping, method, aClass);
|
||||
requestMappingInfoList.add(requestMappingInfo);
|
||||
}
|
||||
}
|
||||
return requestMappingInfoList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置请求路径前缀
|
||||
*
|
||||
* @param aClass controller 类
|
||||
*/
|
||||
private void setPathPrefix(String pluginId, Class<?> aClass, PluginProperties pluginProperties) {
|
||||
RequestMapping requestMapping = aClass.getAnnotation(RequestMapping.class);
|
||||
if (requestMapping == null) {
|
||||
return;
|
||||
}
|
||||
String pathPrefix = pluginProperties.getRestPathPrefix();
|
||||
if (pluginProperties.isEnablePluginIdAsRestPrefix()) {
|
||||
if (StrUtil.isNotEmpty(pathPrefix)) {
|
||||
pathPrefix = joiningPath(pathPrefix, pluginId);
|
||||
} else {
|
||||
pathPrefix = pluginId;
|
||||
}
|
||||
} else {
|
||||
if (StrUtil.isEmpty(pathPrefix)) {
|
||||
// 不启用插件id作为路径前缀, 并且路径前缀为空, 则直接返回。
|
||||
return;
|
||||
}
|
||||
}
|
||||
handleRequestMapping(requestMapping, pathPrefix);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleRequestMapping(RequestMapping requestMapping, String pathPrefix) {
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(requestMapping);
|
||||
Set<String> definePaths = new HashSet<>();
|
||||
definePaths.addAll(Arrays.asList(requestMapping.path()));
|
||||
definePaths.addAll(Arrays.asList(requestMapping.value()));
|
||||
try {
|
||||
Field field = invocationHandler.getClass().getDeclaredField("memberValues");
|
||||
field.setAccessible(true);
|
||||
Map<String, Object> memberValues = (Map<String, Object>) field.get(invocationHandler);
|
||||
String[] newPath = new String[definePaths.size()];
|
||||
int i = 0;
|
||||
for (String definePath : definePaths) {
|
||||
// 解决插件启用、禁用后, 路径前缀重复的问题。
|
||||
if (definePath.contains(pathPrefix)) {
|
||||
newPath[i++] = definePath;
|
||||
} else {
|
||||
newPath[i++] = joiningPath(pathPrefix, definePath);
|
||||
}
|
||||
}
|
||||
if (newPath.length == 0) {
|
||||
newPath = new String[]{pathPrefix};
|
||||
}
|
||||
memberValues.put("path", newPath);
|
||||
memberValues.put("value", new String[]{});
|
||||
} catch (Exception e) {
|
||||
log.error("Define Plugin RestController pathPrefix error : {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接路径
|
||||
*
|
||||
* @param path1 路径1
|
||||
* @param path2 路径2
|
||||
* @return 拼接的路径
|
||||
*/
|
||||
private String joiningPath(String path1, String path2) {
|
||||
if (path1 != null && path2 != null) {
|
||||
if (path1.endsWith("/") && path2.startsWith("/")) {
|
||||
return path1 + path2.substring(1);
|
||||
} else if (!path1.endsWith("/") && !path2.startsWith("/")) {
|
||||
return path1 + "/" + path2;
|
||||
} else {
|
||||
return path1 + path2;
|
||||
}
|
||||
} else if (path1 != null) {
|
||||
return path1;
|
||||
} else if (path2 != null) {
|
||||
return path2;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
|
||||
import org.apache.ibatis.executor.ErrorContext;
|
||||
import org.apache.ibatis.io.Resources;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.session.defaults.DefaultSqlSession;
|
||||
import org.mybatis.spring.mapper.MapperFactoryBean;
|
||||
import org.pf4j.util.FileUtils;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:MybatisHandler
|
||||
* @Date:2024/03/19 14:51
|
||||
* @Filename:MybatisHandler
|
||||
* @deprecated 不再使用,采用{@link MybatisPlusHandler} 统一实现
|
||||
*/
|
||||
@Deprecated
|
||||
public class MybatisHandler implements BasePluginHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<Class<?>> mapperClassList = getMapperList(plugin);
|
||||
if (mapperClassList.isEmpty()) return;
|
||||
|
||||
//注册mapper
|
||||
for (Class<?> mapperClass : mapperClassList) {
|
||||
GenericBeanDefinition definition = new GenericBeanDefinition();
|
||||
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
|
||||
definition.setBeanClass(MapperFactoryBean.class);
|
||||
definition.getPropertyValues().add("addToConfig", true);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
plugin.getPluginApplicationContext().registerBeanDefinition(mapperClass.getName(), definition);
|
||||
}
|
||||
//注册mapper.xml
|
||||
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory");
|
||||
Configuration configuration = sqlSessionFactory.getConfiguration();
|
||||
try {
|
||||
Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
|
||||
Path pluginPath = plugin.getPluginWrapper().getPluginPath();
|
||||
String xmlLocationPattern = plugin.getMapperXmlDir();
|
||||
xmlLocationPattern = xmlLocationPattern.replaceAll("\\*\\*", "<>").replaceAll("\\*", "<>")
|
||||
.replaceAll("\\.", "\\.").replaceAll("<>", ".*");
|
||||
File jarFile = null;
|
||||
if(Files.isDirectory(pluginPath)){
|
||||
List<File> jars = FileUtils.getJars(pluginPath);
|
||||
jarFile = jars.get(0);
|
||||
}else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){
|
||||
jarFile = pluginPath.toFile();
|
||||
}else {
|
||||
throw new RuntimeException("不正确的pluginPath");
|
||||
}
|
||||
Enumeration<JarEntry> jarEntries = new JarFile(jarFile).entries();
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
JarEntry entry = jarEntries.nextElement();
|
||||
String jarEntryName = entry.getName();
|
||||
if (Pattern.matches(xmlLocationPattern, jarEntryName) && jarEntryName.endsWith(".xml")) {
|
||||
URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName);
|
||||
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
|
||||
InputStream in = jarConnection.getInputStream();
|
||||
try {
|
||||
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(in,
|
||||
configuration, url.getPath(), configuration.getSqlFragments());
|
||||
xmlMapperBuilder.parse();
|
||||
in.close();
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Failed to parse mapping resource: '" + url.getPath() + "'", e);
|
||||
} finally {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
ErrorContext.instance().reset();
|
||||
JarFile currJarFile = jarConnection.getJarFile();
|
||||
currJarFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Resources.setDefaultClassLoader(ClassUtils.getDefaultClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
List<Class<?>> mapperClassList = getMapperList(plugin);
|
||||
if (mapperClassList.isEmpty()) return;
|
||||
|
||||
for (Class<?> mapperClass : mapperClassList) {
|
||||
String[] beanNames = plugin.getPluginApplicationContext().getBeanNamesForType(mapperClass);
|
||||
Arrays.stream(beanNames).forEach(beanName -> plugin.getPluginApplicationContext().removeBeanDefinition(beanName));
|
||||
}
|
||||
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) plugin.getMainApplicationContext().getBean("sqlSessionFactory");
|
||||
Configuration configuration = sqlSessionFactory.getConfiguration();
|
||||
clearValues(configuration, "mappedStatements");
|
||||
clearValues(configuration, "caches");
|
||||
clearValues(configuration, "resultMaps");
|
||||
clearValues(configuration, "parameterMaps");
|
||||
clearValues(configuration, "keyGenerators");
|
||||
clearValues(configuration, "sqlFragments");
|
||||
// Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
|
||||
// loadedResourcesField.setAccessible(true);
|
||||
// ((Set<?>) loadedResourcesField.get(configuration)).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取所有Mapper接口
|
||||
* @author dolphin
|
||||
* @date 2021/11/13 8:31
|
||||
*/
|
||||
private List<Class<?>> getMapperList(PluginInfo plugin){
|
||||
List<Class<?>> mapperClassList = new ArrayList<>();
|
||||
|
||||
for (Class<?> aClass : plugin.getClassList()) {
|
||||
Mapper annotation = aClass.getAnnotation(Mapper.class);
|
||||
if (annotation != null) {
|
||||
mapperClassList.add(aClass);
|
||||
}
|
||||
}
|
||||
return mapperClassList;
|
||||
}
|
||||
|
||||
private void clearValues(Configuration configuration, String fieldName) throws Exception {
|
||||
|
||||
Field field = configuration.getClass().getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Map<?, ?> map = (Map<?, ?>) field.get(configuration);
|
||||
DefaultSqlSession.StrictMap<Object> newMap = new DefaultSqlSession.StrictMap<Object>();
|
||||
for (Object key : map.keySet()) {
|
||||
try {
|
||||
newMap.put((String) key, map.get(key));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
newMap.put((String) key, ex.getMessage());
|
||||
}
|
||||
}
|
||||
field.set(configuration, newMap);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.constants.OpsConstants;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import org.pf4j.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.register
|
||||
* @Project:ops
|
||||
* @name:ResourcesHandler
|
||||
* @Date:2024/03/19 14:54
|
||||
* @Filename:ResourcesHandler
|
||||
* @description:Todo
|
||||
*/
|
||||
public class ResourcesHandler implements BasePluginHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
Path pluginPath = plugin.getPluginWrapper().getPluginPath();
|
||||
Set<String> staticClassPathLocations = plugin.getStaticClassPathLocations();
|
||||
File jarFile = null;
|
||||
if(Files.isDirectory(pluginPath)){
|
||||
List<File> jars = FileUtils.getJars(pluginPath);
|
||||
jarFile = jars.get(0);
|
||||
}else if(pluginPath.toFile().getName().toLowerCase().endsWith(".jar")){
|
||||
jarFile = pluginPath.toFile();
|
||||
}else {
|
||||
throw new RuntimeException("不正确的pluginPath");
|
||||
}
|
||||
Enumeration<JarEntry> jarEntries = new JarFile(jarFile).entries();
|
||||
File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId());
|
||||
if (!file.exists()) {
|
||||
FileUtil.mkdir(file);
|
||||
}
|
||||
FileUtil.clean(file);
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
JarEntry entry = jarEntries.nextElement();
|
||||
String jarEntryName = entry.getName();
|
||||
for (String staticClassPathLocation : staticClassPathLocations) { //staticClassPathLocation里读取到所有静态资源的位置 然后将以插件为单位 打包到web的static目录下 即可访问
|
||||
if (!staticClassPathLocation.equals(jarEntryName) && jarEntryName.startsWith(staticClassPathLocation)
|
||||
&& !jarEntryName.endsWith(".class") && !entry.isDirectory()) {
|
||||
URL url = new URL("jar:file:" + jarFile.getAbsolutePath() + "!/" + jarEntryName);
|
||||
JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
|
||||
InputStream in = jarConnection.getInputStream();
|
||||
File file1 = new File(file.getAbsolutePath() + File.separator + jarEntryName);
|
||||
FileUtil.touch(file1.getAbsolutePath());
|
||||
int index;
|
||||
byte[] bytes = new byte[1024];
|
||||
FileOutputStream downloadFile = new FileOutputStream(file.getAbsolutePath() + File.separator + jarEntryName);
|
||||
while ((index = in.read(bytes)) != -1) {
|
||||
downloadFile.write(bytes, 0, index);
|
||||
downloadFile.flush();
|
||||
}
|
||||
downloadFile.close();
|
||||
in.close();
|
||||
JarFile currJarFile = jarConnection.getJarFile();
|
||||
currJarFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
File file = new File(OpsConstants.PLUGINS_RESOURCES_DIR + File.separator + plugin.getPluginId());
|
||||
if (file.exists()) {
|
||||
FileUtil.del(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.group.filter.impl.BasicBeanFilter;
|
||||
import cd.casic.plugin.register.group.filter.impl.ControllerFilter;
|
||||
import cd.casic.plugin.register.group.filter.impl.MapperFilter;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SpringBeanRegisterHandler implements BasePluginHandler {
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<Class<?>> basicBeanClassList = new ArrayList<>();
|
||||
basicBeanClassList.addAll(plugin.getGroupClass(BasicBeanFilter.GROUP_NAME));
|
||||
basicBeanClassList.addAll(plugin.getGroupClass(MapperFilter.GROUP_NAME));
|
||||
basicBeanClassList.addAll(plugin.getGroupClass(ControllerFilter.GROUP_NAME));
|
||||
// if(!basicBeanClassList.isEmpty()){
|
||||
// plugin.getPluginApplicationContext().register(basicBeanClassList.toArray(new Class[0]));
|
||||
// }
|
||||
basicBeanClassList.forEach(clazz -> {
|
||||
AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(clazz);
|
||||
beanDefinition.setBeanClass(clazz);
|
||||
BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
|
||||
String beanName = beanNameGenerator.generateBeanName(beanDefinition, plugin.getPluginApplicationContext());
|
||||
plugin.getPluginApplicationContext().registerBeanDefinition(beanName, beanDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springdoc.api.AbstractOpenApiResource;
|
||||
import org.springdoc.core.service.OpenAPIService;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 插件系统集成SpringDoc。swagger能够根据插件启停自动更新接口文档
|
||||
*/
|
||||
@Slf4j
|
||||
public class SpringDocHandler implements BasePluginHandler {
|
||||
private Set<Class<?>> restControllers;
|
||||
private OpenAPIService openAPIService;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void initialize() {
|
||||
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
|
||||
AbstractOpenApiResource openApiResource = applicationContext.getBean(AbstractOpenApiResource.class);
|
||||
try {
|
||||
// 获取OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段
|
||||
Field additionalRestControllers = ReflectUtil.getField(openApiResource.getClass(), "ADDITIONAL_REST_CONTROLLERS");
|
||||
additionalRestControllers.setAccessible(true);
|
||||
restControllers = (Set<Class<?>>) additionalRestControllers.get(openApiResource);
|
||||
} catch (IllegalAccessException e) {
|
||||
restControllers = null;
|
||||
}
|
||||
openAPIService = applicationContext.getBean(OpenAPIService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
if(restControllers != null){
|
||||
// 将插件中的Controller类添加到OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段
|
||||
restControllers.addAll(plugin.getControllers());
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
if(restControllers != null && !restControllers.isEmpty()){
|
||||
for (Class<?> clazz : plugin.getControllers()) {
|
||||
// 从OpenApiResource的ADDITIONAL_REST_CONTROLLERS字段中移除插件中的Controller类
|
||||
restControllers.remove(clazz);
|
||||
}
|
||||
refresh();
|
||||
plugin.getControllers().clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh(){
|
||||
if(openAPIService != null){
|
||||
// 手动刷新OpenApiResource
|
||||
openAPIService.setCachedOpenAPI(null, Locale.getDefault());
|
||||
// openAPIService.resetCalculatedOpenAPI();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package cd.casic.plugin.register;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.config.ConfigurationFileHandler;
|
||||
import cd.casic.plugin.register.config.SpringAutoConfigurationFileHandler;
|
||||
import cd.casic.plugin.register.database.DatabaseHandler;
|
||||
import cd.casic.plugin.register.group.ClassGroupHandler;
|
||||
import cd.casic.plugin.register.mongoplus.MongoHandler;
|
||||
import cd.casic.plugin.register.mybatis.mybatisplus.MybatisPlusHandler;
|
||||
import cd.casic.plugin.register.websocket.WebSocketHandler;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author:mianbin
|
||||
* @Package:cd.casic.plugin.handler
|
||||
* @Project:ops
|
||||
* @name:LoadPluginHandle
|
||||
* @Date:2024/03/18 15:46
|
||||
* @Filename:LoadPluginHandle
|
||||
* @description:Todo
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class StartPluginManagerHandler implements BasePluginHandler {
|
||||
List<BasePluginHandler> pluginRegisterList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@PostConstruct
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
pluginRegisterList.clear();
|
||||
pluginRegisterList.add(new ClassGroupHandler());
|
||||
// pluginRegisterList.add(new AnnotationHandler());
|
||||
pluginRegisterList.add(new ConfigurationFileHandler());
|
||||
pluginRegisterList.add(new SpringAutoConfigurationFileHandler());
|
||||
pluginRegisterList.add(new SpringBeanRegisterHandler());
|
||||
pluginRegisterList.add(new MybatisPlusHandler());
|
||||
// pluginRegisterList.add(new MybatisHandler());
|
||||
pluginRegisterList.add(new ResourcesHandler());
|
||||
pluginRegisterList.add(new ApplicationContextPluginHandler());
|
||||
pluginRegisterList.add(new DatabaseHandler());
|
||||
pluginRegisterList.add(new ControllerHandler());
|
||||
pluginRegisterList.add(new WebSocketHandler());
|
||||
pluginRegisterList.add(new SpringDocHandler());
|
||||
pluginRegisterList.add(new MongoHandler());
|
||||
for (BasePluginHandler pluginHandle : pluginRegisterList) {
|
||||
pluginHandle.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
for (BasePluginHandler pluginHandler : pluginRegisterList) {
|
||||
pluginHandler.registry(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
for (BasePluginHandler pluginHandler : pluginRegisterList) {
|
||||
pluginHandler.unRegistry(plugin);
|
||||
}
|
||||
// for (int i = pluginRegisterList.size() - 1; i >= 0; i--){
|
||||
// pluginRegisterList.get(i).unRegistry(plugin);
|
||||
// }
|
||||
plugin.getClassList().clear();
|
||||
plugin.setClassList(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package cd.casic.plugin.register.config;
|
||||
|
||||
import org.pf4j.util.StringUtils;
|
||||
|
||||
public class ConfigFileUtils {
|
||||
/**
|
||||
* 拼接插件独立配置文件的文件名和环境后缀
|
||||
* @param fileName 插件独立配置文件文件名
|
||||
* @param suffix 配置文件环境后缀,PluginConfiguration中定义
|
||||
* @return 拼接后的文件名
|
||||
*/
|
||||
public static String joinConfigFileName(String fileName, String suffix){
|
||||
if(StringUtils.isNullOrEmpty(fileName)){
|
||||
return null;
|
||||
}
|
||||
String fileNamePrefix;
|
||||
String fileNamePrefixSuffix;
|
||||
|
||||
if(fileName.lastIndexOf(".") == -1) {
|
||||
fileNamePrefix = fileName;
|
||||
fileNamePrefixSuffix = "";
|
||||
} else {
|
||||
int index = fileName.lastIndexOf(".");
|
||||
fileNamePrefix = fileName.substring(0, index);
|
||||
fileNamePrefixSuffix = fileName.substring(index);
|
||||
}
|
||||
if(suffix == null){
|
||||
suffix = "";
|
||||
}
|
||||
if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){
|
||||
suffix = "-" + suffix;
|
||||
}
|
||||
return fileNamePrefix + suffix + fileNamePrefixSuffix;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package cd.casic.plugin.register.config;
|
||||
|
||||
import cd.casic.plugin.PluginConfigWrapper;
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.PluginProperties;
|
||||
import cd.casic.plugin.PluginYamlConfigurationParser;
|
||||
import cd.casic.plugin.annotation.PluginConfiguration;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import org.pf4j.RuntimeMode;
|
||||
import org.pf4j.util.StringUtils;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 配置文件处理器,用于处理插件独立的配置文件
|
||||
*/
|
||||
public class ConfigurationFileHandler implements BasePluginHandler {
|
||||
private final PluginYamlConfigurationParser configurationParser;
|
||||
|
||||
public ConfigurationFileHandler(){
|
||||
this.configurationParser = new PluginYamlConfigurationParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
PluginProperties pluginProperties = plugin.getMainApplicationContext().getBean(PluginProperties.class);
|
||||
RuntimeMode runtimeMode = pluginProperties.getRuntimeMode();
|
||||
for(Class<?> aClass : plugin.getClassList()){
|
||||
PluginConfiguration configDefinition = aClass.getAnnotation(PluginConfiguration.class);
|
||||
if(configDefinition == null){
|
||||
continue;
|
||||
}
|
||||
String fileName = getConfigFileName(configDefinition, runtimeMode);
|
||||
Object parseObject;
|
||||
if(!StringUtils.isNullOrEmpty(fileName)){
|
||||
PluginConfigWrapper pluginConfigWrapper =
|
||||
new PluginConfigWrapper(fileName, aClass);
|
||||
parseObject = configurationParser.parse(plugin, pluginConfigWrapper);
|
||||
} else {
|
||||
try {
|
||||
parseObject = aClass.getDeclaredConstructor().newInstance();
|
||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Failed to instantiate class " + aClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
String name = aClass.getSimpleName();
|
||||
DefaultListableBeanFactory listableBeanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
|
||||
if(!listableBeanFactory.containsSingleton(name)) {
|
||||
listableBeanFactory.registerSingleton(name, parseObject);
|
||||
}
|
||||
plugin.addPluginConfigObject(parseObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
/*上面的写法没有考虑到资源泄露,这里把clear,关闭资源*/
|
||||
List<Object> objectsToClear = new ArrayList<>(plugin.getPluginConfigObjects());
|
||||
plugin.getPluginConfigObjects().clear();
|
||||
for (Object obj : objectsToClear) {
|
||||
if (obj instanceof AutoCloseable) {
|
||||
((AutoCloseable) obj).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件独立配置文件的文件名
|
||||
* @param pluginConfiguration 插件配置类上的自定义注解
|
||||
* @param runtimeMode 插件系统运行模式
|
||||
* @return 插件独立配置文件的文件名
|
||||
*/
|
||||
private String getConfigFileName(PluginConfiguration pluginConfiguration, RuntimeMode runtimeMode){
|
||||
String fileName = pluginConfiguration.fileName();
|
||||
if(StringUtils.isNullOrEmpty(fileName)){
|
||||
return null;
|
||||
}
|
||||
String suffix = "";
|
||||
if(runtimeMode == RuntimeMode.DEPLOYMENT){
|
||||
// deployment模式
|
||||
suffix = pluginConfiguration.deploySuffix();
|
||||
} else if(runtimeMode == RuntimeMode.DEVELOPMENT){
|
||||
// development模式
|
||||
suffix = pluginConfiguration.devSuffix();
|
||||
}
|
||||
return joinConfigFileName(fileName, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接插件独立配置文件的文件名和环境后缀
|
||||
* @param fileName 插件独立配置文件文件名
|
||||
* @param suffix 配置文件环境后缀,PluginConfiguration中定义
|
||||
* @return 拼接后的文件名
|
||||
*/
|
||||
private String joinConfigFileName(String fileName, String suffix){
|
||||
if(StringUtils.isNullOrEmpty(fileName)){
|
||||
return null;
|
||||
}
|
||||
String fileNamePrefix;
|
||||
String fileNamePrefixSuffix;
|
||||
|
||||
if(fileName.lastIndexOf(".") == -1) {
|
||||
fileNamePrefix = fileName;
|
||||
fileNamePrefixSuffix = "";
|
||||
} else {
|
||||
int index = fileName.lastIndexOf(".");
|
||||
fileNamePrefix = fileName.substring(0, index);
|
||||
fileNamePrefixSuffix = fileName.substring(index);
|
||||
}
|
||||
if(suffix == null){
|
||||
suffix = "";
|
||||
}
|
||||
if(StringUtils.isNotNullOrEmpty(suffix) && !suffix.startsWith("-")){
|
||||
suffix = "-" + suffix;
|
||||
}
|
||||
return fileNamePrefix + suffix + fileNamePrefixSuffix;
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
package cd.casic.plugin.register.config;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.pf4j.OpsPluginDescriptor;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import cd.casic.plugin.utils.YamlUtils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.env.PropertiesPropertySourceLoader;
|
||||
import org.springframework.boot.env.PropertySourceLoader;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class SpringAutoConfigurationFileHandler implements BasePluginHandler {
|
||||
List<PropertySourceLoader> propertySourceLoaders = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 活动文件的属性名称,就是dev,prod等
|
||||
*/
|
||||
private static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
|
||||
|
||||
/**
|
||||
* 配置文件的属性名称
|
||||
*/
|
||||
private static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
propertySourceLoaders.add(new YamlPropertySourceLoader());
|
||||
propertySourceLoaders.add(new PropertiesPropertySourceLoader());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
PluginDescriptor pluginDescriptor = plugin.getPluginWrapper().getDescriptor();
|
||||
ConfigurableEnvironment environment = plugin.getPluginApplicationContext().getEnvironment();
|
||||
|
||||
if(!(pluginDescriptor instanceof OpsPluginDescriptor)){
|
||||
return;
|
||||
}
|
||||
|
||||
List<Resource> resourcesFromDescriptor = getConfigResourceFromDescriptor(plugin);
|
||||
|
||||
List<PropertySource<?>> propProfiles = getPropProfiles(resourcesFromDescriptor);
|
||||
if(ObjectUtils.isEmpty(propProfiles)){
|
||||
return;
|
||||
}
|
||||
for (PropertySource<?> propertySource : propProfiles) {
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
}
|
||||
|
||||
// 发现原始文件中配置的 profiles
|
||||
List<Profile> profiles = getProfiles(environment);
|
||||
if(!ObjectUtils.isEmpty(profiles)){
|
||||
loadProfilesConfig(plugin.getPluginWrapper().getPluginPath(), (OpsPluginDescriptor) pluginDescriptor, environment, profiles);
|
||||
}
|
||||
|
||||
ConfigurationPropertiesBindingPostProcessor.register(plugin.getPluginApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 无需处理,后续关闭pluginApplicationContext即可
|
||||
}
|
||||
|
||||
private List<Resource> getConfigResourceFromDescriptor(PluginInfo plugin){
|
||||
log.info("插件 {} 尝试从插件描述中读取配置文件名", plugin.getPluginId());
|
||||
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
|
||||
if(!(descriptor instanceof OpsPluginDescriptor)){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
|
||||
String configFileName = opsPluginDescriptor.getConfigFileName();
|
||||
if(StrUtil.isEmpty(configFileName)){
|
||||
log.info("插件 {} 的插件描述中未设置配置文件名", plugin.getPluginId());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> configFileActiveNames = getConfigFileActiveNames(configFileName, opsPluginDescriptor.getConfigFileActive());
|
||||
log.info("插件{} 的配置文件为 {} {}", plugin.getPluginId(), configFileName, configFileActiveNames);
|
||||
Path pluginPath = plugin.getPluginWrapper().getPluginPath();
|
||||
|
||||
List<Resource> configFileResources = configFileActiveNames.stream()
|
||||
.map(configFileActiveName -> YamlUtils.getYamlPath(pluginPath, configFileActiveName))
|
||||
.filter(Objects::nonNull)
|
||||
.map(FileSystemResource::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
configFileResources.add(new FileSystemResource(YamlUtils.getYamlPath(pluginPath, configFileName)));
|
||||
|
||||
return configFileResources;
|
||||
}
|
||||
|
||||
private List<Resource> getConfigResourceFromAnnotation(){
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<String> getConfigFileActiveNames(String configFileName, List<String> configFileActives){
|
||||
return configFileActives.stream()
|
||||
.map(configFileActive -> ConfigFileUtils.joinConfigFileName(configFileName, configFileActive))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Resource 中解析出 PropertySource
|
||||
* @param resources resources
|
||||
* @return List
|
||||
* @throws IOException 加载文件 IOException 异常
|
||||
*/
|
||||
private List<PropertySource<?>> getPropProfiles(List<Resource> resources) throws IOException {
|
||||
List<PropertySource<?>> propProfiles = new ArrayList<>();
|
||||
if(resources == null || resources.isEmpty()){
|
||||
return propProfiles;
|
||||
}
|
||||
for (Resource resource : resources) {
|
||||
if(resource == null || !resource.exists()){
|
||||
continue;
|
||||
}
|
||||
String filename = resource.getFilename();
|
||||
if(ObjectUtils.isEmpty(filename)){
|
||||
log.error("File name is empty!");
|
||||
return null;
|
||||
}
|
||||
for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) {
|
||||
if(!canLoadFileExtension(propertySourceLoader, filename)){
|
||||
continue;
|
||||
}
|
||||
log.info("正在从 {} 读取插件配置", filename);
|
||||
List<PropertySource<?>> propertySources = propertySourceLoader.load(filename, resource);
|
||||
if(ObjectUtils.isEmpty(propertySources)){
|
||||
continue;
|
||||
}
|
||||
propProfiles.addAll(propertySources);
|
||||
}
|
||||
}
|
||||
return propProfiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件后缀判断是否可解析
|
||||
* @param loader PropertySourceLoader
|
||||
* @param name 文件名称
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean canLoadFileExtension(PropertySourceLoader loader, String name) {
|
||||
return Arrays.stream(loader.getFileExtensions())
|
||||
.anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name,
|
||||
fileExtension));
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载 spring.profiles.active/spring.profiles.include 定义的配置
|
||||
* @param environment ConfigurableEnvironment
|
||||
* @param profiles 主配置文件中定义的值
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
private void loadProfilesConfig(Path pluginPath, OpsPluginDescriptor opsPluginDescriptor,
|
||||
ConfigurableEnvironment environment, List<Profile> profiles) throws Exception {
|
||||
// 解析当前文件名称
|
||||
for (Profile profile : profiles) {
|
||||
String name = profile.getName();
|
||||
String fileName = ConfigFileUtils.joinConfigFileName(opsPluginDescriptor.getConfigFileName(), name);
|
||||
|
||||
|
||||
Path configFilePath = YamlUtils.getYamlPath(pluginPath, fileName);
|
||||
FileSystemResource configFileResource = new FileSystemResource(configFilePath);
|
||||
if(ObjectUtils.isEmpty(configFileResource)){
|
||||
continue;
|
||||
}
|
||||
List<PropertySource<?>> propProfiles = getPropProfiles(Collections.singletonList(configFileResource));
|
||||
if(ObjectUtils.isEmpty(propProfiles)){
|
||||
return;
|
||||
}
|
||||
for (PropertySource<?> propertySource : propProfiles) {
|
||||
environment.getPropertySources().addLast(propertySource);
|
||||
}
|
||||
}
|
||||
// 重新设置 ActiveProfiles
|
||||
String[] names = profiles.stream()
|
||||
.filter((profile) -> profile != null && !profile.isDefaultProfile())
|
||||
.map(Profile::getName)
|
||||
.toArray(String[]::new);
|
||||
environment.setActiveProfiles(names);
|
||||
}
|
||||
|
||||
private List<Profile> getProfiles(Environment environment) {
|
||||
List<Profile> profiles = new ArrayList<>();
|
||||
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty(environment);
|
||||
profiles.addAll(getOtherActiveProfiles(environment, activatedViaProperty));
|
||||
profiles.addAll(activatedViaProperty);
|
||||
profiles.removeIf(
|
||||
(profile) -> (profile != null && profile.isDefaultProfile()));
|
||||
return profiles;
|
||||
}
|
||||
|
||||
private Set<Profile> getProfilesActivatedViaProperty(Environment environment) {
|
||||
if (!environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
|
||||
&& !environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Binder binder = Binder.get(environment);
|
||||
Set<Profile> activeProfiles = new LinkedHashSet<>();
|
||||
activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
|
||||
activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
|
||||
return activeProfiles;
|
||||
}
|
||||
|
||||
private List<Profile> getOtherActiveProfiles(Environment environment, Set<Profile> activatedViaProperty) {
|
||||
return Arrays.stream(environment.getActiveProfiles()).map(Profile::new)
|
||||
.filter((profile) -> !activatedViaProperty.contains(profile))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Set<Profile> getProfiles(Binder binder, String name) {
|
||||
return binder.bind(name, String[].class).map(this::asProfileSet)
|
||||
.orElse(Collections.emptySet());
|
||||
}
|
||||
|
||||
private Set<Profile> asProfileSet(String[] profileNames) {
|
||||
List<Profile> profiles = new ArrayList<>();
|
||||
for (String profileName : profileNames) {
|
||||
profiles.add(new Profile(profileName));
|
||||
}
|
||||
return new LinkedHashSet<>(profiles);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private static class Profile {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final boolean defaultProfile;
|
||||
|
||||
Profile(String name) {
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
Profile(String name, boolean defaultProfile) {
|
||||
Assert.notNull(name, "Name must not be null");
|
||||
this.name = name;
|
||||
this.defaultProfile = defaultProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
return ((Profile) obj).name.equals(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package cd.casic.plugin.register.database;
|
||||
|
||||
|
||||
import cd.casic.plugin.utils.dBUtils.DBEnums;
|
||||
|
||||
public interface DataBaseProperty {
|
||||
DBEnums type();
|
||||
String driver();
|
||||
String url();
|
||||
String username();
|
||||
String password();
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package cd.casic.plugin.register.database;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.pf4j.OpsPluginDescriptor;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import cd.casic.plugin.register.group.filter.impl.DataBaseEntityFilter;
|
||||
import cd.casic.plugin.utils.dBUtils.SQLGenerator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginDescriptor;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class DatabaseHandler implements BasePluginHandler {
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
DataBaseProperty dataBaseProperty;
|
||||
SQLGenerator sqlGenerator;
|
||||
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
|
||||
if(!(descriptor instanceof OpsPluginDescriptor)){
|
||||
return;
|
||||
}
|
||||
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
|
||||
if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){
|
||||
// 使用插件独立数据源
|
||||
AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext();
|
||||
try{
|
||||
dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class);
|
||||
}catch (Exception e){
|
||||
dataBaseProperty = null;
|
||||
log.info("插件未配置独立数据源");
|
||||
}
|
||||
} else {
|
||||
dataBaseProperty = null;
|
||||
}
|
||||
try {
|
||||
sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class);
|
||||
}catch (Exception e){
|
||||
log.error("无法获取SqlGenerator类型的Bean对象");
|
||||
return;
|
||||
}
|
||||
List<Class<?>> entities = plugin.getGroupClass(DataBaseEntityFilter.GROUP_NAME);
|
||||
for(Class<?> entity : entities){
|
||||
sqlGenerator.sqlDeleteGenerator(entity, dataBaseProperty, plugin.getPluginId());
|
||||
sqlGenerator.sqlGenerator(entity, dataBaseProperty, plugin.getPluginId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
DataBaseProperty dataBaseProperty;
|
||||
SQLGenerator sqlGenerator;
|
||||
PluginDescriptor descriptor = plugin.getPluginWrapper().getDescriptor();
|
||||
if(!(descriptor instanceof OpsPluginDescriptor)){
|
||||
return;
|
||||
}
|
||||
OpsPluginDescriptor opsPluginDescriptor = (OpsPluginDescriptor) descriptor;
|
||||
if(StrUtil.isNotEmpty(opsPluginDescriptor.getConfigFileName())){
|
||||
// 使用插件独立数据源
|
||||
AnnotationConfigApplicationContext pluginApplicationContext = plugin.getPluginApplicationContext();
|
||||
try{
|
||||
dataBaseProperty = pluginApplicationContext.getBean(DataBaseProperty.class);
|
||||
}catch (Exception e){
|
||||
log.info("插件未配置独立数据源");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sqlGenerator = plugin.getMainApplicationContext().getBean(SQLGenerator.class);
|
||||
}catch (Exception e){
|
||||
log.error("无法获取SqlGenerator类型的Bean对象");
|
||||
return;
|
||||
}
|
||||
sqlGenerator.destroyDataSource(dataBaseProperty, plugin.getPluginId());
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package cd.casic.plugin.register.group;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
public class AnnotationUtils {
|
||||
|
||||
@SafeVarargs
|
||||
public static boolean hasAnnotations(Class<?> clazz, boolean allRequired, Class<? extends Annotation>... annotations){
|
||||
if(clazz == null) return false;
|
||||
if(annotations == null) return false;
|
||||
for(Class<? extends Annotation> annotation : annotations){
|
||||
if(clazz.isAnnotationPresent(annotation)){
|
||||
if(!allRequired) return true;
|
||||
}else{
|
||||
if(allRequired) return false;
|
||||
}
|
||||
}
|
||||
return allRequired;
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
package cd.casic.plugin.register.group;
|
||||
|
||||
import cd.casic.plugin.BasePlugin;
|
||||
import cd.casic.plugin.PluginClassLoaderCache;
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import cd.casic.plugin.register.group.filter.impl.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 加载插件中所有的类,并且分组存放到pluginInfo的map中,便于后面的handler进行处理
|
||||
*/
|
||||
@Slf4j
|
||||
public class ClassGroupHandler implements BasePluginHandler {
|
||||
private final List<PluginClassFilter> classFilters = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
classFilters.add(new BasicBeanFilter());
|
||||
classFilters.add(new ControllerFilter());
|
||||
classFilters.add(new DataBaseEntityFilter());
|
||||
classFilters.add(new MapperFilter());
|
||||
classFilters.add(new PluginConfigurationFilter());
|
||||
classFilters.add(new WebSocketFilter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将类分组存到PluginInfo的map中,方便后面其他Handler进行处理
|
||||
* @param plugin PluginInfo
|
||||
*/
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
List<Class<?>> classList = new ArrayList<>();
|
||||
Set<String> classPackageName = scanClassPackageName(plugin.getBasePlugin().scanPackage(), plugin.getBasePlugin().getWrapper());
|
||||
for (String packageName : classPackageName) {
|
||||
ClassLoader loader = PluginClassLoaderCache.getPlugin(plugin.getPluginId());
|
||||
log.warn("Load class {} using classloader {} for plugin {}", packageName, PluginClassLoaderCache.getPlugin(plugin.getPluginId()), plugin.getPluginId());
|
||||
Class<?> clazz = loader.loadClass(packageName);
|
||||
if (!BasePlugin.class.isAssignableFrom(clazz)) {
|
||||
classList.add(clazz);
|
||||
}
|
||||
}
|
||||
plugin.setClassList(classList);
|
||||
/*这里分组其实只是为了分组而分组,目前没有特别明确的用途*/
|
||||
List<Class<?>> pluginClassList = plugin.getClassList().stream().filter(item -> !item.isInterface()).collect(Collectors.toList());
|
||||
if (!pluginClassList.isEmpty()) {
|
||||
for (Class<?> clazz : pluginClassList) {
|
||||
boolean grouped = false;
|
||||
for(PluginClassFilter filter : classFilters){
|
||||
if(filter.filter(clazz)){
|
||||
log.info("将类 {} 添加到 {} 分组", clazz, filter.groupName());
|
||||
plugin.addGroupClass(filter.groupName(), clazz);
|
||||
grouped = true;
|
||||
}
|
||||
}
|
||||
if(!grouped){
|
||||
log.info("类 {} 不属于任何已知分组", clazz);
|
||||
plugin.addGroupClass("unknown", clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载插件时,将插件的类列表清空
|
||||
* @param plugin PluginInfo
|
||||
*/
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
plugin.getClassList().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描插件所在包,获取插件中所有的类名
|
||||
* @param basePackage 插件所在包名
|
||||
* @param pluginWrapper 插件对象包装类
|
||||
* @return 所有的类名集合
|
||||
*/
|
||||
private Set<String> scanClassPackageName(String basePackage, PluginWrapper pluginWrapper) throws IOException {
|
||||
Path pluginPath = pluginWrapper.getPluginPath();
|
||||
if(pluginPath == null || !Files.exists(pluginPath)){
|
||||
throw new RuntimeException("错误的插件路径");
|
||||
}
|
||||
File pluginFile = pluginPath.toFile();
|
||||
Set<String> classPackageNames = new HashSet<>();
|
||||
|
||||
try (JarFile jar = new JarFile(pluginFile)) {
|
||||
Enumeration<JarEntry> jarEntries = jar.entries();
|
||||
while (jarEntries.hasMoreElements()) {
|
||||
JarEntry entry = jarEntries.nextElement();
|
||||
String jarEntryName = entry.getName();
|
||||
if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/", ".").startsWith(basePackage)) {
|
||||
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
|
||||
classPackageNames.add(className);
|
||||
}
|
||||
}
|
||||
}
|
||||
return classPackageNames;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package cd.casic.plugin.register.group.filter;
|
||||
|
||||
public interface PluginClassFilter{
|
||||
String groupName();
|
||||
|
||||
void initialize();
|
||||
|
||||
boolean filter(Class<?> clazz);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
import cd.casic.plugin.register.group.AnnotationUtils;
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
public class BasicBeanFilter implements PluginClassFilter {
|
||||
public static final String GROUP_NAME = "basic_bean";
|
||||
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
return AnnotationUtils.hasAnnotations(clazz, false, Bean.class,
|
||||
Service.class,
|
||||
Component.class,
|
||||
Configuration.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
import cd.casic.plugin.register.group.AnnotationUtils;
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Controller 过滤器,筛选带有Controller或者RestController注解的类
|
||||
*/
|
||||
public class ControllerFilter implements PluginClassFilter {
|
||||
public static final String GROUP_NAME = "spring_controller";
|
||||
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
return AnnotationUtils.hasAnnotations(clazz, false, Controller.class, RestController.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
|
||||
import cd.casic.plugin.register.group.AnnotationUtils;
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import cd.casic.plugin.utils.dBUtils.annotation.Entity;
|
||||
|
||||
public class DataBaseEntityFilter implements PluginClassFilter {
|
||||
public static final String GROUP_NAME = "database_entity";
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
return AnnotationUtils.hasAnnotations(clazz, false, Entity.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
public class MapperFilter implements PluginClassFilter {
|
||||
public static final String GROUP_NAME = "mapper";
|
||||
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
return clazz.isAnnotationPresent(Mapper.class) || clazz.isAnnotationPresent(Repository.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
|
||||
import cd.casic.plugin.BasePlugin;
|
||||
import cd.casic.plugin.annotation.PluginConfiguration;
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
|
||||
public class PluginConfigurationFilter implements PluginClassFilter {
|
||||
public static final String GROUP_NAME = "plugin_config";
|
||||
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
if(BasePlugin.class.isAssignableFrom(clazz)){
|
||||
return false;
|
||||
}
|
||||
|
||||
return clazz.isAnnotationPresent(PluginConfiguration.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package cd.casic.plugin.register.group.filter.impl;
|
||||
|
||||
|
||||
import cd.casic.plugin.register.group.filter.PluginClassFilter;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
|
||||
public class WebSocketFilter implements PluginClassFilter {
|
||||
|
||||
public static final String GROUP_NAME = "websocket";
|
||||
|
||||
@Override
|
||||
public String groupName() {
|
||||
return GROUP_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean filter(Class<?> clazz) {
|
||||
return clazz.isAnnotationPresent(ServerEndpoint.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package cd.casic.plugin.register.mongoplus;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import com.anwen.mongo.annotation.collection.CollectionName;
|
||||
import com.anwen.mongo.conn.CollectionManager;
|
||||
import com.anwen.mongo.conn.ConnectMongoDB;
|
||||
import com.anwen.mongo.convert.CollectionNameConvert;
|
||||
import com.anwen.mongo.manager.MongoPlusClient;
|
||||
import com.anwen.mongo.mapper.BaseMapper;
|
||||
import com.anwen.mongo.service.IService;
|
||||
import com.anwen.mongo.service.impl.ServiceImpl;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bson.Document;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class MongoHandler implements BasePluginHandler {
|
||||
private AnnotationConfigApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
log.info("Start to hand the Mongo Class for plugin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws Exception {
|
||||
try {
|
||||
applicationContext = plugin.getPluginApplicationContext();
|
||||
|
||||
// 注册AutoConfigurationPackages, 用于插件可自动配置
|
||||
// AutoConfigurationPackages.register(applicationContext.getDefaultListableBeanFactory(),
|
||||
// plugin.getBasePlugin().scanPackage());
|
||||
//
|
||||
// Set.of(MongoPlusConfiguration.class, MongoPlusAutoConfiguration.class, OverrideMongoConfiguration.class,
|
||||
// MongoSpringProperty.class, MongoDBFieldProperty.class, MongoTransactionManagerAutoConfiguration.class,
|
||||
// MongoPropertyConfiguration.class, )
|
||||
// ClassLoader pluginClassLoader = pluginRegistryInfo.getPluginClassLoader();
|
||||
// for (String autoConfigClassPackage : installAutoConfigClassString) {
|
||||
// Class<?> aClass = Class.forName(autoConfigClassPackage, false, pluginClassLoader);
|
||||
// autoConfigurationClassSet.add(aClass);
|
||||
// }
|
||||
// for (Class<?> autoConfigurationClass : autoConfigurationClassSet) {
|
||||
// pluginApplicationContext.registerBean(autoConfigurationClass);
|
||||
// }
|
||||
|
||||
applicationContext.getBeansOfType(IService.class)
|
||||
.values()
|
||||
.stream()
|
||||
.filter(s -> s instanceof ServiceImpl)
|
||||
.forEach(s -> {
|
||||
Class<?> clazz = s.getGenericityClass();
|
||||
ServiceImpl<?> serviceImpl = (ServiceImpl<?>) s;
|
||||
serviceImpl.setClazz(clazz);
|
||||
String database = initFactory(clazz);
|
||||
// 这里需要将MongoPlusClient给工厂
|
||||
serviceImpl.setDatabase(database);
|
||||
serviceImpl.setBaseMapper(applicationContext.getBean("baseMapper", BaseMapper.class));
|
||||
});
|
||||
}catch(BeansException e){
|
||||
log.info(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String initFactory(Class<?> clazz) {
|
||||
String collectionName = clazz.getSimpleName().toLowerCase();
|
||||
final String[] dataBaseName = {""};
|
||||
if (clazz.isAnnotationPresent(CollectionName.class)) {
|
||||
CollectionName annotation = clazz.getAnnotation(CollectionName.class);
|
||||
collectionName = annotation.value();
|
||||
dataBaseName[0] = annotation.database();
|
||||
}
|
||||
try {
|
||||
String finalCollectionName = collectionName;
|
||||
final String[] finalDataBaseName = {dataBaseName[0]};
|
||||
List<MongoDatabase> mongoDatabaseList = new ArrayList<>();
|
||||
MongoPlusClient mongoPlusClient = applicationContext.getBean("mongoPlusClient", MongoPlusClient.class);
|
||||
String database = mongoPlusClient.getBaseProperty().getDatabase();
|
||||
Arrays.stream(database.split(",")).collect(Collectors.toList()).forEach(db -> {
|
||||
CollectionNameConvert collectionNameConvert = applicationContext.getBean("collectionNameConvert", CollectionNameConvert.class);
|
||||
CollectionManager collectionManager = new CollectionManager(mongoPlusClient.getMongoClient(), collectionNameConvert, db);
|
||||
ConnectMongoDB connectMongodb = new ConnectMongoDB(mongoPlusClient.getMongoClient(), db, finalCollectionName);
|
||||
MongoDatabase mongoDatabase = mongoPlusClient.getMongoClient().getDatabase(db);
|
||||
mongoDatabaseList.add(mongoDatabase);
|
||||
if (Objects.equals(db, finalDataBaseName[0])) {
|
||||
MongoCollection<Document> collection = connectMongodb.open(mongoDatabase);
|
||||
collectionManager.setCollectionMap(finalCollectionName, collection);
|
||||
}
|
||||
mongoPlusClient.getCollectionManager().put(db, collectionManager);
|
||||
});
|
||||
mongoPlusClient.setMongoDatabase(mongoDatabaseList);
|
||||
} catch (MongoException e) {
|
||||
log.error("Failed to connect to MongoDB: {}", e.getMessage(), e);
|
||||
}
|
||||
return dataBaseName[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 不做操作,直接通过关闭PluginApplicationContext完成注销
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package cd.casic.plugin.register.mybatis;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.mybatis.spring.mapper.MapperFactoryBean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
|
||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||
import org.springframework.context.annotation.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MapperHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(MapperHandler.class);
|
||||
|
||||
private static final String MAPPER_INTERFACE_NAMES = "MybatisMapperInterfaceNames";
|
||||
|
||||
private final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
|
||||
|
||||
|
||||
public MapperHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理插件中的Mapper
|
||||
* @param pluginInfo 插件信息
|
||||
* @param processMapper Mapper的具体处理者
|
||||
*/
|
||||
public void processMapper(PluginInfo pluginInfo,
|
||||
ProcessMapper processMapper){
|
||||
AnnotationConfigApplicationContext applicationContext = pluginInfo.getPluginApplicationContext();
|
||||
// TODO 这里可以把类进行分组,就不用每次都扫mapper
|
||||
List<Class<?>> mapperClassList = new ArrayList<>();
|
||||
|
||||
for (Class<?> aClass : pluginInfo.getClassList()) {
|
||||
Mapper annotation = aClass.getAnnotation(Mapper.class);
|
||||
if (annotation != null) {
|
||||
mapperClassList.add(aClass);
|
||||
}
|
||||
}
|
||||
|
||||
String pluginId = pluginInfo.getPluginId();
|
||||
for (Class<?> mapperClass : mapperClassList) {
|
||||
if (mapperClass == null) {
|
||||
continue;
|
||||
}
|
||||
// BeanNameGenerator beanNameGenerator = new PluginAnnotationBeanNameGenerator(pluginId);
|
||||
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(mapperClass);
|
||||
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(abd);
|
||||
abd.setScope(scopeMetadata.getScopeName());
|
||||
// String beanName = beanNameGenerator.generateBeanName(abd, applicationContext);
|
||||
String beanName = abd.getBeanClassName();
|
||||
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
|
||||
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, applicationContext);
|
||||
try {
|
||||
processMapper.process(definitionHolder, mapperClass);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("process mapper '{}' error. {}", mapperClass.getName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 公共注册生成代理Mapper接口
|
||||
* @param holder ignore
|
||||
* @param mapperClass ignore
|
||||
* @param sqlSessionFactory ignore
|
||||
* @param sqlSessionTemplate ignore
|
||||
*/
|
||||
public void commonProcessMapper(BeanDefinitionHolder holder,
|
||||
Class<?> mapperClass,
|
||||
SqlSessionFactory sqlSessionFactory,
|
||||
SqlSessionTemplate sqlSessionTemplate) {
|
||||
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
|
||||
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClass);
|
||||
definition.setBeanClass(MapperFactoryBean.class);
|
||||
definition.getPropertyValues().add("addToConfig", true);
|
||||
definition.getPropertyValues().add("sqlSessionFactory", sqlSessionFactory);
|
||||
definition.getPropertyValues().add("sqlSessionTemplate", sqlSessionTemplate);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ProcessMapper{
|
||||
void process(BeanDefinitionHolder holder, Class<?> mapperClass) throws Exception;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package cd.casic.plugin.register.mybatis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Mybatis基础配置
|
||||
*/
|
||||
public interface MybatisCommonConfig {
|
||||
|
||||
/**
|
||||
* 数据库表对应的实体类的包名集合。可配置多个
|
||||
* @return Set
|
||||
*/
|
||||
Set<String> entityPackage();
|
||||
|
||||
/**
|
||||
* mybatis xml mapper 匹配规则 <br>
|
||||
* ? 匹配一个字符 <br>
|
||||
* * 匹配零个或多个字符 <br>
|
||||
* ** 匹配路径中的零或多个目录 <br>
|
||||
* 例如: <br>
|
||||
* 文件路径配置为 <p>file:D://xml/*PluginMapper.xml<p> <br>
|
||||
* resources路径配置为 <p>classpath:xml/mapper/*PluginMapper.xml<p> <br>
|
||||
* 包路径配置为 <p>package:com.plugin.xml.mapper.*PluginMapper.xml<p> <br>
|
||||
* @return Set
|
||||
*/
|
||||
Set<String> xmlLocationsMatch();
|
||||
|
||||
/**
|
||||
* 插件是否自主启用配置. 默认进行禁用, 使用主程序的配置
|
||||
* @return 返回true, 表示进行插件自主进行Mybatis相关配置
|
||||
*/
|
||||
default boolean enableOneselfConfig(){
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,123 @@
|
||||
package cd.casic.plugin.register.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.scripting.LanguageDriver;
|
||||
import org.apache.ibatis.scripting.LanguageDriverRegistry;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 插件跟随主程序时, 获取主程序的Mybatis定义的一些配置
|
||||
*/
|
||||
public class PluginFollowCoreConfig {
|
||||
|
||||
private final ApplicationContext mainApplicationContext;
|
||||
|
||||
public PluginFollowCoreConfig(ApplicationContext mainApplicationContext) {
|
||||
this.mainApplicationContext = mainApplicationContext;
|
||||
}
|
||||
|
||||
|
||||
public DataSource getDataSource(){
|
||||
return mainApplicationContext.getBean(DataSource.class);
|
||||
}
|
||||
|
||||
public MybatisConfiguration getMybatisPlusConfiguration(){
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
try {
|
||||
Map<String, com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer> customizerMap =
|
||||
mainApplicationContext.getBeansOfType(com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer.class);
|
||||
if(!customizerMap.isEmpty()){
|
||||
for (com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer customizer : customizerMap.values()) {
|
||||
customizer.customize(configuration);
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
// ignore
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public Interceptor[] getInterceptor(){
|
||||
Map<Class<? extends Interceptor>, Interceptor> interceptorMap = new HashMap<>();
|
||||
try {
|
||||
SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class);
|
||||
// 先从 SqlSessionFactory 工厂中获取拦截器
|
||||
List<Interceptor> interceptors = sqlSessionFactory.getConfiguration().getInterceptors();
|
||||
if(interceptors != null){
|
||||
for (Interceptor interceptor : interceptors) {
|
||||
if(interceptor == null){
|
||||
continue;
|
||||
}
|
||||
interceptorMap.put(interceptor.getClass(), interceptor);
|
||||
}
|
||||
}
|
||||
} catch (Exception e){
|
||||
// ignore
|
||||
}
|
||||
// 再从定义Bean中获取拦截器
|
||||
Map<String, Interceptor> beanInterceptorMap = mainApplicationContext.getBeansOfType(Interceptor.class);
|
||||
if(!beanInterceptorMap.isEmpty()){
|
||||
beanInterceptorMap.forEach((k, v)->{
|
||||
// 如果Class一致, 则会覆盖
|
||||
interceptorMap.put(v.getClass(), v);
|
||||
});
|
||||
}
|
||||
if(interceptorMap.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return interceptorMap.values().toArray(new Interceptor[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseIdProvider getDatabaseIdProvider(){
|
||||
String[] beanNamesForType = mainApplicationContext.getBeanNamesForType(DatabaseIdProvider.class, false, false);
|
||||
if(beanNamesForType.length > 0){
|
||||
return mainApplicationContext.getBean(DatabaseIdProvider.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public LanguageDriver[] getLanguageDriver(){
|
||||
Map<Class<? extends LanguageDriver>, LanguageDriver> languageDriverMap = new HashMap<>();
|
||||
try {
|
||||
SqlSessionFactory sqlSessionFactory = mainApplicationContext.getBean(SqlSessionFactory.class);
|
||||
LanguageDriverRegistry languageRegistry = sqlSessionFactory.getConfiguration()
|
||||
.getLanguageRegistry();
|
||||
// 先从 SqlSessionFactory 工厂中获取LanguageDriver
|
||||
Field proxyTypesField = ReflectionUtils.findField(languageRegistry.getClass(), "LANGUAGE_DRIVER_MAP");
|
||||
Map<Class<? extends LanguageDriver>, LanguageDriver> driverMap = null;
|
||||
if(proxyTypesField != null){
|
||||
proxyTypesField.setAccessible(true);
|
||||
driverMap = (Map<Class<? extends LanguageDriver>, LanguageDriver>) proxyTypesField.get(languageRegistry);
|
||||
}
|
||||
if(driverMap != null){
|
||||
languageDriverMap.putAll(driverMap);
|
||||
}
|
||||
} catch (Exception e){
|
||||
// ignore
|
||||
}
|
||||
Map<String, LanguageDriver> beansLanguageDriver = mainApplicationContext.getBeansOfType(LanguageDriver.class);
|
||||
if(!beansLanguageDriver.isEmpty()){
|
||||
beansLanguageDriver.forEach((k, v)->{
|
||||
// 如果Class一致, 则会覆盖
|
||||
languageDriverMap.put(v.getClass(), v);
|
||||
});
|
||||
}
|
||||
if(languageDriverMap.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
return languageDriverMap.values().toArray(new LanguageDriver[0]);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package cd.casic.plugin.register.mybatis;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.utils.ResourceUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 插件资源发现者
|
||||
* @author starBlues
|
||||
* @version 2.4.0
|
||||
*/
|
||||
public class PluginResourceFinder {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PluginResourceFinder.class);
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
private final ResourcePatternResolver resourcePatternResolver;
|
||||
private final MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
|
||||
|
||||
public PluginResourceFinder(PluginInfo pluginInfo) {
|
||||
this.classLoader = pluginInfo.getPluginWrapper().getPluginClassLoader();
|
||||
this.resourcePatternResolver = new PathMatchingResourcePatternResolver(classLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件中xml资源
|
||||
* @param xmlLocationsMatchSet xml资源匹配集合
|
||||
* @return xml Resource 数组
|
||||
* @throws IOException 获取xml资源异常
|
||||
*/
|
||||
public Resource[] getXmlResource(Set<String> xmlLocationsMatchSet) throws IOException {
|
||||
if(xmlLocationsMatchSet == null || xmlLocationsMatchSet.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
for (String xmlLocationsMatch : xmlLocationsMatchSet) {
|
||||
if(xmlLocationsMatchSet.isEmpty()){
|
||||
continue;
|
||||
}
|
||||
List<Resource> loadResources = getXmlResources(xmlLocationsMatch);
|
||||
if(loadResources != null && !loadResources.isEmpty()){
|
||||
resources.addAll(loadResources);
|
||||
}
|
||||
}
|
||||
|
||||
if(resources.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
|
||||
return resources.toArray(new Resource[0]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取插件的实体类及其别名
|
||||
* @param packagePatterns 实体类包名
|
||||
* @return class 数组
|
||||
* @throws IOException 获取医院异常
|
||||
*/
|
||||
public Class<?>[] getAliasesClasses(Set<String> packagePatterns) throws IOException {
|
||||
if(packagePatterns == null || packagePatterns.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
Set<Class<?>> aliasesClasses = new HashSet<>();
|
||||
for (String packagePattern : packagePatterns) {
|
||||
Resource[] resources = resourcePatternResolver.getResources(
|
||||
ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
|
||||
+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
|
||||
for (Resource resource : resources) {
|
||||
try {
|
||||
ClassMetadata classMetadata = metadataReaderFactory.getMetadataReader(resource).getClassMetadata();
|
||||
Class<?> clazz = classLoader.loadClass(classMetadata.getClassName());
|
||||
aliasesClasses.add(clazz);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.warn("Cannot load the '{}'. Cause by {}", resource, e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliasesClasses.toArray(new Class<?>[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Xml资源
|
||||
* @param mybatisMapperXmlLocationMatch mybatis xml 批量规则
|
||||
* @return 匹配到的xml资源
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
private List<Resource> getXmlResources(String mybatisMapperXmlLocationMatch) throws IOException {
|
||||
String matchLocation = ResourceUtils.getMatchLocation(mybatisMapperXmlLocationMatch);
|
||||
if(matchLocation == null){
|
||||
LOGGER.error("mybatisMapperXmlLocation {} illegal", mybatisMapperXmlLocationMatch);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Resource[] resources = resourcePatternResolver.getResources(matchLocation);
|
||||
if(resources.length > 0){
|
||||
return Arrays.asList(resources);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("mybatis xml resource '{}' match error : {}", mybatisMapperXmlLocationMatch,
|
||||
e.getMessage(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package cd.casic.plugin.register.mybatis.mybatisplus;
|
||||
|
||||
import cd.casic.plugin.register.mybatis.MybatisCommonConfig;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
|
||||
public interface MybatisPlusConfig extends MybatisCommonConfig {
|
||||
|
||||
/**
|
||||
* 插件自主配置Mybatis-Plus的MybatisSqlSessionFactoryBean
|
||||
* MybatisSqlSessionFactoryBean 具体配置说明参考 Mybatis-plus 官网
|
||||
* @param sqlSessionFactoryBean MybatisSqlSessionFactoryBean
|
||||
*/
|
||||
default void oneselfConfig(MybatisSqlSessionFactoryBean sqlSessionFactoryBean){
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写设置配置
|
||||
* 只有 enableOneselfConfig 返回 false, 实现该方法才生效
|
||||
* @param configuration 当前 MybatisConfiguration
|
||||
* @param globalConfig 当前全局配置GlobalConfig
|
||||
*/
|
||||
default void reSetMainConfig(MybatisConfiguration configuration, GlobalConfig globalConfig){
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package cd.casic.plugin.register.mybatis.mybatisplus;
|
||||
|
||||
import cd.casic.plugin.PluginInfo;
|
||||
import cd.casic.plugin.register.BasePluginHandler;
|
||||
import cd.casic.plugin.register.mybatis.MapperHandler;
|
||||
import cd.casic.plugin.register.mybatis.PluginFollowCoreConfig;
|
||||
import cd.casic.plugin.register.mybatis.PluginResourceFinder;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.config.GlobalConfig;
|
||||
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
|
||||
import org.apache.ibatis.io.Resources;
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.apache.ibatis.plugin.Interceptor;
|
||||
import org.apache.ibatis.scripting.LanguageDriver;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.mybatis.spring.SqlSessionTemplate;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
public class MybatisPlusHandler implements BasePluginHandler {
|
||||
@Override
|
||||
public void initialize() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registry(PluginInfo plugin) throws IOException {
|
||||
MybatisPlusConfig config = getObjectByInterfaceClass(plugin.getPluginConfigObjects(), MybatisPlusConfig.class);
|
||||
if(config == null) return;
|
||||
final MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
|
||||
|
||||
if(config.enableOneselfConfig()){
|
||||
config.oneselfConfig(factory);
|
||||
} else {
|
||||
PluginFollowCoreConfig followCoreConfig = new PluginFollowCoreConfig(
|
||||
plugin.getMainApplicationContext()
|
||||
);
|
||||
MybatisConfiguration mybatisPlusConfiguration = followCoreConfig.getMybatisPlusConfiguration();
|
||||
factory.setDataSource(followCoreConfig.getDataSource());
|
||||
factory.setConfiguration(mybatisPlusConfiguration);
|
||||
Interceptor[] interceptor = followCoreConfig.getInterceptor();
|
||||
if(interceptor != null && interceptor.length > 0){
|
||||
factory.setPlugins(interceptor);
|
||||
}
|
||||
DatabaseIdProvider databaseIdProvider = followCoreConfig.getDatabaseIdProvider();
|
||||
if(databaseIdProvider != null){
|
||||
factory.setDatabaseIdProvider(databaseIdProvider);
|
||||
}
|
||||
LanguageDriver[] languageDriver = followCoreConfig.getLanguageDriver();
|
||||
if(languageDriver != null){
|
||||
factory.setScriptingLanguageDrivers(languageDriver);
|
||||
}
|
||||
// 配置mybatis-plus私有的配置
|
||||
GlobalConfig globalConfig = mybatisPlusFollowCoreConfig(factory, plugin.getMainApplicationContext());
|
||||
config.reSetMainConfig(mybatisPlusConfiguration, globalConfig);
|
||||
}
|
||||
|
||||
PluginResourceFinder pluginResourceFinder = new PluginResourceFinder(plugin);
|
||||
|
||||
Class<?>[] aliasesClasses = pluginResourceFinder.getAliasesClasses(config.entityPackage());
|
||||
if(aliasesClasses != null && aliasesClasses.length > 0){
|
||||
factory.setTypeAliases(aliasesClasses);
|
||||
}
|
||||
|
||||
Resource[] xmlResource = pluginResourceFinder.getXmlResource(config.xmlLocationsMatch());
|
||||
if(xmlResource != null && xmlResource.length > 0){
|
||||
factory.setMapperLocations(xmlResource);
|
||||
}
|
||||
ClassLoader defaultClassLoader = Resources.getDefaultClassLoader();
|
||||
try {
|
||||
Resources.setDefaultClassLoader(plugin.getPluginWrapper().getPluginClassLoader());
|
||||
SqlSessionFactory sqlSessionFactory = factory.getObject();
|
||||
if(sqlSessionFactory == null){
|
||||
throw new Exception("Get mybatis-plus sqlSessionFactory is null");
|
||||
}
|
||||
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
|
||||
MapperHandler mapperHandler = new MapperHandler();
|
||||
mapperHandler.processMapper(plugin, (holder, mapperClass) ->
|
||||
mapperHandler.commonProcessMapper(holder, mapperClass, sqlSessionFactory, sqlSessionTemplate));
|
||||
DefaultListableBeanFactory beanFactory = plugin.getPluginApplicationContext().getDefaultListableBeanFactory();
|
||||
beanFactory.registerSingleton("sqlSessionFactory", sqlSessionFactory);
|
||||
beanFactory.registerSingleton("sqlSession", sqlSessionTemplate);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
Resources.setDefaultClassLoader(defaultClassLoader);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unRegistry(PluginInfo plugin) throws Exception {
|
||||
// 不做操作,直接通过关闭PluginApplicationContext完成注销。
|
||||
}
|
||||
|
||||
|
||||
private GlobalConfig mybatisPlusFollowCoreConfig(MybatisSqlSessionFactoryBean factory,
|
||||
ApplicationContext mainApplicationContext){
|
||||
MybatisPlusProperties plusProperties = mainApplicationContext.getBean(MybatisPlusProperties.class);
|
||||
|
||||
GlobalConfig currentGlobalConfig = new GlobalConfig();
|
||||
currentGlobalConfig.setBanner(false);
|
||||
GlobalConfig globalConfig = plusProperties.getGlobalConfig();
|
||||
if(globalConfig != null){
|
||||
currentGlobalConfig.setDbConfig(globalConfig.getDbConfig());
|
||||
currentGlobalConfig.setIdentifierGenerator(globalConfig.getIdentifierGenerator());
|
||||
currentGlobalConfig.setMetaObjectHandler(globalConfig.getMetaObjectHandler());
|
||||
currentGlobalConfig.setSqlInjector(globalConfig.getSqlInjector());
|
||||
}
|
||||
factory.setGlobalConfig(currentGlobalConfig);
|
||||
return currentGlobalConfig;
|
||||
}
|
||||
|
||||
// TODO 临时放这里,先跑通mybatis-plus集成测试
|
||||
public static <T> T getObjectByInterfaceClass(Set<Object> sourceObject, Class<T> interfaceClass){
|
||||
if(sourceObject == null || sourceObject.isEmpty()){
|
||||
return null;
|
||||
}
|
||||
for (Object configSingletonObject : sourceObject) {
|
||||
Set<Class<?>> allInterfacesForClassAsSet = ClassUtils
|
||||
.getAllInterfacesAsSet(configSingletonObject);
|
||||
if(allInterfacesForClassAsSet.contains(interfaceClass)){
|
||||
return (T) configSingletonObject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package cd.casic.plugin.register.websocket;
|
||||
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
|
||||
public interface BaseServerEndpoint extends DisposableBean {
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user