From a5fbf9bbaf8d47b78d664be3a51e309c7dd7835f Mon Sep 17 00:00:00 2001 From: ma Date: Mon, 2 Feb 2026 18:32:11 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E5=BD=93=E5=88=9D=E6=B2=A1=E6=83=B3=E8=83=BD=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E5=88=B0=E7=8E=B0=E5=9C=A8=E8=BF=99=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/native-image.yml | 21 +- pom.xml | 166 +- .../pushserver/PushServerApplication.java | 22 - .../aspect/SecurityInterceptor.java | 88 - .../pushserver/common/PortalResponse.java | 44 - .../pushserver/common/PortalSessionKeys.java | 9 - .../common/PortalSessionSupport.java | 41 - .../config/JsonDtoPackageHints.java | 61 - .../config/MyBatisNativeConfiguration.java | 336 - .../config/PortalAdminInitializer.java | 47 - .../config/PortalDataSourceProperties.java | 35 - .../config/PortalDatabaseConfig.java | 48 - .../config/PortalJacksonConfig.java | 14 - .../config/PortalMybatisConfig.java | 9 - .../config/PortalSchemaInitializer.java | 139 - .../config/PortalSecurityConfig.java | 154 - .../config/PortalWecomProperties.java | 17 - .../pushserver/config/PushConfiguration.java | 13 - .../pushserver/config/PushProperties.java | 135 - .../qingzhou/pushserver/config/WebConfig.java | 30 - .../controller/CaptchaController.java | 28 - .../controller/DashboardController.java | 47 - .../pushserver/controller/PageController.java | 19 - .../controller/PortalAppController.java | 165 - .../controller/PortalAuthController.java | 56 - .../controller/PortalCorpController.java | 49 - .../controller/PortalErrorController.java | 38 - .../controller/PortalInitController.java | 51 - .../controller/PortalMeController.java | 57 - .../controller/PortalMessageController.java | 77 - .../controller/PortalProxyController.java | 46 - .../controller/PortalSystemController.java | 41 - .../pushserver/controller/PushController.java | 55 - .../openapi/OpenApiMessageController.java | 69 - .../wecom/WecomCallbackController.java | 104 - .../exception/GlobalExceptionHandler.java | 31 - .../pushserver/exception/PortalException.java | 20 - .../exception/PortalExceptionHandler.java | 43 - .../pushserver/exception/PortalStatus.java | 22 - .../manager/wecom/AesException.java | 54 - .../manager/wecom/WXBizMsgCrypt.java | 448 - .../manager/wecom/WecomAgentInfo.java | 37 - .../manager/wecom/WecomApiClient.java | 172 - .../manager/wecom/WecomMessageParser.java | 68 - .../manager/wecom/WecomMessagePayload.java | 93 - .../manager/wecom/WecomResponse.java | 32 - .../manager/wecom/WecomSendResponse.java | 39 - .../pushserver/manager/wecom/WecomToken.java | 28 - .../mapper/portal/PortalAppApiKeyMapper.java | 9 - .../mapper/portal/PortalCorpConfigMapper.java | 9 - .../mapper/portal/PortalMessageLogMapper.java | 9 - .../portal/PortalProxyConfigMapper.java | 9 - .../portal/PortalSystemConfigMapper.java | 9 - .../mapper/portal/PortalUserMapper.java | 9 - .../mapper/portal/PortalWecomAppMapper.java | 9 - .../openapi/OpenApiMessageSendRequest.java | 142 - .../model/dto/openapi/PushRequest.java | 109 - .../portal/PortalAppApiKeyUpdateRequest.java | 17 - .../dto/portal/PortalAppCreateRequest.java | 28 - .../dto/portal/PortalAppUpdateRequest.java | 10 - .../dto/portal/PortalCorpConfigRequest.java | 17 - .../model/dto/portal/PortalInitRequest.java | 17 - .../model/dto/portal/PortalLoginRequest.java | 39 - .../dto/portal/PortalMessageSendRequest.java | 151 - .../model/dto/portal/PortalMessageType.java | 18 - .../portal/PortalPasswordUpdateRequest.java | 28 - .../dto/portal/PortalProxyConfigRequest.java | 29 - .../dto/portal/PortalRegisterRequest.java | 28 - .../model/entity/portal/PortalAppApiKey.java | 33 - .../model/entity/portal/PortalCorpConfig.java | 27 - .../model/entity/portal/PortalMessageLog.java | 58 - .../entity/portal/PortalProxyConfig.java | 39 - .../entity/portal/PortalSystemConfig.java | 24 - .../model/entity/portal/PortalUser.java | 26 - .../model/entity/portal/PortalWecomApp.java | 41 - .../vo/portal/DashboardChartsResponse.java | 83 - .../model/vo/portal/DashboardLogResponse.java | 50 - .../vo/portal/DashboardStatsResponse.java | 41 - .../vo/portal/PortalAppApiKeyResponse.java | 59 - .../model/vo/portal/PortalAppResponse.java | 68 - .../model/vo/portal/PortalCorpResponse.java | 14 - .../vo/portal/PortalMessageLogConverter.java | 28 - .../vo/portal/PortalMessageLogResponse.java | 131 - .../model/vo/portal/PortalPageResponse.java | 57 - .../vo/portal/PortalProxyConfigResponse.java | 36 - .../model/vo/portal/PortalUserResponse.java | 41 - .../pushserver/security/CaptchaService.java | 47 - .../security/PortalAppApiKeyRateLimiter.java | 56 - .../PortalJsonLoginAuthenticationFilter.java | 71 - .../security/PortalUserDetails.java | 61 - .../security/PortalUserDetailsService.java | 31 - .../pushserver/service/DashboardService.java | 15 - .../service/PortalAccessTokenService.java | 13 - .../service/PortalAppApiKeyService.java | 21 - .../service/PortalCorpConfigService.java | 13 - .../service/PortalMessageLogService.java | 13 - .../service/PortalMessageService.java | 9 - .../service/PortalProxyConfigService.java | 14 - .../pushserver/service/PortalUserService.java | 15 - .../service/PortalWecomAppService.java | 20 - .../pushserver/service/PushService.java | 9 - .../service/SystemConfigService.java | 14 - .../service/impl/DashboardServiceImpl.java | 243 - .../impl/PortalAccessTokenServiceImpl.java | 64 - .../impl/PortalAppApiKeyServiceImpl.java | 130 - .../impl/PortalCorpConfigServiceImpl.java | 53 - .../impl/PortalMessageLogServiceImpl.java | 61 - .../impl/PortalMessageServiceImpl.java | 238 - .../impl/PortalProxyConfigServiceImpl.java | 69 - .../service/impl/PortalUserServiceImpl.java | 85 - .../impl/PortalWecomAppServiceImpl.java | 143 - .../service/impl/PushServiceImpl.java | 105 - .../service/impl/SystemConfigServiceImpl.java | 77 - .../pushserver/utils/CasTokenBucket.java | 55 - .../native-image/reachability-metadata.json | 10892 ---------------- .../META-INF/native-image/reflect-config.json | 38 - src/main/resources/application-dev.yml | 16 - src/main/resources/application-prod.yml | 16 - src/main/resources/application.yml | 24 - .../PushServerApplicationTests.java | 13 - 120 files changed, 36 insertions(+), 17678 deletions(-) delete mode 100644 src/main/java/dev/qingzhou/pushserver/PushServerApplication.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/PushProperties.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/config/WebConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PageController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/PushController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/exception/PortalException.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/DashboardService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/PushService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java delete mode 100644 src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java delete mode 100644 src/main/resources/META-INF/native-image/reachability-metadata.json delete mode 100644 src/main/resources/META-INF/native-image/reflect-config.json delete mode 100644 src/main/resources/application-dev.yml delete mode 100644 src/main/resources/application-prod.yml delete mode 100644 src/main/resources/application.yml delete mode 100644 src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java diff --git a/.github/workflows/native-image.yml b/.github/workflows/native-image.yml index 7a62475..55d5e2b 100644 --- a/.github/workflows/native-image.yml +++ b/.github/workflows/native-image.yml @@ -65,8 +65,8 @@ jobs: npm install npm run build cd .. - mkdir -p src/main/resources/static - cp -r push-server-web/dist/* src/main/resources/static/ + mkdir -p push-server-core/src/main/resources/static + cp -r push-server-web/dist/* push-server-core/src/main/resources/static/ - name: Run tests shell: bash @@ -74,12 +74,13 @@ jobs: - name: Build native image shell: bash - run: ${{ env.MVN_CMD }} -B -Pnative -DskipTests native:compile + run: ${{ env.MVN_CMD }} -B -pl push-server-core -am -Pnative -DskipTests native:compile - name: Rename binary shell: bash run: | - src="target/push-server${{ matrix.ext }}" + mkdir -p target + src="push-server-core/target/push-server-core${{ matrix.ext }}" dest="target/push-server-${{ matrix.artifact }}${{ matrix.ext }}" if [ -f "$src" ]; then mv "$src" "$dest" @@ -143,18 +144,18 @@ jobs: npm install npm run build cd .. - mkdir -p src/main/resources/static - cp -r push-server-web/dist/* src/main/resources/static/ + mkdir -p push-server-core/src/main/resources/static + cp -r push-server-web/dist/* push-server-core/src/main/resources/static/ - name: Build native image shell: bash - run: ${{ env.MVN_CMD }} -B -Pnative -DskipTests native:compile + run: ${{ env.MVN_CMD }} -B -pl push-server-core -am -Pnative -DskipTests native:compile - name: Upload binary uses: actions/upload-artifact@v4 with: name: push-server-${{ matrix.arch }} - path: target/push-server + path: push-server-core/target/push-server-core if-no-files-found: error docker-push: @@ -180,8 +181,8 @@ jobs: - name: Prepare binaries run: | mkdir -p target - cp bin-amd64/push-server target/push-server-amd64 - cp bin-arm64/push-server target/push-server-arm64 + cp bin-amd64/push-server-core target/push-server-amd64 + cp bin-arm64/push-server-core target/push-server-arm64 ls -R target - name: Set up Docker Buildx diff --git a/pom.xml b/pom.xml index c532caa..48d779c 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ dev.qingzhou push-server 0.1.3 + pom push-server push-server @@ -37,148 +38,31 @@ UTF-8 prod - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-webmvc - - - org.springframework.boot - spring-boot-starter-jdbc - - - com.baomidou - mybatis-plus-spring-boot4-starter - 3.5.15 - - - org.xerial - sqlite-jdbc - 3.46.0.0 - - - org.springframework.security - spring-security-crypto - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-validation-test - test - - - org.springframework.boot - spring-boot-starter-webmvc-test - test - - - - com.github.ben-manes.caffeine - caffeine - - - - dev.qingzhou - push-core - 1.0.0 - - - org.projectlombok - lombok - provided - - - - - - - src/main/resources - true - - application.yml - application.properties - **/*.json - **/*.xml - - - - - src/main/resources - false - - static/** - templates/** - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - org.projectlombok - lombok - - - org.springframework.boot - spring-boot-configuration-processor - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.graalvm.buildtools - native-maven-plugin - - dev.qingzhou.pushserver.PushServerApplication - false - - -Djava.specification.version=25 - -march=compatibility - --initialize-at-build-time=org.sqlite.util.ProcessRunner - --initialize-at-build-time=org.sqlite.util.OSInfo - --initialize-at-run-time=org.apache.ibatis - - --initialize-at-run-time=org.mybatis - --initialize-at-run-time=org.apache.ibatis.logging - --initialize-at-run-time=org.apache.ibatis.logging.LogFactory - - - - - org.apache.maven.plugins - maven-resources-plugin - - - @ - - false - - - - + + push-server-api + push-server-core + + + + + com.baomidou + mybatis-plus-spring-boot4-starter + 3.5.15 + + + org.xerial + sqlite-jdbc + 3.46.0.0 + + + dev.qingzhou + push-core + 1.0.0 + + + @@ -203,4 +87,4 @@ - + \ No newline at end of file diff --git a/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java b/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java deleted file mode 100644 index e7997de..0000000 --- a/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.qingzhou.pushserver; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -import java.io.File; - -@SpringBootApplication -public class PushServerApplication { - - public static void main(String[] args) { - // 允许 HTTP 代理进行 Basic 认证(解决 HTTPS 隧道建立时的 407 错误) - System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); - System.setProperty("jdk.http.auth.proxying.disabledSchemes", ""); - - if (System.getProperty("java.home") == null) { - System.setProperty("java.home", new File(".").getAbsolutePath()); - } - SpringApplication.run(PushServerApplication.class, args); - } - -} diff --git a/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java b/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java deleted file mode 100644 index c20f3fd..0000000 --- a/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.qingzhou.pushserver.aspect; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import dev.qingzhou.pushserver.config.PushProperties; -import dev.qingzhou.pushserver.utils.CasTokenBucket; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.HandlerInterceptor; - -import java.util.concurrent.TimeUnit; - -@Component -public class SecurityInterceptor implements HandlerInterceptor { - - private static final Logger log = LoggerFactory.getLogger(SecurityInterceptor.class); - - private final PushProperties properties; - private final int maxFails; - - // 1. 封禁名单缓存:Key=IP, Value=封禁截止时间戳 - private final Cache blockList; - - // 2. 失败计数缓存:Key=IP, Value=失败次数 - private final Cache failCounts; - - private final CasTokenBucket casTokenBucket; - - public SecurityInterceptor(PushProperties properties) { - this.properties = properties; - PushProperties.Security security = properties.getSecurity(); - this.blockList = Caffeine.newBuilder() - .expireAfterWrite(security.getBlockMinutes(), TimeUnit.MINUTES) - .build(); - this.failCounts = Caffeine.newBuilder() - .expireAfterWrite(security.getFailWindowMinutes(), TimeUnit.MINUTES) - .build(); - this.maxFails = security.getMaxFails(); - this.casTokenBucket = new CasTokenBucket(security.getRateLimitCapacity(), security.getRateLimitQps()); - } - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - String clientIp = getClientIp(request); - - // --- 1. 检查是否在小黑屋,或者是否大量无意义请求,都需要限制 --- - if (!casTokenBucket.tryAcquire() || blockList.getIfPresent(clientIp) != null) { - log.warn("Blocked request from IP: {}", clientIp); - response.setStatus(429); // Too Many Requests - response.getWriter().write("Too Many Requests."); - return false; - } - - // --- 2. 执行鉴权逻辑 --- - String apiKey = request.getHeader("X-API-Key"); - if (!isAuthorized(apiKey)) { - // 鉴权失败 -> 计数器 +1 - Integer count = failCounts.get(clientIp, k -> 0); - failCounts.put(clientIp, count + 1); - log.warn("Auth failed for IP: {}, count: {}", clientIp, count + 1); - if (count + 1 >= maxFails) { - blockList.put(clientIp, System.currentTimeMillis()); - log.error("IP {} has been blocked due to brute force attempts.", clientIp); - } - - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.getWriter().write("Unauthorized"); - return false; - } - return true; - } - - private boolean isAuthorized(String apiKey) { - return StringUtils.hasText(apiKey) && apiKey.equals(properties.getAuth().getKey()); - } - - // 获取真实IP(如果前面有Nginx,需要取 X-Forwarded-For) - private String getClientIp(HttpServletRequest request) { - String xfHeader = request.getHeader("X-Forwarded-For"); - if (xfHeader == null) { - return request.getRemoteAddr(); - } - return xfHeader.split(",")[0].trim(); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java b/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java deleted file mode 100644 index e88cd07..0000000 --- a/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.qingzhou.pushserver.common; - -public class PortalResponse { - - private boolean success; - private String message; - private T data; - - public static PortalResponse ok(T data) { - PortalResponse response = new PortalResponse<>(); - response.success = true; - response.message = "成功"; - response.data = data; - return response; - } - - public static PortalResponse ok(String message, T data) { - PortalResponse response = new PortalResponse<>(); - response.success = true; - response.message = message; - response.data = data; - return response; - } - - public static PortalResponse fail(String message) { - PortalResponse response = new PortalResponse<>(); - response.success = false; - response.message = message; - response.data = null; - return response; - } - - public boolean isSuccess() { - return success; - } - - public String getMessage() { - return message; - } - - public T getData() { - return data; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java b/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java deleted file mode 100644 index 422ca21..0000000 --- a/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.common; - -public final class PortalSessionKeys { - - public static final String USER_ID = "portal_user_id"; - - private PortalSessionKeys() { - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java b/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java deleted file mode 100644 index d2908ee..0000000 --- a/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.qingzhou.pushserver.common; - -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.security.PortalUserDetails; -import jakarta.servlet.http.HttpSession; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -public final class PortalSessionSupport { - - private PortalSessionSupport() { - } - - public static Long requireUserId(HttpSession session) { - if (session != null) { - Object value = session.getAttribute(PortalSessionKeys.USER_ID); - if (value instanceof Long userId) { - return userId; - } - } - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication.isAuthenticated()) { - Object principal = authentication.getPrincipal(); - if (principal instanceof PortalUserDetails userDetails) { - Long userId = userDetails.getUserId(); - if (session != null) { - session.setAttribute(PortalSessionKeys.USER_ID, userId); - } - return userId; - } - if (principal instanceof Long userId) { - if (session != null) { - session.setAttribute(PortalSessionKeys.USER_ID, userId); - } - return userId; - } - } - throw new PortalException(PortalStatus.UNAUTHORIZED, "未授权"); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java b/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java deleted file mode 100644 index 75bfc42..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java +++ /dev/null @@ -1,61 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.type.filter.TypeFilter; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.util.ClassUtils; - -@Configuration(proxyBeanMethods = false) -@ImportRuntimeHints(JsonDtoPackageHints.DtoHints.class) -public class JsonDtoPackageHints { - - static class DtoHints implements RuntimeHintsRegistrar { - - // 你要批量注册的 DTO 包(可加多个) - private static final String[] DTO_PACKAGES = { - "dev.qingzhou.pushserver.model.dto", - "dev.qingzhou.pushserver.model.dto.portal" - }; - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - // 不使用默认过滤器(默认只扫 @Component 那些) - ClassPathScanningCandidateComponentProvider scanner = - new ClassPathScanningCandidateComponentProvider(false); - - // 放开:所有“独立类”(顶级类/静态内部类)都进来 - TypeFilter includeAllIndependent = (metadataReader, metadataReaderFactory) -> - metadataReader.getClassMetadata().isIndependent(); - - scanner.addIncludeFilter(includeAllIndependent); - - for (String basePackage : DTO_PACKAGES) { - for (var bd : scanner.findCandidateComponents(basePackage)) { - String className = bd.getBeanClassName(); - if (className == null) continue; - - // 过滤一些你不想注册的(可按需调整) - if (className.endsWith("package-info")) continue; - - try { - Class clazz = ClassUtils.forName(className, classLoader); - - // Jackson 绑定常用:构造器/方法/字段 - hints.reflection().registerType( - clazz, - MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, - MemberCategory.INVOKE_PUBLIC_METHODS - ); - } catch (Throwable ignored) { - // 某些类可能因为缺依赖加载失败:这里忽略,避免打包中断 - // 你也可以改成打印日志帮助定位 - } - } - } - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java b/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java deleted file mode 100644 index 0ca0a98..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java +++ /dev/null @@ -1,336 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import com.baomidou.mybatisplus.annotation.IEnum; -import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; -import com.baomidou.mybatisplus.core.MybatisParameterHandler; -import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver; -import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler; -import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler; -import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; -import com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler; -import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; -import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; -import org.apache.commons.logging.LogFactory; -import org.apache.ibatis.annotations.DeleteProvider; -import org.apache.ibatis.annotations.InsertProvider; -import org.apache.ibatis.annotations.SelectProvider; -import org.apache.ibatis.annotations.UpdateProvider; -import org.apache.ibatis.cache.decorators.FifoCache; -import org.apache.ibatis.cache.decorators.LruCache; -import org.apache.ibatis.cache.decorators.SoftCache; -import org.apache.ibatis.cache.decorators.WeakCache; -import org.apache.ibatis.cache.impl.PerpetualCache; -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.executor.parameter.ParameterHandler; -import org.apache.ibatis.executor.resultset.ResultSetHandler; -import org.apache.ibatis.executor.statement.BaseStatementHandler; -import org.apache.ibatis.executor.statement.RoutingStatementHandler; -import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.javassist.util.proxy.ProxyFactory; -import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; -import org.apache.ibatis.logging.Log; -import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; -import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; -import org.apache.ibatis.logging.log4j2.Log4j2Impl; -import org.apache.ibatis.logging.nologging.NoLoggingImpl; -import org.apache.ibatis.logging.slf4j.Slf4jImpl; -import org.apache.ibatis.logging.stdout.StdOutImpl; -import org.apache.ibatis.mapping.BoundSql; -import org.apache.ibatis.reflection.TypeParameterResolver; -import org.apache.ibatis.scripting.defaults.RawLanguageDriver; -import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; -import org.apache.ibatis.session.SqlSessionFactory; -import org.mybatis.spring.SqlSessionFactoryBean; -import org.mybatis.spring.mapper.MapperFactoryBean; -import org.mybatis.spring.mapper.MapperScannerConfigurer; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.ImportRuntimeHints; -import org.springframework.core.ResolvableType; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - -/** - * This configuration will move to mybatis-spring-native. - */ -@Configuration(proxyBeanMethods = false) -@ImportRuntimeHints(MyBatisNativeConfiguration.MyBaitsRuntimeHintsRegistrar.class) -public class MyBatisNativeConfiguration { - - @Bean - MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() { - return new MyBatisBeanFactoryInitializationAotProcessor(); - } - - @Bean - static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() { - return new MyBatisMapperFactoryBeanPostProcessor(); - } - - static class MyBaitsRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - - // 你要批量注册的 DTO 包(可加多个) - private static final String[] DTO_PACKAGES = { - "dev.qingzhou.pushserver.model.dto", - "dev.qingzhou.pushserver.model.dto.portal" - }; - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - Stream.of(RawLanguageDriver.class, - // TODO 增加了MybatisXMLLanguageDriver.class - XMLLanguageDriver.class, MybatisXMLLanguageDriver.class, - RuntimeSupport.class, - ProxyFactory.class, - Slf4jImpl.class, - Log.class, - JakartaCommonsLoggingImpl.class, - Log4j2Impl.class, - Jdk14LoggingImpl.class, - StdOutImpl.class, - NoLoggingImpl.class, - SqlSessionFactory.class, - PerpetualCache.class, - FifoCache.class, - LruCache.class, - SoftCache.class, - WeakCache.class, - //TODO 增加了MybatisSqlSessionFactoryBean.class - SqlSessionFactoryBean.class, MybatisSqlSessionFactoryBean.class, - ArrayList.class, - HashMap.class, - TreeSet.class, - HashSet.class - ).forEach(x -> hints.reflection().registerType(x, MemberCategory.values())); - Stream.of( - "org/apache/ibatis/builder/xml/*.dtd", - "org/apache/ibatis/builder/xml/*.xsd" - ).forEach(hints.resources()::registerPattern); - - hints.serialization().registerType(java.lang.invoke.SerializedLambda.class); - hints.reflection().registerType(java.lang.invoke.SerializedLambda.class); - - hints.proxies().registerJdkProxy(StatementHandler.class); - hints.proxies().registerJdkProxy(Executor.class); - hints.proxies().registerJdkProxy(ResultSetHandler.class); - hints.proxies().registerJdkProxy(ParameterHandler.class); - -// hints.reflection().registerType(MybatisPlusInterceptor.class); - hints.reflection().registerType(AbstractWrapper.class,MemberCategory.values()); - hints.reflection().registerType(UpdateWrapper.class,MemberCategory.values()); - hints.reflection().registerType(QueryWrapper.class,MemberCategory.values()); - - hints.reflection().registerType(BoundSql.class,MemberCategory.DECLARED_FIELDS); - hints.reflection().registerType(RoutingStatementHandler.class,MemberCategory.DECLARED_FIELDS); - hints.reflection().registerType(BaseStatementHandler.class,MemberCategory.DECLARED_FIELDS); - hints.reflection().registerType(MybatisParameterHandler.class,MemberCategory.DECLARED_FIELDS); - - - hints.reflection().registerType(IEnum.class,MemberCategory.INVOKE_PUBLIC_METHODS); - // register typeHandler - hints.reflection().registerType(CompositeEnumTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - hints.reflection().registerType(FastjsonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - hints.reflection().registerType(GsonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - hints.reflection().registerType(JacksonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - hints.reflection().registerType(MybatisEnumTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); - - } - } - - static class MyBatisBeanFactoryInitializationAotProcessor - implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter { - - private final Set> excludeClasses = new HashSet<>(); - - MyBatisBeanFactoryInitializationAotProcessor() { - excludeClasses.add(MapperScannerConfigurer.class); - } - - @Override public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { - return excludeClasses.contains(registeredBean.getBeanClass()); - } - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - String[] beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class); - if (beanNames.length == 0) { - return null; - } - return (context, code) -> { - RuntimeHints hints = context.getRuntimeHints(); - for (String beanName : beanNames) { - BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1)); - PropertyValue mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface"); - if (mapperInterface != null && mapperInterface.getValue() != null) { - Class mapperInterfaceType = (Class) mapperInterface.getValue(); - if (mapperInterfaceType != null) { - registerReflectionTypeIfNecessary(mapperInterfaceType, hints); - hints.proxies().registerJdkProxy(mapperInterfaceType); - hints.resources() - .registerPattern(mapperInterfaceType.getName().replace('.', '/').concat(".xml")); - registerMapperRelationships(mapperInterfaceType, hints); - } - } - } - }; - } - - private void registerMapperRelationships(Class mapperInterfaceType, RuntimeHints hints) { - Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType); - for (Method method : methods) { - if (method.getDeclaringClass() != Object.class) { - ReflectionUtils.makeAccessible(method); - registerSqlProviderTypes(method, hints, SelectProvider.class, SelectProvider::value, SelectProvider::type); - registerSqlProviderTypes(method, hints, InsertProvider.class, InsertProvider::value, InsertProvider::type); - registerSqlProviderTypes(method, hints, UpdateProvider.class, UpdateProvider::value, UpdateProvider::type); - registerSqlProviderTypes(method, hints, DeleteProvider.class, DeleteProvider::value, DeleteProvider::type); - Class returnType = MyBatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method); - registerReflectionTypeIfNecessary(returnType, hints); - MyBatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method) - .forEach(x -> registerReflectionTypeIfNecessary(x, hints)); - } - } - } - - @SafeVarargs - private void registerSqlProviderTypes( - Method method, RuntimeHints hints, Class annotationType, Function>... providerTypeResolvers) { - for (T annotation : method.getAnnotationsByType(annotationType)) { - for (Function> providerTypeResolver : providerTypeResolvers) { - registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), hints); - } - } - } - - private void registerReflectionTypeIfNecessary(Class type, RuntimeHints hints) { - if (!type.isPrimitive() && !type.getName().startsWith("java")) { - hints.reflection().registerType(type, MemberCategory.values()); - } - } - - } - - static class MyBatisMapperTypeUtils { - private MyBatisMapperTypeUtils() { - // NOP - } - - static Class resolveReturnClass(Class mapperInterface, Method method) { - Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); - return typeToClass(resolvedReturnType, method.getReturnType()); - } - - static Set> resolveParameterClasses(Class mapperInterface, Method method) { - return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface)) - .map(x -> typeToClass(x, x instanceof Class ? (Class) x : Object.class)).collect(Collectors.toSet()); - } - - private static Class typeToClass(Type src, Class fallback) { - Class result = null; - if (src instanceof Class) { - if (((Class) src).isArray()) { - result = ((Class) src).getComponentType(); - } else { - result = (Class) src; - } - } else if (src instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) src; - int index = (parameterizedType.getRawType() instanceof Class - && Map.class.isAssignableFrom((Class) parameterizedType.getRawType()) - && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0; - Type actualType = parameterizedType.getActualTypeArguments()[index]; - result = typeToClass(actualType, fallback); - } - if (result == null) { - result = fallback; - } - return result; - } - - } - - static class MyBatisMapperFactoryBeanPostProcessor implements MergedBeanDefinitionPostProcessor, BeanFactoryAware { - - private static final org.apache.commons.logging.Log LOG = LogFactory.getLog( - MyBatisMapperFactoryBeanPostProcessor.class); - - private static final String MAPPER_FACTORY_BEAN = "org.mybatis.spring.mapper.MapperFactoryBean"; - - private ConfigurableBeanFactory beanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = (ConfigurableBeanFactory) beanFactory; - } - - @Override - public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { - if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) { - resolveMapperFactoryBeanTypeIfNecessary(beanDefinition); - } - } - - private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) { - if (!beanDefinition.hasBeanClass() || !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) { - return; - } - if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) { - Class mapperInterface = getMapperInterface(beanDefinition); - if (mapperInterface != null) { - // Exposes a generic type information to context for prevent early initializing - ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); - constructorArgumentValues.addGenericArgumentValue(mapperInterface); - beanDefinition.setConstructorArgumentValues(constructorArgumentValues); - beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface)); - } - } - } - - private Class getMapperInterface(RootBeanDefinition beanDefinition) { - try { - return (Class) beanDefinition.getPropertyValues().get("mapperInterface"); - } - catch (Exception e) { - LOG.debug("Fail getting mapper interface type.", e); - return null; - } - } - - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java b/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java deleted file mode 100644 index a90e96c..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import dev.qingzhou.pushserver.service.PortalUserService; -import java.security.SecureRandom; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; - -//@Component -public class PortalAdminInitializer implements ApplicationRunner { - - private static final Logger log = LoggerFactory.getLogger(PortalAdminInitializer.class); - - private static final String ADMIN_ACCOUNT = "admin"; - private static final String PASSWORD_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; - private static final int PASSWORD_LENGTH = 12; - - private final PortalUserService portalUserService; - private final SecureRandom random = new SecureRandom(); - - public PortalAdminInitializer(PortalUserService portalUserService) { - this.portalUserService = portalUserService; - } - - @Override - public void run(ApplicationArguments args) { - PortalUser existing = portalUserService.findByAccount(ADMIN_ACCOUNT); - if (existing != null) { - return; - } - String password = generatePassword(); - portalUserService.register(ADMIN_ACCOUNT, password); - log.info("Initialized admin user: account={}, password={}", ADMIN_ACCOUNT, password); - } - - private String generatePassword() { - StringBuilder builder = new StringBuilder(PASSWORD_LENGTH); - for (int i = 0; i < PASSWORD_LENGTH; i++) { - int index = random.nextInt(PASSWORD_CHARS.length()); - builder.append(PASSWORD_CHARS.charAt(index)); - } - return builder.toString(); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java b/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java deleted file mode 100644 index 1d84136..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "push.portal.datasource") -public class PortalDataSourceProperties { - - private String url; - private String filePath = "./data/push-server-portal.db"; - private Integer maxPoolSize = 5; - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public Integer getMaxPoolSize() { - return maxPoolSize; - } - - public void setMaxPoolSize(Integer maxPoolSize) { - this.maxPoolSize = maxPoolSize; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java b/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java deleted file mode 100644 index 74560f4..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; -import com.zaxxer.hikari.HikariDataSource; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import javax.sql.DataSource; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.springframework.util.StringUtils; - -@Configuration -@EnableTransactionManagement -public class PortalDatabaseConfig { - - @Bean - public DataSource dataSource(PortalDataSourceProperties properties) { - String jdbcUrl = properties.getUrl(); - if (!StringUtils.hasText(jdbcUrl)) { - jdbcUrl = buildSqliteUrl(properties.getFilePath()); - } - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setJdbcUrl(jdbcUrl); - dataSource.setDriverClassName("org.sqlite.JDBC"); - dataSource.setMaximumPoolSize(properties.getMaxPoolSize()); - return dataSource; - } - - @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() { - return new MybatisPlusInterceptor(); - } - - private String buildSqliteUrl(String filePath) { - Path path = Paths.get(filePath).toAbsolutePath(); - Path parent = path.getParent(); - if (parent != null) { - try { - Files.createDirectories(parent); - } catch (Exception ex) { - throw new IllegalStateException("Failed to create sqlite directory: " + parent, ex); - } - } - return "jdbc:sqlite:" + path; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java b/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java deleted file mode 100644 index 4fa6a95..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class PortalJacksonConfig { - - @Bean - public ObjectMapper objectMapper() { - return new ObjectMapper(); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java b/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java deleted file mode 100644 index 2c96afa..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import org.mybatis.spring.annotation.MapperScan; -import org.springframework.context.annotation.Configuration; - -@Configuration -@MapperScan(basePackages = "dev.qingzhou.pushserver.mapper.portal", sqlSessionFactoryRef = "sqlSessionFactory") -public class PortalMybatisConfig { -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java b/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java deleted file mode 100644 index 4f9f586..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java +++ /dev/null @@ -1,139 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import jakarta.annotation.PostConstruct; -import java.sql.Connection; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import javax.sql.DataSource; -import org.springframework.stereotype.Component; - -@Component -public class PortalSchemaInitializer { - - private final DataSource dataSource; - - public PortalSchemaInitializer(DataSource dataSource) { - this.dataSource = dataSource; - } - - @PostConstruct - public void initialize() { - List statements = new ArrayList<>(); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_system_config ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - config_key TEXT NOT NULL UNIQUE, - config_value TEXT, - updated_at INTEGER NOT NULL - ) - """); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_user ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - account TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - """); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_corp_config ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL UNIQUE, - corp_id TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - """); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_wecom_app ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - agent_id TEXT NOT NULL, - secret TEXT NOT NULL, - token TEXT, - encoding_aes_key TEXT, - name TEXT, - avatar_url TEXT, - description TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE(user_id, agent_id) - ) - """); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_app_api_key ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - app_id INTEGER NOT NULL UNIQUE, - api_key_hash TEXT NOT NULL, - api_key_plain TEXT NOT NULL, - rate_limit_per_minute INTEGER NOT NULL DEFAULT 0, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - """); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_proxy_config ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL UNIQUE, - host TEXT NOT NULL, - port INTEGER NOT NULL, - username TEXT, - password TEXT, - type TEXT NOT NULL DEFAULT 'HTTP', - exit_ip TEXT, - active INTEGER NOT NULL DEFAULT 1, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ) - """); - statements.add(""" - CREATE UNIQUE INDEX IF NOT EXISTS idx_v2_app_api_key_hash - ON v2_app_api_key(api_key_hash) - """); - List alterStatements = new ArrayList<>(); - alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN api_key_plain TEXT NOT NULL DEFAULT ''"); - alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN rate_limit_per_minute INTEGER NOT NULL DEFAULT 0"); - alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN token TEXT"); - alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN encoding_aes_key TEXT"); - statements.add(""" - CREATE TABLE IF NOT EXISTS v2_message_log ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - app_id INTEGER NOT NULL, - agent_id TEXT NOT NULL, - msg_type TEXT NOT NULL, - to_user TEXT, - to_party TEXT, - to_all INTEGER NOT NULL DEFAULT 0, - title TEXT, - description TEXT, - url TEXT, - content TEXT, - request_json TEXT, - response_json TEXT, - success INTEGER NOT NULL, - error_message TEXT, - created_at INTEGER NOT NULL - ) - """); - - try (Connection connection = dataSource.getConnection()) { - try (Statement statement = connection.createStatement()) { - for (String sql : statements) { - statement.execute(sql); - } - for (String sql : alterStatements) { - try { - statement.execute(sql); - } catch (Exception ignored) { - // Column may already exist; ignore migration errors to stay backward compatible. - } - } - } - } catch (Exception ex) { - throw new IllegalStateException("Failed to initialize portal schema", ex); - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java b/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java deleted file mode 100644 index 33b8a7c..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java +++ /dev/null @@ -1,154 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.qingzhou.pushserver.common.PortalSessionKeys; -import dev.qingzhou.pushserver.security.CaptchaService; -import dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter; -import dev.qingzhou.pushserver.security.PortalUserDetails; -import dev.qingzhou.pushserver.security.PortalUserDetailsService; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; - -import java.io.PrintWriter; -import java.util.Map; - -@Configuration -public class PortalSecurityConfig { - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - public AuthenticationManager authenticationManager( - PortalUserDetailsService userDetailsService, - PasswordEncoder passwordEncoder - ) { - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); - provider.setPasswordEncoder(passwordEncoder); - return new ProviderManager(provider); - } - - - @Bean - public SessionAuthenticationStrategy sessionAuthenticationStrategy() { - return new ChangeSessionIdAuthenticationStrategy(); - } - - @Bean - public PortalJsonLoginAuthenticationFilter portalJsonLoginAuthenticationFilter( - ObjectMapper objectMapper, - CaptchaService captchaService, // 确保 CaptchaService 加了 @Service 注解 - AuthenticationManager authenticationManager, - SessionAuthenticationStrategy sessionAuthenticationStrategy - ) { - // 创建 Filter - PortalJsonLoginAuthenticationFilter filter = - new PortalJsonLoginAuthenticationFilter(objectMapper, captchaService); - - // 注入必要的组件 - filter.setAuthenticationManager(authenticationManager); - filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); - - SecurityContextRepository contextRepository = new HttpSessionSecurityContextRepository(); - filter.setSecurityContextRepository(contextRepository); - - // 设置 JSON 成功处理器 (解决 302 问题) - filter.setAuthenticationSuccessHandler((request, response, authentication) -> { - HttpSession session = request.getSession(true); - Object principal = authentication.getPrincipal(); - if (principal instanceof PortalUserDetails userDetails) { - session.setAttribute(PortalSessionKeys.USER_ID, userDetails.getUserId()); - } - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_OK); - PrintWriter out = response.getWriter(); - out.write(objectMapper.writeValueAsString(Map.of( - "code", 200, - "msg", "登录成功", - "username", authentication.getName() - ))); - }); - - filter.setAuthenticationFailureHandler((request, response, exception) -> { - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_OK); - PrintWriter out = response.getWriter(); - out.write(objectMapper.writeValueAsString(Map.of( - "code", 401, - "msg", "登录失败: " + exception.getMessage() - ))); - }); - - return filter; - } - - @Bean - public SecurityFilterChain securityFilterChain( - HttpSecurity http, - PortalJsonLoginAuthenticationFilter portalJsonLoginAuthenticationFilter - ) throws Exception { // 注意这里要抛出异常 - http.authorizeHttpRequests(authorize -> authorize - // 静态资源和登录接口放行 - .requestMatchers("/","/api/login", "/login", "/index.html", "/assets/**", "/logo.png","/favicon.ico", "/api/captcha").permitAll() - .requestMatchers("/api/public/**").permitAll() - .requestMatchers("/api/system/version").permitAll() - .requestMatchers("/error").permitAll() - .requestMatchers("/api/v2/openapi/**").permitAll() - .requestMatchers("/api/v2/auth/register", "/api/v2/auth/csrf").permitAll() - .requestMatchers("/api/v2/wecom/**").permitAll() - .requestMatchers("/api/v1/**").permitAll() - .anyRequest().authenticated() - ) - .csrf(csrf -> csrf - .spa() - .ignoringRequestMatchers("/api/v1/**", "/api/login", "/api/captcha", "/api/v2/openapi/**", "/api/v2/wecom/**") - ) - .sessionManagement(session -> session - .sessionCreationPolicy(org.springframework.security.config.http.SessionCreationPolicy.IF_REQUIRED) - ) - - .exceptionHandling(exceptionHandling -> - exceptionHandling.authenticationEntryPoint((request, response, authException) -> { - String requestURI = request.getRequestURI(); - // 判断:如果是 API 请求,或者是 AJAX 请求 - if (requestURI.startsWith("/api/") || "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { - // 方案 A:对接口返回 401 JSON - response.setContentType("application/json;charset=UTF-8"); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 - response.getWriter().write("{\"code\": 401, \"msg\": \"未登录或会话已过期\"}"); - } else { - // 方案 B:对普通页面访问,重定向到登录页 - // 注意:这里不要跳首页 /,应该跳 /login,否则可能死循环 - response.sendRedirect("/login"); - } - }) - ) - .logout(logout -> logout - .logoutUrl("/logout") - .logoutSuccessHandler((request, response, authentication) -> { - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\": 200, \"msg\": \"退出成功\"}"); - }) - ) - // 你的自定义 Filter - .addFilterAt(portalJsonLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java b/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java deleted file mode 100644 index 8830ee8..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "push.portal.wecom") -public class PortalWecomProperties { - - private String baseUrl = "https://qyapi.weixin.qq.com"; - - public String getBaseUrl() { - return baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java b/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java deleted file mode 100644 index 50caf6d..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({ - PushProperties.class, - PortalWecomProperties.class, - PortalDataSourceProperties.class -}) -public class PushConfiguration { -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java b/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java deleted file mode 100644 index 9449dd4..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java +++ /dev/null @@ -1,135 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import jakarta.validation.constraints.NotBlank; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -@Validated -@ConfigurationProperties(prefix = "push") -public class PushProperties { - - private final Auth auth = new Auth(); - private final Wecom wecom = new Wecom(); - private final Security security = new Security(); - - public Auth getAuth() { - return auth; - } - - public Wecom getWecom() { - return wecom; - } - - public Security getSecurity() { - return security; - } - - public static class Auth { - @NotBlank - private String key; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - } - - public static class Wecom { - @NotBlank - private String appKey; - @NotBlank - private String appSecret; - @NotBlank - private String agentId; - private String webhookUrl; - - public String getAppKey() { - return appKey; - } - - public void setAppKey(String appKey) { - this.appKey = appKey; - } - - public String getAppSecret() { - return appSecret; - } - - public void setAppSecret(String appSecret) { - this.appSecret = appSecret; - } - - public String getAgentId() { - return agentId; - } - - public void setAgentId(String agentId) { - this.agentId = agentId; - } - - public String getWebhookUrl() { - return webhookUrl; - } - - public void setWebhookUrl(String webhookUrl) { - this.webhookUrl = webhookUrl; - } - } - - public static class Security { - public static final int DEFAULT_BLOCK_MINUTES = 30; - public static final int DEFAULT_FAIL_WINDOW_MINUTES = 5; - public static final int DEFAULT_MAX_FAILS = 5; - public static final long DEFAULT_RATE_LIMIT_CAPACITY = 10; - public static final long DEFAULT_RATE_LIMIT_QPS = 1; - - private Integer blockMinutes; - private Integer failWindowMinutes; - private Integer maxFails; - private Long rateLimitCapacity; - private Long rateLimitQps; - - public int getBlockMinutes() { - return blockMinutes != null ? blockMinutes : DEFAULT_BLOCK_MINUTES; - } - - public void setBlockMinutes(Integer blockMinutes) { - this.blockMinutes = blockMinutes; - } - - public int getFailWindowMinutes() { - return failWindowMinutes != null ? failWindowMinutes : DEFAULT_FAIL_WINDOW_MINUTES; - } - - public void setFailWindowMinutes(Integer failWindowMinutes) { - this.failWindowMinutes = failWindowMinutes; - } - - public int getMaxFails() { - return maxFails != null ? maxFails : DEFAULT_MAX_FAILS; - } - - public void setMaxFails(Integer maxFails) { - this.maxFails = maxFails; - } - - public long getRateLimitCapacity() { - return rateLimitCapacity != null ? rateLimitCapacity : DEFAULT_RATE_LIMIT_CAPACITY; - } - - public void setRateLimitCapacity(Long rateLimitCapacity) { - this.rateLimitCapacity = rateLimitCapacity; - } - - public long getRateLimitQps() { - return rateLimitQps != null ? rateLimitQps : DEFAULT_RATE_LIMIT_QPS; - } - - public void setRateLimitQps(Long rateLimitQps) { - this.rateLimitQps = rateLimitQps; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java b/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java deleted file mode 100644 index 86dcbe3..0000000 --- a/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.qingzhou.pushserver.config; - -import dev.qingzhou.pushserver.aspect.SecurityInterceptor; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - private final SecurityInterceptor securityInterceptor; - - public WebConfig(SecurityInterceptor securityInterceptor) { - this.securityInterceptor = securityInterceptor; - } - - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(securityInterceptor) - .addPathPatterns("/v1/**"); - } - - @Override - public void configurePathMatch(PathMatchConfigurer configurer) { - // 给所有标有 @RestController 注解的类,统一添加 "/api" 前缀 - configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class)); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java b/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java deleted file mode 100644 index f7d6cf2..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.service.SystemConfigService; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class CaptchaController { - - private final SystemConfigService configService; - - public CaptchaController(SystemConfigService configService) { - this.configService = configService; - } - - public record CaptchaResponse(boolean enabled, String siteKey) { - } - - @GetMapping(value = "/captcha", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity captcha() { - return ResponseEntity.ok(new CaptchaResponse( - configService.isTurnstileEnabled(), - configService.getTurnstileSiteKey() - )); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java b/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java deleted file mode 100644 index 07ab0bb..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; -import dev.qingzhou.pushserver.service.DashboardService; -import jakarta.servlet.http.HttpSession; -import java.util.List; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/dashboard") -public class DashboardController { - - private final DashboardService dashboardService; - - public DashboardController(DashboardService dashboardService) { - this.dashboardService = dashboardService; - } - - @GetMapping("/stats") - public PortalResponse stats(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - return PortalResponse.ok(dashboardService.fetchStats(userId)); - } - - @GetMapping("/charts") - public PortalResponse charts(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - return PortalResponse.ok(dashboardService.fetchCharts(userId)); - } - - @GetMapping("/recent-logs") - public PortalResponse> recentLogs( - @RequestParam(defaultValue = "10") int limit, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - int safeLimit = Math.max(1, Math.min(limit, 100)); - return PortalResponse.ok(dashboardService.fetchRecentLogs(userId, safeLimit)); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PageController.java b/src/main/java/dev/qingzhou/pushserver/controller/PageController.java deleted file mode 100644 index 77d1bfd..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PageController.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -public class PageController { - - @RequestMapping(value = { - "/{path:[^\\.]*}", // 匹配一级路径,如 /login, /dashboard - "/**/{path:[^\\.]*}" // 匹配多级路径,如 /system/users, /user/profile/edit - }) - public String redirect() { - // 转发到根目录的 index.html,让 Vue Router 在前端接管路由 - return "forward:/index.html"; - } - -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java deleted file mode 100644 index 8647af3..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java +++ /dev/null @@ -1,165 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest; -import dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest; -import dev.qingzhou.pushserver.model.dto.portal.PortalAppUpdateRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.model.vo.portal.PortalAppApiKeyResponse; -import dev.qingzhou.pushserver.model.vo.portal.PortalAppResponse; -import dev.qingzhou.pushserver.service.PortalAppApiKeyService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import java.util.List; -import java.util.stream.Collectors; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/apps") -public class PortalAppController { - - private final PortalWecomAppService appService; - private final PortalAppApiKeyService apiKeyService; - - public PortalAppController(PortalWecomAppService appService, PortalAppApiKeyService apiKeyService) { - this.appService = appService; - this.apiKeyService = apiKeyService; - } - - @PostMapping - public PortalResponse create( - @Valid @RequestBody PortalAppCreateRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalWecomApp app = appService.addApp(userId, request.getAgentId(), request.getSecret()); - return PortalResponse.ok(toResponse(app)); - } - - @PutMapping("/{appId}") - public PortalResponse update( - @PathVariable Long appId, - @RequestBody PortalAppUpdateRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalWecomApp app = appService.updateApp( - userId, appId, - request.getSecret(), - request.getToken(), - request.getEncodingAesKey() - ); - return PortalResponse.ok(toResponse(app)); - } - - @GetMapping - public PortalResponse> list(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - List apps = appService.listByUser(userId).stream() - .map(this::toResponse) - .collect(Collectors.toList()); - return PortalResponse.ok(apps); - } - - @DeleteMapping("/{appId}") - public PortalResponse delete(@PathVariable Long appId, HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - appService.deleteApp(userId, appId); - apiKeyService.removeByAppId(appId); - return PortalResponse.ok("已删除", null); - } - - @PostMapping("/{appId}/sync") - public PortalResponse sync(@PathVariable Long appId, HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalWecomApp app = appService.syncApp(userId, appId); - return PortalResponse.ok(toResponse(app)); - } - - @GetMapping("/{appId}/api-key") - public PortalResponse getApiKey( - @PathVariable Long appId, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalAppApiKey record = apiKeyService.findByAppId(userId, appId); - return PortalResponse.ok(toApiKeyResponse(appId, record)); - } - - @PostMapping("/{appId}/api-key/reset") - public PortalResponse resetApiKey( - @PathVariable Long appId, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalAppApiKey record = apiKeyService.rotateKey(userId, appId); - return PortalResponse.ok(toApiKeyResponse(appId, record)); - } - - @PostMapping("/{appId}/api-key") - public PortalResponse createApiKey( - @PathVariable Long appId, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalAppApiKey record = apiKeyService.rotateKey(userId, appId); - return PortalResponse.ok(toApiKeyResponse(appId, record)); - } - - @PutMapping("/{appId}/api-key") - public PortalResponse updateApiKey( - @PathVariable Long appId, - @Valid @RequestBody PortalAppApiKeyUpdateRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalAppApiKey record = apiKeyService.updateRateLimit(userId, appId, request.getRateLimitPerMinute()); - return PortalResponse.ok(toApiKeyResponse(appId, record)); - } - - @DeleteMapping("/{appId}/api-key") - public PortalResponse deleteApiKey( - @PathVariable Long appId, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - appService.requireByUser(userId, appId); - apiKeyService.removeByAppId(appId); - return PortalResponse.ok("已删除", null); - } - - private PortalAppResponse toResponse(PortalWecomApp app) { - PortalAppResponse response = new PortalAppResponse(); - response.setId(app.getId()); - response.setAgentId(app.getAgentId()); - response.setName(app.getName()); - response.setAvatarUrl(app.getAvatarUrl()); - response.setDescription(app.getDescription()); - response.setCreatedAt(app.getCreatedAt()); - response.setUpdatedAt(app.getUpdatedAt()); - return response; - } - - private PortalAppApiKeyResponse toApiKeyResponse(Long appId, PortalAppApiKey record) { - PortalAppApiKeyResponse response = new PortalAppApiKeyResponse(); - response.setAppId(appId); - response.setHasKey(record != null); - if (record != null) { - response.setApiKey(record.getApiKeyPlain()); - response.setRateLimitPerMinute(record.getRateLimitPerMinute()); - response.setCreatedAt(record.getCreatedAt()); - response.setUpdatedAt(record.getUpdatedAt()); - } - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java deleted file mode 100644 index c33cd39..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java +++ /dev/null @@ -1,56 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.model.dto.portal.PortalRegisterRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse; -import dev.qingzhou.pushserver.service.PortalUserService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/auth") -public class PortalAuthController { - - private final PortalUserService userService; - - public PortalAuthController(PortalUserService userService) { - this.userService = userService; - } - - @PostMapping("/register") - public PortalResponse register( - @Valid @RequestBody PortalRegisterRequest request - ) { - PortalUser user = userService.register(request.getAccount(), request.getPassword()); - return PortalResponse.ok(toResponse(user)); - } - - @GetMapping("/csrf") - public PortalResponse csrf(CsrfToken token) { - return PortalResponse.ok(token.getToken()); - } - - @PostMapping("/logout") - public PortalResponse logout(HttpSession session) { - if (session != null) { - session.invalidate(); - } - return PortalResponse.ok("已退出登录", null); - } - - private PortalUserResponse toResponse(PortalUser user) { - PortalUserResponse response = new PortalUserResponse(); - response.setId(user.getId()); - response.setAccount(user.getAccount()); - response.setCreatedAt(user.getCreatedAt()); - response.setUpdatedAt(user.getUpdatedAt()); - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java deleted file mode 100644 index 344e641..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java +++ /dev/null @@ -1,49 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse; -import dev.qingzhou.pushserver.service.PortalCorpConfigService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/corp") -public class PortalCorpController { - - private final PortalCorpConfigService corpConfigService; - - public PortalCorpController(PortalCorpConfigService corpConfigService) { - this.corpConfigService = corpConfigService; - } - - @GetMapping - public PortalResponse getCorp(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalCorpConfig config = corpConfigService.getByUserId(userId); - PortalCorpResponse response = new PortalCorpResponse(); - if (config != null) { - response.setCorpId(config.getCorpId()); - } - return PortalResponse.ok(response); - } - - @PutMapping - public PortalResponse upsert( - @Valid @RequestBody PortalCorpConfigRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalCorpConfig config = corpConfigService.upsert(userId, request.getCorpId()); - PortalCorpResponse response = new PortalCorpResponse(); - response.setCorpId(config.getCorpId()); - return PortalResponse.ok(response); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java deleted file mode 100644 index 8b21a93..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.boot.webmvc.error.ErrorController; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class PortalErrorController implements ErrorController { - - @RequestMapping("/error") - public ResponseEntity> handleError(HttpServletRequest request) { - Object status = request.getAttribute("jakarta.servlet.error.status_code"); - String message = "系统发生错误"; - - if (status != null) { - int statusCode = Integer.parseInt(status.toString()); - if (statusCode == HttpStatus.NOT_FOUND.value()) { - message = "接口不存在"; - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(PortalResponse.fail(message)); - } else if (statusCode == HttpStatus.UNAUTHORIZED.value()) { - message = "未授权或会话过期"; - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(PortalResponse.fail(message)); - } else if (statusCode == HttpStatus.FORBIDDEN.value()) { - message = "没有访问权限"; - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PortalResponse.fail(message)); - } else if (statusCode >= 500) { - message = "系统内部错误"; - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(PortalResponse.fail(message)); - } - } - - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(PortalResponse.fail(message)); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java deleted file mode 100644 index 018580c..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java +++ /dev/null @@ -1,51 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.model.dto.portal.PortalInitRequest; -import dev.qingzhou.pushserver.service.PortalUserService; -import dev.qingzhou.pushserver.service.SystemConfigService; -import jakarta.validation.Valid; -import java.util.Map; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/public") -public class PortalInitController { - - private final PortalUserService userService; - private final SystemConfigService configService; - - public PortalInitController(PortalUserService userService, SystemConfigService configService) { - this.userService = userService; - this.configService = configService; - } - - @GetMapping("/init-status") - public ResponseEntity> getInitStatus() { - boolean initialized = userService.count() > 0; - return ResponseEntity.ok(Map.of("initialized", initialized)); - } - - @PostMapping("/init") - public ResponseEntity> initialize(@Valid @RequestBody PortalInitRequest request) { - if (userService.count() > 0) { - return ResponseEntity.badRequest().body(Map.of("msg", "System already initialized")); - } - - // Create Admin User - userService.register(request.getUsername(), request.getPassword()); - - // Save Turnstile Config - configService.setTurnstileConfig( - request.isTurnstileEnabled(), - request.getTurnstileSiteKey(), - request.getTurnstileSecretKey() - ); - - return ResponseEntity.ok(Map.of("msg", "Initialization successful")); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java deleted file mode 100644 index 06d19ab..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.model.dto.portal.PortalPasswordUpdateRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse; -import dev.qingzhou.pushserver.service.PortalUserService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/me") -public class PortalMeController { - - private final PortalUserService userService; - - public PortalMeController(PortalUserService userService) { - this.userService = userService; - } - - @GetMapping - public PortalResponse me(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalUser user = userService.getById(userId); - if (user == null) { - throw new PortalException(PortalStatus.NOT_FOUND, "用户未找到"); - } - return PortalResponse.ok(toResponse(user)); - } - - @PutMapping("/password") - public PortalResponse updatePassword( - @Valid @RequestBody PortalPasswordUpdateRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - userService.updatePassword(userId, request.getOldPassword(), request.getNewPassword()); - return PortalResponse.ok("密码已更新", null); - } - - private PortalUserResponse toResponse(PortalUser user) { - PortalUserResponse response = new PortalUserResponse(); - response.setId(user.getId()); - response.setAccount(user.getAccount()); - response.setCreatedAt(user.getCreatedAt()); - response.setUpdatedAt(user.getUpdatedAt()); - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java deleted file mode 100644 index a4c8102..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogConverter; -import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse; -import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; -import dev.qingzhou.pushserver.service.PortalMessageLogService; -import dev.qingzhou.pushserver.service.PortalMessageService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import java.util.List; -import java.util.stream.Collectors; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/messages") -public class PortalMessageController { - - private final PortalMessageService messageService; - private final PortalMessageLogService messageLogService; - - public PortalMessageController(PortalMessageService messageService, PortalMessageLogService messageLogService) { - this.messageService = messageService; - this.messageLogService = messageLogService; - } - - @PostMapping("/send") - public PortalResponse send( - @Valid @RequestBody PortalMessageSendRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalMessageLog log = messageService.send(userId, request); - return PortalResponse.ok(PortalMessageLogConverter.toResponse(log)); - } - - @GetMapping("/logs") - public PortalResponse logs( - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer pageSize, - @RequestParam(defaultValue = "20") int limit, - @RequestParam(required = false) Boolean success, - @RequestParam(required = false) Long appId, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - boolean usePagination = page != null || pageSize != null; - if (usePagination) { - int resolvedPage = page == null ? 1 : page; - int resolvedPageSize = pageSize == null ? 20 : pageSize; - PortalPageResponse pagedLogs = messageLogService.pageLogs(userId, appId, success, resolvedPage, resolvedPageSize); - List records = pagedLogs.getRecords().stream() - .map(PortalMessageLogConverter::toResponse) - .collect(Collectors.toList()); - PortalPageResponse response = PortalPageResponse.of( - records, - pagedLogs.getTotal(), - pagedLogs.getPage(), - pagedLogs.getPageSize() - ); - return PortalResponse.ok(response); - } - - List logs = messageLogService.listRecent(userId, limit, appId, success).stream() - .map(PortalMessageLogConverter::toResponse) - .collect(Collectors.toList()); - return PortalResponse.ok(logs); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java deleted file mode 100644 index 98452fa..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.common.PortalSessionSupport; -import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse; -import dev.qingzhou.pushserver.service.PortalProxyConfigService; -import jakarta.servlet.http.HttpSession; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/v2/proxy") -public class PortalProxyController { - - private final PortalProxyConfigService proxyConfigService; - - public PortalProxyController(PortalProxyConfigService proxyConfigService) { - this.proxyConfigService = proxyConfigService; - } - - @GetMapping - public PortalResponse getProxy(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalProxyConfig config = proxyConfigService.getByUserId(userId); - return PortalResponse.ok(PortalProxyConfigResponse.from(config)); - } - - @PutMapping - public PortalResponse upsert( - @Valid @RequestBody PortalProxyConfigRequest request, - HttpSession session - ) { - Long userId = PortalSessionSupport.requireUserId(session); - PortalProxyConfig config = proxyConfigService.upsert(userId, request); - return PortalResponse.ok(PortalProxyConfigResponse.from(config)); - } - - @DeleteMapping - public PortalResponse delete(HttpSession session) { - Long userId = PortalSessionSupport.requireUserId(session); - proxyConfigService.deleteByUserId(userId); - return PortalResponse.ok(null); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java b/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java deleted file mode 100644 index 07daa58..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.service.SystemConfigService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.web.bind.annotation.*; -import java.util.Map; - -@RestController -@RequestMapping("/system") -public class PortalSystemController { - - private final SystemConfigService systemConfigService; - - @Value("${info.app.version:unknown}") - private String appVersion; - - public PortalSystemController(SystemConfigService systemConfigService) { - this.systemConfigService = systemConfigService; - } - - @GetMapping("/version") - public PortalResponse> getVersion() { - return PortalResponse.ok(Map.of("version", appVersion)); - } - - @GetMapping("/version/ignore") - public PortalResponse> getIgnoreVersion() { - String version = systemConfigService.get("ignore_version", ""); - return PortalResponse.ok(Map.of("version", version)); - } - - @PostMapping("/version/ignore") - public PortalResponse setIgnoreVersion(@RequestBody Map body) { - String version = body.get("version"); - if (version != null) { - systemConfigService.set("ignore_version", version); - } - return PortalResponse.ok(null); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/PushController.java b/src/main/java/dev/qingzhou/pushserver/controller/PushController.java deleted file mode 100644 index af97c71..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/PushController.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.qingzhou.pushserver.controller; - -import dev.qingzhou.push.core.model.PushResult; -import dev.qingzhou.pushserver.config.PushProperties; -import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; -import dev.qingzhou.pushserver.service.PushService; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v1") -public class PushController { - - private final PushService pushService; - private final PushProperties properties; - - public PushController(PushService pushService, PushProperties properties) { - this.pushService = pushService; - this.properties = properties; - } - - @PostMapping("/push") - public ResponseEntity push( - @RequestHeader(value = "X-API-Key", required = false) String apiKey, - @RequestBody PushRequest request - ) { - if (!isAuthorized(apiKey)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(PushResult.fail("未授权")); - } - try { - PushResult result = pushService.push(request); - return ResponseEntity.ok(result); - } catch (IllegalArgumentException ex) { - return ResponseEntity.badRequest() - .body(PushResult.fail(ex.getMessage())); - } catch (IllegalStateException ex) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(PushResult.fail(ex.getMessage())); - } - } - - private boolean isAuthorized(String apiKey) { - if (!StringUtils.hasText(apiKey)) { - return false; - } - return apiKey.equals(properties.getAuth().getKey()); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java b/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java deleted file mode 100644 index b52fdb0..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.qingzhou.pushserver.controller.openapi; - -import dev.qingzhou.pushserver.common.PortalResponse; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest; -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogConverter; -import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse; -import dev.qingzhou.pushserver.service.PortalAppApiKeyService; -import dev.qingzhou.pushserver.service.PortalMessageService; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/v2/openapi/messages") -public class OpenApiMessageController { - - private final PortalAppApiKeyService apiKeyService; - private final PortalMessageService messageService; - - public OpenApiMessageController( - PortalAppApiKeyService apiKeyService, - PortalMessageService messageService - ) { - this.apiKeyService = apiKeyService; - this.messageService = messageService; - } - - @PostMapping("/send") - public ResponseEntity> send( - @RequestHeader(value = "X-API-Key", required = false) String apiKey, - @RequestBody OpenApiMessageSendRequest request - ) { - try { - PortalAppApiKeyService.AppAuthContext ctx = apiKeyService.requireAppByApiKey(apiKey); - PortalMessageSendRequest portalRequest = toPortalRequest(request, ctx.app().getId()); - PortalMessageLog log = messageService.send(ctx.app().getUserId(), portalRequest); - return ResponseEntity.ok(PortalResponse.ok(PortalMessageLogConverter.toResponse(log))); - } catch (PortalException ex) { - return ResponseEntity.status(ex.getStatus().getHttpStatus()) - .body(PortalResponse.fail(ex.getMessage())); - } catch (Exception ex) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(PortalResponse.fail("系统内部错误")); - } - } - - private PortalMessageSendRequest toPortalRequest(OpenApiMessageSendRequest request, Long appId) { - PortalMessageSendRequest target = new PortalMessageSendRequest(); - target.setAppId(appId); - target.setToUser(request.getToUser()); - target.setToParty(request.getToParty()); - target.setToAll(request.getToAll()); - target.setMsgType(request.getMsgType()); - target.setContent(request.getContent()); - target.setTitle(request.getTitle()); - target.setDescription(request.getDescription()); - target.setUrl(request.getUrl()); - target.setBtnText(request.getBtnText()); - target.setArticles(request.getArticles()); - return target; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java b/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java deleted file mode 100644 index e20ec99..0000000 --- a/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java +++ /dev/null @@ -1,104 +0,0 @@ -package dev.qingzhou.pushserver.controller.wecom; - -import dev.qingzhou.pushserver.manager.wecom.AesException; -import dev.qingzhou.pushserver.manager.wecom.WXBizMsgCrypt; -import dev.qingzhou.pushserver.manager.wecom.WecomMessageParser; -import dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.service.PortalCorpConfigService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; - -@Slf4j -@RestController -@RequestMapping("/v2/wecom/callback/{appId}") -public class WecomCallbackController { - - private final PortalWecomAppService wecomAppService; - private final PortalCorpConfigService corpConfigService; - - public WecomCallbackController(PortalWecomAppService wecomAppService, PortalCorpConfigService corpConfigService) { - this.wecomAppService = wecomAppService; - this.corpConfigService = corpConfigService; - } - - /** - * 企业微信回调 URL 验证 (GET) - */ - @GetMapping - public String verify( - @PathVariable("appId") Long appId, - @RequestParam("msg_signature") String signature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce, - @RequestParam("echostr") String echostr) { - - log.info("Received WeCom callback verification for appId={}: signature={}, timestamp={}, nonce={}, echostr={}", - appId, signature, timestamp, nonce, echostr); - - try { - WXBizMsgCrypt wxcpt = getWxCrypt(appId); - return wxcpt.VerifyURL(signature, timestamp, nonce, echostr); - } catch (AesException e) { - log.error("WeCom verification failed", e); - return "FAILED: " + e.getMessage(); - } catch (Exception e) { - log.error("System error during verification", e); - return "ERROR"; - } - } - - /** - * 企业微信消息/事件推送 (POST) - */ - @PostMapping - public String handleMessage( - @PathVariable("appId") Long appId, - @RequestParam("msg_signature") String signature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce, - @RequestBody String body) { - - log.info("Received WeCom message for appId={}: signature={}, timestamp={}, nonce={}", appId, signature, timestamp, nonce); - - try { - WXBizMsgCrypt wxcpt = getWxCrypt(appId); - String decryptedMsg = wxcpt.DecryptMsg(signature, timestamp, nonce, body); - log.info("Decrypted XML: {}", decryptedMsg); - - WecomMessagePayload payload = WecomMessageParser.parse(decryptedMsg); - log.info("Parsed Payload: {}", payload); - - // TODO: 后续可以根据 payload.getMsgType() 或 payload.getEvent() 分发到不同的处理器 - - return "success"; - } catch (AesException e) { - log.error("WeCom message decryption failed", e); - return "FAILED"; // 企业微信要求处理失败不返回 success,会重试 - } catch (Exception e) { - log.error("System error during message handling", e); - return "ERROR"; - } - } - - private WXBizMsgCrypt getWxCrypt(Long appId) throws AesException { - PortalWecomApp app = wecomAppService.getById(appId); - if (app == null) { - throw new AesException(AesException.IllegalAesKey, "App not found"); - } - - // 校验配置是否完整 - if (app.getToken() == null || app.getEncodingAesKey() == null) { - throw new AesException(AesException.IllegalAesKey, "Token or EncodingAESKey not configured for this app"); - } - - PortalCorpConfig corpConfig = corpConfigService.getByUserId(app.getUserId()); - if (corpConfig == null || corpConfig.getCorpId() == null) { - throw new AesException(AesException.ValidateCorpidError, "CorpConfig not found"); - } - - return new WXBizMsgCrypt(app.getToken(), app.getEncodingAesKey(), corpConfig.getCorpId()); - } -} \ No newline at end of file diff --git a/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java b/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java deleted file mode 100644 index f046a26..0000000 --- a/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.qingzhou.pushserver.exception; - -import dev.qingzhou.push.core.model.PushResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -import java.util.Objects; - -@RestControllerAdvice(basePackages = "dev.qingzhou.pushserver.controller.openapi") -public class GlobalExceptionHandler { - - private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); - - - @ExceptionHandler(Exception.class) - public PushResult handleException(Exception e) { - // 1. 只有服务端自己看日志 - log.error("Unknown error occurred", e); - // 2. 告诉前端“系统异常”,别告诉他具体哪行代码错了 - return PushResult.fail("系统内部错误: " + e.getClass().getSimpleName()); - } - - // 可以专门捕获参数校验异常,返回具体字段错误 - @ExceptionHandler(MethodArgumentNotValidException.class) - public PushResult handleValidException(org.springframework.web.bind.MethodArgumentNotValidException e) { - return PushResult.fail("参数错误: " + Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java b/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java deleted file mode 100644 index 39c040f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.qingzhou.pushserver.exception; - -public class PortalException extends RuntimeException { - - private final PortalStatus status; - - public PortalException(PortalStatus status, String message) { - super(message); - this.status = status; - } - - public PortalException(PortalStatus status, String message, Throwable cause) { - super(message, cause); - this.status = status; - } - - public PortalStatus getStatus() { - return status; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java b/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java deleted file mode 100644 index de0e853..0000000 --- a/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java +++ /dev/null @@ -1,43 +0,0 @@ -package dev.qingzhou.pushserver.exception; - -import dev.qingzhou.pushserver.common.PortalResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@RestControllerAdvice(basePackages = "dev.qingzhou.pushserver.controller") -public class PortalExceptionHandler { - - private static final Logger log = LoggerFactory.getLogger(PortalExceptionHandler.class); - - @ExceptionHandler(PortalException.class) - public ResponseEntity> handlePortalException(PortalException ex) { - return ResponseEntity.status(ex.getStatus().getHttpStatus()) - .body(PortalResponse.fail(ex.getMessage())); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleValidation(MethodArgumentNotValidException ex) { - String message = "校验失败"; - if (ex.getBindingResult().getFieldError() != null) { - message = ex.getBindingResult().getFieldError().getDefaultMessage(); - } - return ResponseEntity.badRequest().body(PortalResponse.fail(message)); - } - - @ExceptionHandler(HttpMessageNotReadableException.class) - public ResponseEntity> handleNotReadable(HttpMessageNotReadableException ex) { - return ResponseEntity.badRequest().body(PortalResponse.fail("请求参数格式错误")); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity> handleException(Exception ex) { - log.error("Unhandled portal error", ex); - return ResponseEntity.internalServerError() - .body(PortalResponse.fail("服务器内部错误")); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java b/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java deleted file mode 100644 index 56ad363..0000000 --- a/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.qingzhou.pushserver.exception; - -import org.springframework.http.HttpStatus; - -public enum PortalStatus { - BAD_REQUEST(HttpStatus.BAD_REQUEST), - UNAUTHORIZED(HttpStatus.UNAUTHORIZED), - NOT_FOUND(HttpStatus.NOT_FOUND), - CONFLICT(HttpStatus.CONFLICT), - TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS), - BAD_GATEWAY(HttpStatus.BAD_GATEWAY); - - private final HttpStatus httpStatus; - - PortalStatus(HttpStatus httpStatus) { - this.httpStatus = httpStatus; - } - - public HttpStatus getHttpStatus() { - return httpStatus; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java deleted file mode 100644 index bf2b572..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -@SuppressWarnings("serial") -public class AesException extends Exception { - - public final static int OK = 0; - public final static int ValidateSignatureError = -40001; - public final static int ParseXmlError = -40002; - public final static int ComputeSignatureError = -40003; - public final static int IllegalAesKey = -40004; - public final static int ValidateCorpidError = -40005; - public final static int EncryptAESError = -40006; - public final static int DecryptAESError = -40007; - public final static int IllegalBuffer = -40008; - - private int code; - - public AesException(int code) { - super(getMessage(code)); - this.code = code; - } - - public AesException(int code, String message) { - super(message); - this.code = code; - } - - public int getCode() { - return code; - } - - private static String getMessage(int code) { - switch (code) { - case ValidateSignatureError: - return "签名验证错误"; - case ParseXmlError: - return "xml解析失败"; - case ComputeSignatureError: - return "sha加密生成签名失败"; - case IllegalAesKey: - return "SymmetricKey非法"; - case ValidateCorpidError: - return "corpid校验失败"; - case EncryptAESError: - return "aes加密失败"; - case DecryptAESError: - return "aes解密失败"; - case IllegalBuffer: - return "解密后得到的buffer非法"; - default: - return null; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java deleted file mode 100644 index d0f7ab8..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java +++ /dev/null @@ -1,448 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.StringReader; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Base64; -import java.util.Random; - -/** - * 提供接收和推送给企业微信消息的加解密接口(v1.1).
  1. 第三方回复加密消息给企业微信
  2. 第三方收到企业微信发送的消息,验证签名,解密
- * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 - *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432125.html
  2. - *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. - *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. - *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
- */ -public class WXBizMsgCrypt { - static Charset CHARSET = StandardCharsets.UTF_8; - Base64.Decoder base64Decoder = Base64.getDecoder(); - Base64.Encoder base64Encoder = Base64.getEncoder(); - byte[] aesKey; - String token; - String receiveId; - - /** - * 构造函数 - * - * @param token 企业微信后台,开发者设置的token - * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey - * @param receiveId 不同场景含义不同,appId或者corpId - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public WXBizMsgCrypt(String token, String encodingAesKey, String receiveId) throws AesException { - if (encodingAesKey.length() != 43) { - throw new AesException(AesException.IllegalAesKey); - } - this.token = token; - this.receiveId = receiveId; - aesKey = base64Decoder.decode(encodingAesKey + "="); - } - - // 生成4个字节的网络字节序 - byte[] getNetworkBytesOrder(int sourceNumber) { - byte[] orderBytes = new byte[4]; - orderBytes[3] = (byte) (sourceNumber & 0xFF); - orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); - orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); - orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); - return orderBytes; - } - - // 还原4个字节的网络字节序 - int recoverNetworkBytesOrder(byte[] orderBytes) { - int sourceNumber = 0; - for (int i = 0; i < 4; i++) { - sourceNumber <<= 8; - sourceNumber |= orderBytes[i] & 0xff; - } - return sourceNumber; - } - - // 随机生成16位字符串 - String getRandomStr() { - String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < 16; i++) { - int number = random.nextInt(base.length()); - sb.append(base.charAt(number)); - } - return sb.toString(); - } - - /** - * 对明文进行加密. - * - * @param text 需要加密的明文 - * @return 加密后base64编码的字符串 - * @throws AesException aes加密失败 - */ - String encrypt(String randomStr, String text) throws AesException { - ByteGroup byteCollector = new ByteGroup(); - byte[] randomStrBytes = randomStr.getBytes(CHARSET); - byte[] textBytes = text.getBytes(CHARSET); - byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); - byte[] receiveIdBytes = receiveId.getBytes(CHARSET); - - // randomStr + networkBytesOrder + text + receiveId - byteCollector.addBytes(randomStrBytes); - byteCollector.addBytes(networkBytesOrder); - byteCollector.addBytes(textBytes); - byteCollector.addBytes(receiveIdBytes); - - // ... + pad: 使用自定义的填充方式对明文进行补位填充 - byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); - byteCollector.addBytes(padBytes); - - // 获得最终的字节流, 未加密 - byte[] unencrypted = byteCollector.toBytes(); - - try { - // 设置加密模式为AES的CBC模式 - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); - IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); - - // 加密 - byte[] encrypted = cipher.doFinal(unencrypted); - - // 使用BASE64对加密后的字符串进行编码 - return base64Encoder.encodeToString(encrypted); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.EncryptAESError); - } - } - - /** - * 对密文进行解密. - * - * @param text 需要解密的密文 - * @return 解密得到的明文 - * @throws AesException aes解密失败 - */ - String decrypt(String text) throws AesException { - byte[] original; - try { - // 设置解密模式为AES的CBC模式 - Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); - SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); - IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); - cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); - - // 使用BASE64对密文进行解码 - byte[] encrypted = base64Decoder.decode(text); - - // 解密 - original = cipher.doFinal(encrypted); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.DecryptAESError); - } - - String xmlContent, from_receiveId; - try { - // 去除补位字符 - byte[] bytes = PKCS7Encoder.decode(original); - - // 分离16位随机字符串,网络字节序和receiveId - byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); - - int xmlLength = recoverNetworkBytesOrder(networkOrder); - - xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); - from_receiveId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.IllegalBuffer); - } - - // receiveId不相同的情况 - if (!from_receiveId.equals(receiveId)) { - throw new AesException(AesException.ValidateCorpidError); - } - return xmlContent; - - } - - /** - * 将公众平台回复用户的消息加密打包. - *
    - *
  1. 对要发送的消息进行AES-CBC加密
  2. - *
  3. 生成安全签名
  4. - *
  5. 将消息密文和时间戳、随机数字、安全签名打包成xml格式
  6. - *
- * - * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串 - * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp - * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce - * @return 加密后的可以直接回复用户的xml - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { - // 加密 - String encrypt = encrypt(getRandomStr(), replyMsg); - - // 生成安全签名 - if (timeStamp == "") { - timeStamp = Long.toString(System.currentTimeMillis()); - } - - String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); - - // 生成发送的xml - String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); - return result; - } - - /** - * 验证URL - * - * @param msgSignature 签名串,对应URL参数的msg_signature - * @param timeStamp 时间戳,对应URL参数的timestamp - * @param nonce 随机串,对应URL参数的nonce - * @param echoStr 随机串,对应URL参数的echostr - * @return 解密之后的echostr - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) throws AesException { - String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); - - if (!signature.equals(msgSignature)) { - throw new AesException(AesException.ValidateSignatureError); - } - - String result = decrypt(echoStr); - return result; - } - - /** - * 检验消息的真实性,并且获取解密后的明文. - *
    - *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. - *
  3. 若验证通过,则提取xml中的加密消息
  4. - *
  5. 对消息进行解密
  6. - *
- * - * @param msgSignature 签名串,对应URL参数的msg_signature - * @param timeStamp 时间戳,对应URL参数的timestamp - * @param nonce 随机串,对应URL参数的nonce - * @param postData 密文,对应POST请求的数据 - * @return 解密后的原文 - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) throws AesException { - // 提取密文 - Object[] encrypt = XMLParse.extract(postData); - - // 验证安全签名 - String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); - - // 和URL中的签名比较是否相等 - if (!signature.equals(msgSignature)) { - throw new AesException(AesException.ValidateSignatureError); - } - - // 解密 - String result = decrypt(encrypt[1].toString()); - return result; - } - - // ----------------------------------------------------------- - // 内部类: PKCS7Encoder - // ----------------------------------------------------------- - static class PKCS7Encoder { - static Charset CHARSET = StandardCharsets.UTF_8; - static int BLOCK_SIZE = 32; - - /** - * 获得对明文进行补位填充的字节. - * - * @param count 需要进行填充补位操作的明文长度 - * @return 补齐用的字节数组 - */ - static byte[] encode(int count) { - // 计算需要填充的位数 - int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); - if (amountToPad == 0) { - amountToPad = BLOCK_SIZE; - } - // 获得补位所用的字符 - char padChr = chr(amountToPad); - String tmp = new String(); - for (int index = 0; index < amountToPad; index++) { - tmp += padChr; - } - return tmp.getBytes(CHARSET); - } - - /** - * 删除解密后明文的补位字符 - * - * @param decrypted 解密后的明文 - * @return 删除补位字符后的明文 - */ - static byte[] decode(byte[] decrypted) { - int pad = (int) decrypted[decrypted.length - 1]; - if (pad < 1 || pad > 32) { - pad = 0; - } - return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); - } - - /** - * 将数字转化成ASCII码对应的字符,用于对明文进行补码 - * - * @param a 需要转化的数字 - * @return 转化得到的字符 - */ - static char chr(int a) { - byte target = (byte) (a & 0xFF); - return (char) target; - } - } - - // ----------------------------------------------------------- - // 内部类: ByteGroup - // ----------------------------------------------------------- - static class ByteGroup { - java.util.ArrayList byteContainer = new java.util.ArrayList(); - - public byte[] toBytes() { - byte[] bytes = new byte[byteContainer.size()]; - for (int i = 0; i < byteContainer.size(); i++) { - bytes[i] = byteContainer.get(i); - } - return bytes; - } - - public ByteGroup addBytes(byte[] bytes) { - for (byte b : bytes) { - byteContainer.add(b); - } - return this; - } - - public int size() { - return byteContainer.size(); - } - } - - // ----------------------------------------------------------- - // 内部类: SHA1 - // ----------------------------------------------------------- - static class SHA1 { - /** - * 用SHA1算法生成安全签名 - * - * @param token 票据 - * @param timestamp 时间戳 - * @param nonce 随机字符串 - * @param encrypt 密文 - * @return 安全签名 - * @throws AesException - */ - public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException { - try { - String[] array = new String[]{token, timestamp, nonce, encrypt}; - StringBuffer sb = new StringBuffer(); - // 字符串排序 - Arrays.sort(array); - for (int i = 0; i < 4; i++) { - sb.append(array[i]); - } - String str = sb.toString(); - // SHA1签名生成 - MessageDigest md = MessageDigest.getInstance("SHA-1"); - md.update(str.getBytes()); - byte[] digest = md.digest(); - - StringBuffer hexstr = new StringBuffer(); - String shaHex = ""; - for (int i = 0; i < digest.length; i++) { - shaHex = Integer.toHexString(digest[i] & 0xFF); - if (shaHex.length() < 2) { - hexstr.append(0); - } - hexstr.append(shaHex); - } - return hexstr.toString(); - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.ComputeSignatureError); - } - } - } - - // ----------------------------------------------------------- - // 内部类: XMLParse - // ----------------------------------------------------------- - static class XMLParse { - /** - * 提取出xml数据包中的加密消息 - * - * @param xmltext 待提取的xml字符串 - * @return 提取出的加密消息字符串 - * @throws AesException - */ - public static Object[] extract(String xmltext) throws AesException { - Object[] result = new Object[3]; - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - - // XXE 防护 - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(xmltext); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - NodeList nodelist1 = root.getElementsByTagName("Encrypt"); - result[0] = 0; - result[1] = nodelist1.item(0).getTextContent(); - //result[2] = root.getElementsByTagName("ToUserName").item(0).getTextContent(); - return result; - } catch (Exception e) { - e.printStackTrace(); - throw new AesException(AesException.ParseXmlError); - } - } - - /** - * 生成xml消息 - * - * @param encrypt 加密后的消息密文 - * @param signature 安全签名 - * @param timestamp 时间戳 - * @param nonce 随机字符串 - * @return 生成的xml字符串 - */ - public static String generate(String encrypt, String signature, String timestamp, String nonce) { - String format = "\n" + "\n" - + "\n" - + "%3$s\n" + "\n" + ""; - return String.format(format, encrypt, signature, timestamp, nonce); - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java deleted file mode 100644 index 41acd93..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java +++ /dev/null @@ -1,37 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class WecomAgentInfo extends WecomResponse { - - private String name; - - private String description; - - @JsonProperty("square_logo_url") - private String avatarUrl; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getAvatarUrl() { - return avatarUrl; - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java deleted file mode 100644 index 109ca75..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java +++ /dev/null @@ -1,172 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import dev.qingzhou.pushserver.config.PortalWecomProperties; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import org.springframework.http.MediaType; -import org.springframework.http.client.JdkClientHttpRequestFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClient; -import org.springframework.web.client.RestClientException; - -import java.io.IOException; -import java.net.*; -import java.net.http.HttpClient; -import java.util.Collections; -import java.util.List; - -@Component -public class WecomApiClient { - - private final PortalWecomProperties properties; - private final RestClient defaultClient; - - public WecomApiClient(PortalWecomProperties properties) { - this.properties = properties; - this.defaultClient = RestClient.builder() - .baseUrl(properties.getBaseUrl()) - .build(); - } - - private RestClient getClient(PortalProxyConfig proxyConfig) { - if (proxyConfig == null || !Boolean.TRUE.equals(proxyConfig.getActive())) { - return defaultClient; - } - - Proxy.Type proxyType = "SOCKS5".equalsIgnoreCase(proxyConfig.getType()) ? Proxy.Type.SOCKS : Proxy.Type.HTTP; - InetSocketAddress proxyAddr = new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()); - - HttpClient.Builder builder = HttpClient.newBuilder() - .proxy(new ProxySelector() { - @Override - public List select(URI uri) { - return Collections.singletonList(new Proxy(proxyType, proxyAddr)); - } - - @Override - public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { - // 忽略连接失败回调 - } - }); - - if (StringUtils.hasText(proxyConfig.getUsername())) { - builder.authenticator(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - char[] password = proxyConfig.getPassword() != null ? proxyConfig.getPassword().toCharArray() : new char[0]; - return new PasswordAuthentication(proxyConfig.getUsername(), password); - } - }); - } - - return RestClient.builder() - .baseUrl(properties.getBaseUrl()) - .requestFactory(new JdkClientHttpRequestFactory(builder.build())) - .build(); - } - - public WecomToken getToken(String corpId, String secret, PortalProxyConfig proxyConfig) { - try { - WecomToken response = getClient(proxyConfig).get() - .uri(uriBuilder -> uriBuilder - .path("/cgi-bin/gettoken") - .queryParam("corpid", corpId) - .queryParam("corpsecret", secret) - .build()) - .retrieve() - .body(WecomToken.class); - return requireSuccess(response, "gettoken"); - } catch (RestClientException ex) { - throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 gettoken 接口失败", ex); - } - } - - public WecomAgentInfo getAgentInfo(String accessToken, String agentId, PortalProxyConfig proxyConfig) { - try { - WecomAgentInfo response = getClient(proxyConfig).get() - .uri(uriBuilder -> uriBuilder - .path("/cgi-bin/agent/get") - .queryParam("access_token", accessToken) - .queryParam("agentid", agentId) - .build()) - .retrieve() - .body(WecomAgentInfo.class); - return requireSuccess(response, "agent/get"); - } catch (RestClientException ex) { - throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 agent/get 接口失败", ex); - } - } - - public WecomSendResponse sendMessage(String accessToken, Object payload, PortalProxyConfig proxyConfig) { - try { - WecomSendResponse response = getClient(proxyConfig).post() - .uri(uriBuilder -> uriBuilder - .path("/cgi-bin/message/send") - .queryParam("access_token", accessToken) - .build()) - .contentType(MediaType.APPLICATION_JSON) - .body(payload) - .retrieve() - .body(WecomSendResponse.class); - if (response == null) { - throw new PortalException(PortalStatus.BAD_GATEWAY, "企业微信 message/send 响应为空"); - } - return response; - } catch (RestClientException ex) { - throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 message/send 接口失败", ex); - } - } - - public void testConnectivity(PortalProxyConfig proxyConfig) { - if (proxyConfig == null) { - return; - } - try { - // 尝试访问企业微信根域名,仅测试网络连通性 - getClient(proxyConfig).get() - .uri("/cgi-bin/gettoken") - .retrieve() - .toBodilessEntity(); - } catch (Exception ex) { - String msg = ex.getMessage(); - if (ex.getCause() != null) { - msg += " (" + ex.getCause().getMessage() + ")"; - } - throw new PortalException(PortalStatus.BAD_REQUEST, "代理连接测试失败: " + msg); - } - } - - private T requireSuccess( - T response, - String action - ) { - if (response == null) { - throw new PortalException(PortalStatus.BAD_GATEWAY, "企业微信 " + action + " 响应为空"); - } - if (!response.isSuccess()) { - if (Integer.valueOf(60020).equals(response.getErrcode())) { - String ip = "unknown"; - String errmsg = response.getErrmsg(); - if (errmsg != null && errmsg.contains("from ip: ")) { - int start = errmsg.indexOf("from ip: ") + 9; - int end = errmsg.indexOf(",", start); - if (end == -1) { - end = errmsg.length(); - } - ip = errmsg.substring(start, end).trim(); - } - throw new PortalException( - PortalStatus.BAD_REQUEST, - "企业微信 IP 白名单校验失败。请在企业微信应用设置的“企业可信 IP”列表中添加本服务器 IP [" + ip + "]。" - ); - } - throw new PortalException( - PortalStatus.BAD_REQUEST, - "企业微信 " + action + " 失败: " + response.getErrmsg() + " (" + response.getErrcode() + ")" - ); - } - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java deleted file mode 100644 index 6c44faf..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java +++ /dev/null @@ -1,68 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.StringReader; - -public class WecomMessageParser { - - public static WecomMessagePayload parse(String xml) { - WecomMessagePayload payload = new WecomMessagePayload(); - try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); - dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - dbf.setXIncludeAware(false); - dbf.setExpandEntityReferences(false); - - DocumentBuilder db = dbf.newDocumentBuilder(); - StringReader sr = new StringReader(xml); - InputSource is = new InputSource(sr); - Document document = db.parse(is); - - Element root = document.getDocumentElement(); - - payload.setToUserName(getElementValue(root, "ToUserName")); - payload.setFromUserName(getElementValue(root, "FromUserName")); - payload.setReceiveMsgType(getElementValue(root, "MsgType")); // 使用 receiveMsgType - payload.setContent(getElementValue(root, "Content")); - payload.setReceiveAgentId(getElementValue(root, "AgentID")); // 使用 receiveAgentId - payload.setEvent(getElementValue(root, "Event")); - payload.setEventKey(getElementValue(root, "EventKey")); - - // 图片消息字段 - payload.setPicUrl(getElementValue(root, "PicUrl")); - payload.setMediaId(getElementValue(root, "MediaId")); - - String createTime = getElementValue(root, "CreateTime"); - if (createTime != null && !createTime.isEmpty()) { - payload.setCreateTime(Long.parseLong(createTime)); - } - - String msgId = getElementValue(root, "MsgId"); - if (msgId != null && !msgId.isEmpty()) { - payload.setMsgId(Long.parseLong(msgId)); - } - - } catch (Exception e) { - e.printStackTrace(); - // 解析失败返回空对象或部分对象 - } - return payload; - } - - private static String getElementValue(Element root, String tagName) { - NodeList nodeList = root.getElementsByTagName(tagName); - if (nodeList != null && nodeList.getLength() > 0) { - return nodeList.item(0).getTextContent(); - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java deleted file mode 100644 index d3efdbe..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java +++ /dev/null @@ -1,93 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import java.util.List; - -@Data -public class WecomMessagePayload { - - // ========================================== - // 发送消息字段 (JSON) - // ========================================== - private String touser; - private String toparty; - private String totag; - private String msgtype; - private Long agentid; - private Integer safe; - - @JsonProperty("enable_id_trans") - private Integer enableIdTrans; - - @JsonProperty("enable_duplicate_check") - private Integer enableDuplicateCheck; - - @JsonProperty("duplicate_check_interval") - private Integer duplicateCheckInterval; - - private Text text; - private Markdown markdown; - private TextCard textcard; - private News news; - - // ========================================== - // 接收回调字段 (XML 解析后赋值) - // ========================================== - private String toUserName; - private String fromUserName; - private Long createTime; - // 注意:接收时的 MsgType 和发送时的 msgtype 可能大小写不同, - // 但通常我们可以复用 msgtype 字段,或者分开。 - // 为了不破坏现有发送逻辑,发送用 msgtype (全小写)。 - // 接收到的 XML MsgType 也是 "text" 等小写 (在 XML 值里),但标签是 PascalCase。 - // 这里我们额外定义字段用于接收,避免混淆 - private String receiveMsgType; - - private String content; // 接收到的文本内容 - private Long msgId; - private String receiveAgentId; // 接收到的 AgentID - private String event; - private String eventKey; - - // 图片消息字段 - private String picUrl; - private String mediaId; - - // ========================================== - // 内部类定义 (用于发送消息) - // ========================================== - - @Data - public static class Text { - private String content; - } - - @Data - public static class Markdown { - private String content; - } - - @Data - public static class TextCard { - private String title; - private String description; - private String url; - @JsonProperty("btntxt") - private String btnText; - } - - @Data - public static class News { - private List
articles; - } - - @Data - public static class Article { - private String title; - private String description; - private String url; - @JsonProperty("picurl") - private String picUrl; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java deleted file mode 100644 index ae481fc..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java +++ /dev/null @@ -1,32 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class WecomResponse { - - @JsonProperty("errcode") - private Integer errcode; - - @JsonProperty("errmsg") - private String errmsg; - - public Integer getErrcode() { - return errcode; - } - - public void setErrcode(Integer errcode) { - this.errcode = errcode; - } - - public String getErrmsg() { - return errmsg; - } - - public void setErrmsg(String errmsg) { - this.errmsg = errmsg; - } - - public boolean isSuccess() { - return errcode != null && errcode == 0; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java deleted file mode 100644 index 9c9e90f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class WecomSendResponse extends WecomResponse { - - @JsonProperty("invaliduser") - private String invalidUser; - - @JsonProperty("invalidparty") - private String invalidParty; - - @JsonProperty("invalidtag") - private String invalidTag; - - public String getInvalidUser() { - return invalidUser; - } - - public void setInvalidUser(String invalidUser) { - this.invalidUser = invalidUser; - } - - public String getInvalidParty() { - return invalidParty; - } - - public void setInvalidParty(String invalidParty) { - this.invalidParty = invalidParty; - } - - public String getInvalidTag() { - return invalidTag; - } - - public void setInvalidTag(String invalidTag) { - this.invalidTag = invalidTag; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java b/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java deleted file mode 100644 index ae36b3f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.manager.wecom; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class WecomToken extends WecomResponse { - - @JsonProperty("access_token") - private String accessToken; - - @JsonProperty("expires_in") - private Integer expiresIn; - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public Integer getExpiresIn() { - return expiresIn; - } - - public void setExpiresIn(Integer expiresIn) { - this.expiresIn = expiresIn; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java deleted file mode 100644 index 8ebdc65..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalAppApiKeyMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java deleted file mode 100644 index b92d0aa..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalCorpConfigMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java deleted file mode 100644 index ea13641..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalMessageLogMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java deleted file mode 100644 index e8ffd52..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalProxyConfigMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java deleted file mode 100644 index 3615920..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalSystemConfigMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java deleted file mode 100644 index 7a99fbc..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalUserMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java b/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java deleted file mode 100644 index 0600567..0000000 --- a/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.mapper.portal; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface PortalWecomAppMapper extends BaseMapper { -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java deleted file mode 100644 index 4c8d260..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java +++ /dev/null @@ -1,142 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.openapi; - -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageType; -import java.util.List; - -public class OpenApiMessageSendRequest { - - // Compatibility fields for v1 - private String target; - private String type; - - // v2 fields - private String toUser; - private String toParty; - private Boolean toAll; - private PortalMessageType msgType = PortalMessageType.TEXT; - private String content; - private String title; - private String description; - private String url; - private String btnText; - private List articles; - - public String getTarget() { - return target; - } - - public void setTarget(String target) { - this.target = target; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - if (type != null) { - try { - this.msgType = PortalMessageType.valueOf(type.toUpperCase()); - } catch (IllegalArgumentException e) { - // Ignore invalid type string, fallback to default or existing msgType - } - } - } - - public String getToUser() { - String effectiveUser = (toUser == null && target != null) ? target : toUser; - if ("@all".equalsIgnoreCase(effectiveUser)) { - return null; // Handled by getToAll() - } - return effectiveUser; - } - - public void setToUser(String toUser) { - this.toUser = toUser; - } - - public String getToParty() { - return toParty; - } - - public void setToParty(String toParty) { - this.toParty = toParty; - } - - public boolean isToAll() { - return Boolean.TRUE.equals(getToAll()); - } - - public Boolean getToAll() { - if (Boolean.TRUE.equals(toAll)) { - return true; - } - // Support "@all" in target or toUser - if ("@all".equalsIgnoreCase(target) || "@all".equalsIgnoreCase(toUser)) { - return true; - } - return false; - } - - public void setToAll(Boolean toAll) { - this.toAll = toAll; - } - - public PortalMessageType getMsgType() { - return msgType != null ? msgType : PortalMessageType.TEXT; - } - - public void setMsgType(PortalMessageType msgType) { - this.msgType = msgType; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getBtnText() { - return btnText; - } - - public void setBtnText(String btnText) { - this.btnText = btnText; - } - - public List getArticles() { - return articles; - } - - public void setArticles(List articles) { - this.articles = articles; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java deleted file mode 100644 index 00685f2..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java +++ /dev/null @@ -1,109 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.openapi; - -import java.util.List; - -public class PushRequest { - - private String target; - private String type; - private String title; - private String content; - private String url; - private String mediaId; - private List
articles; - - public String getTarget() { - return target; - } - - public void setTarget(String target) { - this.target = target; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getMediaId() { - return mediaId; - } - - public void setMediaId(String mediaId) { - this.mediaId = mediaId; - } - - public List
getArticles() { - return articles; - } - - public void setArticles(List
articles) { - this.articles = articles; - } - - public static class Article { - private String title; - private String description; - private String url; - private String picUrl; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getPicUrl() { - return picUrl; - } - - public void setPicUrl(String picUrl) { - this.picUrl = picUrl; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java deleted file mode 100644 index 524775b..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.Min; - -public class PortalAppApiKeyUpdateRequest { - - @Min(0) - private Integer rateLimitPerMinute; - - public Integer getRateLimitPerMinute() { - return rateLimitPerMinute; - } - - public void setRateLimitPerMinute(Integer rateLimitPerMinute) { - this.rateLimitPerMinute = rateLimitPerMinute; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java deleted file mode 100644 index d45f007..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; - -public class PortalAppCreateRequest { - - @NotBlank(message = "AgentId 不能为空") - private String agentId; - - @NotBlank(message = "Secret 不能为空") - private String secret; - - public String getAgentId() { - return agentId; - } - - public void setAgentId(String agentId) { - this.agentId = agentId; - } - - public String getSecret() { - return secret; - } - - public void setSecret(String secret) { - this.secret = secret; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java deleted file mode 100644 index 76ab441..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import lombok.Data; - -@Data -public class PortalAppUpdateRequest { - private String secret; - private String token; - private String encodingAesKey; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java deleted file mode 100644 index c5ac88f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; - -public class PortalCorpConfigRequest { - - @NotBlank(message = "CorpId is required") - private String corpId; - - public String getCorpId() { - return corpId; - } - - public void setCorpId(String corpId) { - this.corpId = corpId; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java deleted file mode 100644 index 96614ad..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Data -public class PortalInitRequest { - @NotBlank - private String username; - - @NotBlank - private String password; - - private boolean turnstileEnabled; - private String turnstileSiteKey; - private String turnstileSecretKey; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java deleted file mode 100644 index 1c98ac9..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; - -public class PortalLoginRequest { - - @NotBlank(message = "账号不能为空") - private String account; - - @NotBlank(message = "密码不能为空") - private String password; - - @NotBlank(message = "验证码不能为空") - private String captcha; - - public String getAccount() { - return account; - } - - public void setAccount(String account) { - this.account = account; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getCaptcha() { - return captcha; - } - - public void setCaptcha(String captcha) { - this.captcha = captcha; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java deleted file mode 100644 index ea5b275..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java +++ /dev/null @@ -1,151 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotNull; -import java.util.List; - -public class PortalMessageSendRequest { - - @NotNull(message = "应用 ID 不能为空") - private Long appId; - private String toUser; - private String toParty; - private Boolean toAll; - private PortalMessageType msgType = PortalMessageType.TEXT; - private String content; - private String title; - private String description; - private String url; - private String btnText; - private List articles; - - public Long getAppId() { - return appId; - } - - public void setAppId(Long appId) { - this.appId = appId; - } - - public String getToUser() { - return toUser; - } - - public void setToUser(String toUser) { - this.toUser = toUser; - } - - public String getToParty() { - return toParty; - } - - public void setToParty(String toParty) { - this.toParty = toParty; - } - - public boolean isToAll() { - return Boolean.TRUE.equals(toAll); - } - - public Boolean getToAll() { - return toAll; - } - - public void setToAll(Boolean toAll) { - this.toAll = toAll; - } - - public PortalMessageType getMsgType() { - return msgType != null ? msgType : PortalMessageType.TEXT; - } - - public void setMsgType(PortalMessageType msgType) { - this.msgType = msgType; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getBtnText() { - return btnText; - } - - public void setBtnText(String btnText) { - this.btnText = btnText; - } - - public List getArticles() { - return articles; - } - - public void setArticles(List articles) { - this.articles = articles; - } - - public static class PortalNewsArticle { - private String title; - private String description; - private String url; - private String picUrl; - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getPicUrl() { - return picUrl; - } - - public void setPicUrl(String picUrl) { - this.picUrl = picUrl; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java deleted file mode 100644 index 27fdb92..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -public enum PortalMessageType { - TEXT("text"), - TEXT_CARD("textcard"), - MARKDOWN("markdown"), - NEWS("news"); - - private final String value; - - PortalMessageType(String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java deleted file mode 100644 index fdefc75..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; - -public class PortalPasswordUpdateRequest { - - @NotBlank(message = "旧密码不能为空") - private String oldPassword; - - @NotBlank(message = "新密码不能为空") - private String newPassword; - - public String getOldPassword() { - return oldPassword; - } - - public void setOldPassword(String oldPassword) { - this.oldPassword = oldPassword; - } - - public String getNewPassword() { - return newPassword; - } - - public void setNewPassword(String newPassword) { - this.newPassword = newPassword; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java deleted file mode 100644 index e89f2b5..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Data -public class PortalProxyConfigRequest { - - @NotBlank(message = "服务器地址不能为空") - private String host; - - @NotNull(message = "端口不能为空") - @Min(value = 1, message = "端口范围无效") - @Max(value = 65535, message = "端口范围无效") - private Integer port; - - private String username; - - private String password; - - private String type = "HTTP"; - - private String exitIp; - - private Boolean active = true; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java b/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java deleted file mode 100644 index 0bbb7a5..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.model.dto.portal; - -import jakarta.validation.constraints.NotBlank; - -public class PortalRegisterRequest { - - @NotBlank(message = "账号不能为空") - private String account; - - @NotBlank(message = "密码不能为空") - private String password; - - public String getAccount() { - return account; - } - - public void setAccount(String account) { - this.account = account; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java deleted file mode 100644 index 9defdfb..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_app_api_key") -public class PortalAppApiKey { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("app_id") - private Long appId; - - @TableField("api_key_hash") - private String apiKeyHash; - - @TableField("api_key_plain") - private String apiKeyPlain; - - @TableField("rate_limit_per_minute") - private Integer rateLimitPerMinute; - - @TableField("created_at") - private Long createdAt; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java deleted file mode 100644 index c5ba1ec..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_corp_config") -public class PortalCorpConfig { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("user_id") - private Long userId; - - @TableField("corp_id") - private String corpId; - - @TableField("created_at") - private Long createdAt; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java deleted file mode 100644 index e457f65..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java +++ /dev/null @@ -1,58 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_message_log") -public class PortalMessageLog { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("user_id") - private Long userId; - - @TableField("app_id") - private Long appId; - - @TableField("agent_id") - private String agentId; - - @TableField("msg_type") - private String msgType; - - @TableField("to_user") - private String toUser; - - @TableField("to_party") - private String toParty; - - @TableField("to_all") - private Integer toAll; - - private String title; - - private String description; - - private String url; - - private String content; - - @TableField("request_json") - private String requestJson; - - @TableField("response_json") - private String responseJson; - - private Integer success; - - @TableField("error_message") - private String errorMessage; - - @TableField("created_at") - private Long createdAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java deleted file mode 100644 index 5bb1868..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_proxy_config") -public class PortalProxyConfig { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("user_id") - private Long userId; - - private String host; - - private Integer port; - - private String username; - - private String password; - - private String type; // HTTP, SOCKS5 - - @TableField("exit_ip") - private String exitIp; - - private Boolean active; - - @TableField("created_at") - private Long createdAt; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java deleted file mode 100644 index c4f4dcc..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_system_config") -public class PortalSystemConfig { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("config_key") - private String configKey; - - @TableField("config_value") - private String configValue; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java deleted file mode 100644 index 2a84e43..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_user") -public class PortalUser { - - @TableId(type = IdType.AUTO) - private Long id; - - private String account; - - @TableField("password_hash") - private String passwordHash; - - @TableField("created_at") - private Long createdAt; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java b/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java deleted file mode 100644 index 8552261..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.qingzhou.pushserver.model.entity.portal; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -@Data -@TableName("v2_wecom_app") -public class PortalWecomApp { - - @TableId(type = IdType.AUTO) - private Long id; - - @TableField("user_id") - private Long userId; - - @TableField("agent_id") - private String agentId; - - private String secret; - - private String token; - - @TableField("encoding_aes_key") - private String encodingAesKey; - - private String name; - - @TableField("avatar_url") - private String avatarUrl; - - private String description; - - @TableField("created_at") - private Long createdAt; - - @TableField("updated_at") - private Long updatedAt; -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java deleted file mode 100644 index 6af7e7b..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java +++ /dev/null @@ -1,83 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -import java.util.List; - -public class DashboardChartsResponse { - - private List trend; - private List distribution; - - public List getTrend() { - return trend; - } - - public void setTrend(List trend) { - this.trend = trend; - } - - public List getDistribution() { - return distribution; - } - - public void setDistribution(List distribution) { - this.distribution = distribution; - } - - public static class TrendPoint { - private String date; - private long count; - - public TrendPoint() { - } - - public TrendPoint(String date, long count) { - this.date = date; - this.count = count; - } - - public String getDate() { - return date; - } - - public void setDate(String date) { - this.date = date; - } - - public long getCount() { - return count; - } - - public void setCount(long count) { - this.count = count; - } - } - - public static class DistributionSlice { - private String name; - private long value; - - public DistributionSlice() { - } - - public DistributionSlice(String name, long value) { - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public long getValue() { - return value; - } - - public void setValue(long value) { - this.value = value; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java deleted file mode 100644 index 82c162c..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java +++ /dev/null @@ -1,50 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class DashboardLogResponse { - - private String time; - private String appName; - private String receiver; - private int status; - private String errorMsg; - - public String getTime() { - return time; - } - - public void setTime(String time) { - this.time = time; - } - - public String getAppName() { - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getReceiver() { - return receiver; - } - - public void setReceiver(String receiver) { - this.receiver = receiver; - } - - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - - public String getErrorMsg() { - return errorMsg; - } - - public void setErrorMsg(String errorMsg) { - this.errorMsg = errorMsg; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java deleted file mode 100644 index c697010..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class DashboardStatsResponse { - - private long todayTotal; - private double successRate; - private long activeApps; - private String lastErrorTime; - - public long getTodayTotal() { - return todayTotal; - } - - public void setTodayTotal(long todayTotal) { - this.todayTotal = todayTotal; - } - - public double getSuccessRate() { - return successRate; - } - - public void setSuccessRate(double successRate) { - this.successRate = successRate; - } - - public long getActiveApps() { - return activeApps; - } - - public void setActiveApps(long activeApps) { - this.activeApps = activeApps; - } - - public String getLastErrorTime() { - return lastErrorTime; - } - - public void setLastErrorTime(String lastErrorTime) { - this.lastErrorTime = lastErrorTime; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java deleted file mode 100644 index 81736a6..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java +++ /dev/null @@ -1,59 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class PortalAppApiKeyResponse { - - private Long appId; - private boolean hasKey; - private String apiKey; - private Integer rateLimitPerMinute; - private Long createdAt; - private Long updatedAt; - - public Long getAppId() { - return appId; - } - - public void setAppId(Long appId) { - this.appId = appId; - } - - public boolean isHasKey() { - return hasKey; - } - - public void setHasKey(boolean hasKey) { - this.hasKey = hasKey; - } - - public String getApiKey() { - return apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public Integer getRateLimitPerMinute() { - return rateLimitPerMinute; - } - - public void setRateLimitPerMinute(Integer rateLimitPerMinute) { - this.rateLimitPerMinute = rateLimitPerMinute; - } - - public Long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Long createdAt) { - this.createdAt = createdAt; - } - - public Long getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Long updatedAt) { - this.updatedAt = updatedAt; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java deleted file mode 100644 index 76f3c98..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java +++ /dev/null @@ -1,68 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class PortalAppResponse { - - private Long id; - private String agentId; - private String name; - private String avatarUrl; - private String description; - private Long createdAt; - private Long updatedAt; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getAgentId() { - return agentId; - } - - public void setAgentId(String agentId) { - this.agentId = agentId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getAvatarUrl() { - return avatarUrl; - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Long createdAt) { - this.createdAt = createdAt; - } - - public Long getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Long updatedAt) { - this.updatedAt = updatedAt; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java deleted file mode 100644 index d729420..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class PortalCorpResponse { - - private String corpId; - - public String getCorpId() { - return corpId; - } - - public void setCorpId(String corpId) { - this.corpId = corpId; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java deleted file mode 100644 index bb81764..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java +++ /dev/null @@ -1,28 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; - -public final class PortalMessageLogConverter { - - private PortalMessageLogConverter() { - } - - public static PortalMessageLogResponse toResponse(PortalMessageLog log) { - PortalMessageLogResponse response = new PortalMessageLogResponse(); - response.setId(log.getId()); - response.setAppId(log.getAppId()); - response.setAgentId(log.getAgentId()); - response.setMsgType(log.getMsgType()); - response.setToUser(log.getToUser()); - response.setToParty(log.getToParty()); - response.setToAll(log.getToAll() != null && log.getToAll() == 1); - response.setTitle(log.getTitle()); - response.setDescription(log.getDescription()); - response.setUrl(log.getUrl()); - response.setContent(log.getContent()); - response.setSuccess(log.getSuccess() != null && log.getSuccess() == 1); - response.setErrorMessage(log.getErrorMessage()); - response.setCreatedAt(log.getCreatedAt()); - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java deleted file mode 100644 index 63a1484..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java +++ /dev/null @@ -1,131 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class PortalMessageLogResponse { - - private Long id; - private Long appId; - private String agentId; - private String msgType; - private String toUser; - private String toParty; - private boolean toAll; - private String title; - private String description; - private String url; - private String content; - private boolean success; - private String errorMessage; - private Long createdAt; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Long getAppId() { - return appId; - } - - public void setAppId(Long appId) { - this.appId = appId; - } - - public String getAgentId() { - return agentId; - } - - public void setAgentId(String agentId) { - this.agentId = agentId; - } - - public String getMsgType() { - return msgType; - } - - public void setMsgType(String msgType) { - this.msgType = msgType; - } - - public String getToUser() { - return toUser; - } - - public void setToUser(String toUser) { - this.toUser = toUser; - } - - public String getToParty() { - return toParty; - } - - public void setToParty(String toParty) { - this.toParty = toParty; - } - - public boolean isToAll() { - return toAll; - } - - public void setToAll(boolean toAll) { - this.toAll = toAll; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public String getErrorMessage() { - return errorMessage; - } - - public void setErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public Long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Long createdAt) { - this.createdAt = createdAt; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java deleted file mode 100644 index 9ce5b5b..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -import java.util.List; - -public class PortalPageResponse { - - private List records; - private long total; - private int page; - private int pageSize; - - public PortalPageResponse() { - } - - public PortalPageResponse(List records, long total, int page, int pageSize) { - this.records = records; - this.total = total; - this.page = page; - this.pageSize = pageSize; - } - - public static PortalPageResponse of(List records, long total, int page, int pageSize) { - return new PortalPageResponse<>(records, total, page, pageSize); - } - - public List getRecords() { - return records; - } - - public void setRecords(List records) { - this.records = records; - } - - public long getTotal() { - return total; - } - - public void setTotal(long total) { - this.total = total; - } - - public int getPage() { - return page; - } - - public void setPage(int page) { - this.page = page; - } - - public int getPageSize() { - return pageSize; - } - - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java deleted file mode 100644 index f8be8c9..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import lombok.Data; - -@Data -public class PortalProxyConfigResponse { - private Long id; - private String host; - private Integer port; - private String username; - private String password; - private String type; - private String exitIp; - private Boolean active; - private Long createdAt; - private Long updatedAt; - - public static PortalProxyConfigResponse from(PortalProxyConfig config) { - if (config == null) { - return null; - } - PortalProxyConfigResponse response = new PortalProxyConfigResponse(); - response.setId(config.getId()); - response.setHost(config.getHost()); - response.setPort(config.getPort()); - response.setUsername(config.getUsername()); - response.setPassword(config.getPassword()); - response.setType(config.getType()); - response.setExitIp(config.getExitIp()); - response.setActive(config.getActive()); - response.setCreatedAt(config.getCreatedAt()); - response.setUpdatedAt(config.getUpdatedAt()); - return response; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java b/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java deleted file mode 100644 index 53f37af..0000000 --- a/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -package dev.qingzhou.pushserver.model.vo.portal; - -public class PortalUserResponse { - - private Long id; - private String account; - private Long createdAt; - private Long updatedAt; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getAccount() { - return account; - } - - public void setAccount(String account) { - this.account = account; - } - - public Long getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(Long createdAt) { - this.createdAt = createdAt; - } - - public Long getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(Long updatedAt) { - this.updatedAt = updatedAt; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java b/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java deleted file mode 100644 index 1985211..0000000 --- a/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.qingzhou.pushserver.security; - -import dev.qingzhou.pushserver.service.SystemConfigService; -import java.util.Map; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClient; - -@Service -public class CaptchaService { - - private static final String VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; - private final SystemConfigService configService; - private final RestClient restClient; - - public CaptchaService(SystemConfigService configService) { - this.configService = configService; - this.restClient = RestClient.create(); - } - - public void validate(String input) { - if (!configService.isTurnstileEnabled()) { - return; - } - - if (!StringUtils.hasText(input)) { - throw new BadCredentialsException("验证码 Token 不能为空"); - } - - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("secret", configService.getTurnstileSecretKey()); - formData.add("response", input); - - Map result = restClient.post() - .uri(VERIFY_URL) - .body(formData) - .retrieve() - .body(Map.class); - - if (result == null || !Boolean.TRUE.equals(result.get("success"))) { - throw new BadCredentialsException("验证码校验失败"); - } - } -} \ No newline at end of file diff --git a/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java b/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java deleted file mode 100644 index 65cc2bf..0000000 --- a/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java +++ /dev/null @@ -1,56 +0,0 @@ -package dev.qingzhou.pushserver.security; - -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.stereotype.Component; - -/** - * Simple in-memory fixed-window rate limiter keyed by API key record. - */ -@Component -public class PortalAppApiKeyRateLimiter { - - private static final long WINDOW_MS = 60_000L; - - private static class WindowCounter { - private long windowStart = System.currentTimeMillis(); - private final AtomicInteger count = new AtomicInteger(); - - synchronized boolean tryAcquire(int limit) { - long now = System.currentTimeMillis(); - if (now - windowStart >= WINDOW_MS) { - windowStart = now; - count.set(0); - } - int current = count.incrementAndGet(); - return current <= limit; - } - } - - private final Map buckets = new ConcurrentHashMap<>(); - - public void check(PortalAppApiKey apiKey) { - Integer limit = apiKey.getRateLimitPerMinute(); - if (limit == null || limit <= 0) { - return; - } - Long appId = apiKey.getAppId(); - if (appId == null) { - throw new PortalException(PortalStatus.BAD_REQUEST, "API Key 记录无效"); - } - WindowCounter counter = buckets.computeIfAbsent(appId, ignored -> new WindowCounter()); - if (!counter.tryAcquire(limit)) { - throw new PortalException(PortalStatus.TOO_MANY_REQUESTS, "API Key 速率限制已超出"); - } - } - - public void evict(Long appId) { - if (appId != null) { - buckets.remove(appId); - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java b/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java deleted file mode 100644 index 43a33d6..0000000 --- a/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.qingzhou.pushserver.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; - -import org.jspecify.annotations.NonNull; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -// 移除不需要的 import -// import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; - -public class PortalJsonLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { - - private final ObjectMapper objectMapper; - private final CaptchaService captchaService; - - public PortalJsonLoginAuthenticationFilter( - ObjectMapper objectMapper, - CaptchaService captchaService - ) { - super("/api/login"); - this.objectMapper = objectMapper; - this.captchaService = captchaService; - } - - @Override - public Authentication attemptAuthentication( - @NonNull HttpServletRequest request, - @NonNull HttpServletResponse response - ) throws AuthenticationException { - if (!isJsonRequest(request)) { - throw new AuthenticationServiceException("不支持的内容类型"); - } - PortalLoginRequest loginRequest; - try { - loginRequest = objectMapper.readValue(request.getInputStream(), PortalLoginRequest.class); - } catch (IOException ex) { - ex.printStackTrace(); - throw new AuthenticationServiceException("无效的登录参数", ex); - } - - String account = loginRequest.getAccount(); - String password = loginRequest.getPassword(); - String captcha = loginRequest.getCaptcha(); - - if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { - throw new AuthenticationServiceException("账号和密码不能为空"); - } - - captchaService.validate(captcha); - UsernamePasswordAuthenticationToken authRequest = - new UsernamePasswordAuthenticationToken(account, password); - authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); - - return this.getAuthenticationManager().authenticate(authRequest); - } - - private boolean isJsonRequest(HttpServletRequest request) { - String contentType = request.getContentType(); - return contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE); - } -} \ No newline at end of file diff --git a/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java b/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java deleted file mode 100644 index 9d37f9e..0000000 --- a/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java +++ /dev/null @@ -1,61 +0,0 @@ -package dev.qingzhou.pushserver.security; - -import java.util.Collection; -import java.util.List; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -public class PortalUserDetails implements UserDetails { - - private final Long userId; - private final String username; - private final String password; - private final List authorities; - - public PortalUserDetails(Long userId, String username, String password) { - this.userId = userId; - this.username = username; - this.password = password; - this.authorities = List.of(new SimpleGrantedAuthority("ROLE_USER")); - } - - public Long getUserId() { - return userId; - } - - @Override - public Collection getAuthorities() { - return authorities; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java b/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java deleted file mode 100644 index 036a3bb..0000000 --- a/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.qingzhou.pushserver.security; - -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import dev.qingzhou.pushserver.service.PortalUserService; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalUserDetailsService implements UserDetailsService { - - private final PortalUserService portalUserService; - - public PortalUserDetailsService(PortalUserService portalUserService) { - this.portalUserService = portalUserService; - } - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - if (!StringUtils.hasText(username)) { - throw new UsernameNotFoundException("Account is required"); - } - PortalUser user = portalUserService.findByAccount(username); - if (user == null) { - throw new UsernameNotFoundException("User not found"); - } - return new PortalUserDetails(user.getId(), user.getAccount(), user.getPasswordHash()); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java b/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java deleted file mode 100644 index 2716666..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; -import java.util.List; - -public interface DashboardService { - - DashboardStatsResponse fetchStats(Long userId); - - DashboardChartsResponse fetchCharts(Long userId); - - List fetchRecentLogs(Long userId, int limit); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java deleted file mode 100644 index 0e9cb32..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import dev.qingzhou.pushserver.manager.wecom.WecomToken; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; - -public interface PortalAccessTokenService { - - WecomToken fetchToken(String corpId, String secret, PortalProxyConfig proxyConfig); - - String getToken(Long appId, String corpId, String secret, PortalProxyConfig proxyConfig); - - void evict(Long appId); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java deleted file mode 100644 index 1c07b6f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; - -public interface PortalAppApiKeyService extends IService { - - PortalAppApiKey rotateKey(Long userId, Long appId); - - PortalAppApiKey findByAppId(Long userId, Long appId); - - void removeByAppId(Long appId); - - PortalAppApiKey updateRateLimit(Long userId, Long appId, Integer rateLimitPerMinute); - - AppAuthContext requireAppByApiKey(String apiKey); - - record AppAuthContext(PortalAppApiKey apiKey, PortalWecomApp app) { - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java deleted file mode 100644 index b50eed0..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; - -public interface PortalCorpConfigService extends IService { - - PortalCorpConfig getByUserId(Long userId); - - PortalCorpConfig requireByUserId(Long userId); - - PortalCorpConfig upsert(Long userId, String corpId); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java deleted file mode 100644 index c483508..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; -import java.util.List; - -public interface PortalMessageLogService extends IService { - - List listRecent(Long userId, int limit, Long appId, Boolean success); - - PortalPageResponse pageLogs(Long userId, Long appId, Boolean success, int page, int pageSize); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java deleted file mode 100644 index 32dc980..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; - -public interface PortalMessageService { - - PortalMessageLog send(Long userId, PortalMessageSendRequest request); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java deleted file mode 100644 index b277077..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; - -public interface PortalProxyConfigService extends IService { - - PortalProxyConfig getByUserId(Long userId); - - PortalProxyConfig upsert(Long userId, PortalProxyConfigRequest request); - - void deleteByUserId(Long userId); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java deleted file mode 100644 index e1a2616..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; - -public interface PortalUserService extends IService { - - PortalUser register(String account, String password); - - PortalUser authenticate(String account, String password); - - PortalUser findByAccount(String account); - - void updatePassword(Long userId, String oldPassword, String newPassword); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java b/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java deleted file mode 100644 index 0909104..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java +++ /dev/null @@ -1,20 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import com.baomidou.mybatisplus.extension.service.IService; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import java.util.List; - -public interface PortalWecomAppService extends IService { - - PortalWecomApp addApp(Long userId, String agentId, String secret); - - List listByUser(Long userId); - - PortalWecomApp requireByUser(Long userId, Long appId); - - PortalWecomApp updateApp(Long userId, Long appId, String secret, String token, String encodingAesKey); - - PortalWecomApp syncApp(Long userId, Long appId); - - void deleteApp(Long userId, Long appId); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/PushService.java b/src/main/java/dev/qingzhou/pushserver/service/PushService.java deleted file mode 100644 index 59a7c78..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/PushService.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.qingzhou.pushserver.service; - -import dev.qingzhou.push.core.model.PushResult; -import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; - -public interface PushService { - - PushResult push(PushRequest request); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java b/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java deleted file mode 100644 index 8b4ec41..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.qingzhou.pushserver.service; - -public interface SystemConfigService { - - String get(String key); - String get(String key, String defaultValue); - void set(String key, String value); - - // Helper methods for Turnstile - boolean isTurnstileEnabled(); - String getTurnstileSiteKey(); - String getTurnstileSecretKey(); - void setTurnstileConfig(boolean enabled, String siteKey, String secretKey); -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java deleted file mode 100644 index 6e8688e..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java +++ /dev/null @@ -1,243 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; -import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; -import dev.qingzhou.pushserver.service.DashboardService; -import dev.qingzhou.pushserver.service.PortalMessageLogService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.springframework.stereotype.Service; - -@Service -public class DashboardServiceImpl implements DashboardService { - - private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("MM-dd"); - private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - private static final ZoneId ZONE = ZoneId.systemDefault(); - - private final PortalMessageLogService messageLogService; - private final PortalWecomAppService appService; - - public DashboardServiceImpl(PortalMessageLogService messageLogService, PortalWecomAppService appService) { - this.messageLogService = messageLogService; - this.appService = appService; - } - - @Override - public DashboardStatsResponse fetchStats(Long userId) { - long startOfDay = atStartOfDayMillis(0); - - long todayTotal = countLogs(userId, startOfDay, null); - long todaySuccess = countLogs(userId, startOfDay, 1); - double successRate = todayTotal == 0 ? 100.0 : todaySuccess * 100.0 / todayTotal; - - long activeApps = appService.count(new QueryWrapper() - .eq("user_id", userId)); - - PortalMessageLog lastError = messageLogService.getOne(new QueryWrapper() - .eq("user_id", userId) - .ge("created_at", startOfDay) - .eq("success", 0) - .orderByDesc("created_at") - .last("limit 1"), false); - String lastErrorTime = null; - if (lastError != null && lastError.getCreatedAt() != null) { - lastErrorTime = Instant.ofEpochMilli(lastError.getCreatedAt()) - .atZone(ZONE) - .toLocalTime() - .format(DateTimeFormatter.ofPattern("HH:mm:ss")); - } - - DashboardStatsResponse response = new DashboardStatsResponse(); - response.setTodayTotal(todayTotal); - response.setSuccessRate(successRate); - response.setActiveApps(activeApps); - response.setLastErrorTime(lastErrorTime); - return response; - } - - @Override - public DashboardChartsResponse fetchCharts(Long userId) { - Map appNames = loadAppNames(userId); - - DashboardChartsResponse response = new DashboardChartsResponse(); - response.setTrend(buildTrend(userId)); - response.setDistribution(buildDistribution(userId, appNames)); - return response; - } - - @Override - public List fetchRecentLogs(Long userId, int limit) { - int safeLimit = Math.max(1, Math.min(limit, 100)); - Map appNames = loadAppNames(userId); - - List logs = messageLogService.list(new QueryWrapper() - .eq("user_id", userId) - .orderByDesc("created_at") - .last("limit " + safeLimit)); - - List responses = new ArrayList<>(logs.size()); - for (PortalMessageLog log : logs) { - DashboardLogResponse item = new DashboardLogResponse(); - item.setTime(formatDateTime(log.getCreatedAt())); - item.setAppName(resolveAppName(appNames, log.getAppId(), log.getAgentId())); - item.setReceiver(resolveReceiver(log)); - item.setStatus(log.getSuccess() != null && log.getSuccess() == 1 ? 1 : 0); - item.setErrorMsg(log.getErrorMessage()); - responses.add(item); - } - return responses; - } - - private long countLogs(Long userId, long startTime, Integer success) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId) - .ge("created_at", startTime); - if (success != null) { - wrapper.eq("success", success); - } - return messageLogService.count(wrapper); - } - - private List buildTrend(Long userId) { - LocalDate today = LocalDate.now(ZONE); - LocalDate startDate = today.minusDays(6); - long startMillis = startDate.atStartOfDay(ZONE).toInstant().toEpochMilli(); - - Map counts = new LinkedHashMap<>(); - for (int i = 6; i >= 0; i--) { - LocalDate date = today.minusDays(i); - counts.put(date, 0L); - } - - // Optimize: Only select created_at - List logs = messageLogService.list(new QueryWrapper() - .select("created_at") - .eq("user_id", userId) - .ge("created_at", startMillis)); - - for (PortalMessageLog log : logs) { - if (log.getCreatedAt() == null) { - continue; - } - LocalDate date = Instant.ofEpochMilli(log.getCreatedAt()).atZone(ZONE).toLocalDate(); - // Ensure the date is within our 7-day window - if (!date.isBefore(startDate) && !date.isAfter(today)) { - counts.put(date, counts.getOrDefault(date, 0L) + 1); - } - } - - List trend = new ArrayList<>(counts.size()); - for (Map.Entry entry : counts.entrySet()) { - trend.add(new DashboardChartsResponse.TrendPoint(entry.getKey().format(DATE_FMT), entry.getValue())); - } - return trend; - } - - private List buildDistribution(Long userId, Map appNames) { - long startMillis = atStartOfDayMillis(29); - List logs = messageLogService.list(new QueryWrapper() - .select("app_id") // Optimize: Only select app_id - .eq("user_id", userId) - .ge("created_at", startMillis)); - Map counts = new HashMap<>(); - for (PortalMessageLog log : logs) { - if (log.getAppId() == null) { - continue; - } - counts.put(log.getAppId(), counts.getOrDefault(log.getAppId(), 0L) + 1); - } - - List slices = counts.entrySet().stream() - .map(e -> new DashboardChartsResponse.DistributionSlice( - resolveAppName(appNames, e.getKey(), null), - e.getValue())) - .sorted(Comparator.comparingLong(DashboardChartsResponse.DistributionSlice::getValue).reversed()) - .collect(Collectors.toList()); - - if (slices.size() > 5) { - List top = new ArrayList<>(slices.subList(0, 5)); - long other = slices.subList(5, slices.size()).stream() - .mapToLong(DashboardChartsResponse.DistributionSlice::getValue) - .sum(); - top.add(new DashboardChartsResponse.DistributionSlice("Other", other)); - slices = top; - } - - return slices; - } - - private Map loadAppNames(Long userId) { - List apps = appService.list(new QueryWrapper() - .eq("user_id", userId)); - if (apps == null || apps.isEmpty()) { - return Collections.emptyMap(); - } - Map map = new HashMap<>(); - for (PortalWecomApp app : apps) { - String name = app.getName(); - if (name == null || name.isBlank()) { - name = app.getAgentId(); - } - map.put(app.getId(), name); - } - return map; - } - - private String resolveAppName(Map appNames, Long appId, String agentId) { - if (appId != null && appNames.containsKey(appId)) { - return appNames.get(appId); - } - if (agentId != null) { - return agentId; - } - return "Unknown app"; - } - - private String resolveReceiver(PortalMessageLog log) { - if (log.getToAll() != null && log.getToAll() == 1) { - return "ALL"; - } - List parts = new ArrayList<>(2); - if (log.getToUser() != null && !log.getToUser().isBlank()) { - parts.add(log.getToUser()); - } - if (log.getToParty() != null && !log.getToParty().isBlank()) { - parts.add(log.getToParty()); - } - if (parts.isEmpty()) { - return "--"; - } - return String.join(" / ", parts); - } - - private String formatDateTime(Long millis) { - if (millis == null) { - return null; - } - return Instant.ofEpochMilli(millis).atZone(ZONE).toLocalDateTime().format(DATETIME_FMT); - } - - private long atStartOfDayMillis(int daysAgo) { - return LocalDate.now(ZONE) - .minusDays(daysAgo) - .atStartOfDay(ZONE) - .toInstant() - .toEpochMilli(); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java deleted file mode 100644 index 8c77625..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java +++ /dev/null @@ -1,64 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; -import dev.qingzhou.pushserver.manager.wecom.WecomToken; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import dev.qingzhou.pushserver.service.PortalAccessTokenService; -import java.util.concurrent.TimeUnit; -import org.springframework.stereotype.Component; - -@Component -public class PortalAccessTokenServiceImpl implements PortalAccessTokenService { - - private static final int DEFAULT_EXPIRES_IN = 7200; - private static final long EXPIRE_BUFFER_MILLIS = 60_000L; - - private final WecomApiClient wecomApiClient; - private final Cache cache; - - public PortalAccessTokenServiceImpl(WecomApiClient wecomApiClient) { - this.wecomApiClient = wecomApiClient; - this.cache = Caffeine.newBuilder() - .expireAfterWrite(2, TimeUnit.HOURS) - .build(); - } - - @Override - public WecomToken fetchToken(String corpId, String secret, PortalProxyConfig proxyConfig) { - return wecomApiClient.getToken(corpId, secret, proxyConfig); - } - - @Override - public String getToken(Long appId, String corpId, String secret, PortalProxyConfig proxyConfig) { - CachedToken cached = cache.getIfPresent(appId); - long now = System.currentTimeMillis(); - if (cached != null && cached.expireAtMillis > now) { - return cached.value; - } - WecomToken token = fetchToken(corpId, secret, proxyConfig); - int expiresIn = token.getExpiresIn() != null ? token.getExpiresIn() : DEFAULT_EXPIRES_IN; - long expireAt = now + TimeUnit.SECONDS.toMillis(expiresIn) - EXPIRE_BUFFER_MILLIS; - if (expireAt < now) { - expireAt = now; - } - cache.put(appId, new CachedToken(token.getAccessToken(), expireAt)); - return token.getAccessToken(); - } - - @Override - public void evict(Long appId) { - cache.invalidate(appId); - } - - private static class CachedToken { - private final String value; - private final long expireAtMillis; - - private CachedToken(String value, long expireAtMillis) { - this.value = value; - this.expireAtMillis = expireAtMillis; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java deleted file mode 100644 index 9fce699..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java +++ /dev/null @@ -1,130 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.service.PortalAppApiKeyService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Base64; -import java.util.HexFormat; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalAppApiKeyServiceImpl extends ServiceImpl - implements PortalAppApiKeyService { - - private final PortalWecomAppService appService; - private final PortalAppApiKeyRateLimiter rateLimiter; - private final SecureRandom random = new SecureRandom(); - - public PortalAppApiKeyServiceImpl(PortalWecomAppService appService, PortalAppApiKeyRateLimiter rateLimiter) { - this.appService = appService; - this.rateLimiter = rateLimiter; - } - - @Override - public PortalAppApiKey rotateKey(Long userId, Long appId) { - PortalWecomApp app = appService.requireByUser(userId, appId); - String rawKey = generateKey(); - String hash = hashKey(rawKey); - PortalAppApiKey existing = getOne(new QueryWrapper() - .eq("app_id", app.getId())); - long now = System.currentTimeMillis(); - if (existing == null) { - PortalAppApiKey record = new PortalAppApiKey(); - record.setAppId(app.getId()); - record.setApiKeyHash(hash); - record.setApiKeyPlain(rawKey); - record.setRateLimitPerMinute(0); - record.setCreatedAt(now); - record.setUpdatedAt(now); - save(record); - return record; - } - existing.setApiKeyHash(hash); - existing.setApiKeyPlain(rawKey); - existing.setUpdatedAt(now); - updateById(existing); - return existing; - } - - @Override - public PortalAppApiKey findByAppId(Long userId, Long appId) { - PortalWecomApp app = appService.requireByUser(userId, appId); - return getOne(new QueryWrapper() - .eq("app_id", app.getId())); - } - - @Override - public void removeByAppId(Long appId) { - remove(new QueryWrapper() - .eq("app_id", appId)); - rateLimiter.evict(appId); - } - - @Override - public PortalAppApiKey updateRateLimit(Long userId, Long appId, Integer rateLimitPerMinute) { - if (rateLimitPerMinute != null && rateLimitPerMinute < 0) { - throw new PortalException(PortalStatus.BAD_REQUEST, "每分钟速率限制必须大于等于 0"); - } - PortalWecomApp app = appService.requireByUser(userId, appId); - PortalAppApiKey record = getOne(new QueryWrapper() - .eq("app_id", app.getId())); - if (record == null) { - throw new PortalException(PortalStatus.NOT_FOUND, "未找到 API Key"); - } - record.setRateLimitPerMinute(rateLimitPerMinute == null ? 0 : rateLimitPerMinute); - record.setUpdatedAt(System.currentTimeMillis()); - updateById(record); - return record; - } - - @Override - public AppAuthContext requireAppByApiKey(String apiKey) { - if (!StringUtils.hasText(apiKey)) { - throw new PortalException(PortalStatus.UNAUTHORIZED, "缺少 API Key"); - } - String hash = hashKey(apiKey.trim()); - PortalAppApiKey record = getOne(new QueryWrapper() - .eq("api_key_hash", hash)); - if (record == null) { - throw new PortalException(PortalStatus.UNAUTHORIZED, "无效的 API Key"); - } - PortalWecomApp app = appService.getById(record.getAppId()); - if (app == null) { - throw new PortalException(PortalStatus.UNAUTHORIZED, "无效的 API Key"); - } - rateLimiter.check(record); - return new AppAuthContext(record, app); - } - - private String generateKey() { - byte[] bytes = new byte[32]; - random.nextBytes(bytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - } - - private String hashKey(String key) { - MessageDigest digest = getDigest(); - byte[] hashed = digest.digest(key.getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(hashed); - } - - private MessageDigest getDigest() { - try { - return MessageDigest.getInstance("SHA-256"); - } catch (Exception ex) { - throw new IllegalStateException("SHA-256 not available", ex); - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java deleted file mode 100644 index 882cfc8..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import dev.qingzhou.pushserver.service.PortalCorpConfigService; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalCorpConfigServiceImpl extends ServiceImpl implements PortalCorpConfigService { - - @Override - public PortalCorpConfig getByUserId(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - return getOne(wrapper); - } - - @Override - public PortalCorpConfig requireByUserId(Long userId) { - PortalCorpConfig config = getByUserId(userId); - if (config == null) { - throw new PortalException(PortalStatus.BAD_REQUEST, "企业配置未设置"); - } - return config; - } - - @Override - public PortalCorpConfig upsert(Long userId, String corpId) { - if (!StringUtils.hasText(corpId)) { - throw new PortalException(PortalStatus.BAD_REQUEST, "CorpId 不能为空"); - } - PortalCorpConfig config = getByUserId(userId); - long now = System.currentTimeMillis(); - if (config == null) { - config = new PortalCorpConfig(); - config.setUserId(userId); - config.setCorpId(corpId.trim()); - config.setCreatedAt(now); - config.setUpdatedAt(now); - save(config); - } else { - config.setCorpId(corpId.trim()); - config.setUpdatedAt(now); - updateById(config); - } - return config; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java deleted file mode 100644 index e047c78..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.service.PortalMessageLogService; -import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; -import java.util.List; -import org.springframework.stereotype.Service; - -@Service -public class PortalMessageLogServiceImpl extends ServiceImpl implements PortalMessageLogService { - - @Override - public List listRecent(Long userId, int limit, Long appId, Boolean success) { - int safeLimit = Math.max(1, Math.min(limit, 200)); - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId) - .orderByDesc("created_at") - .last("limit " + safeLimit); - if (appId != null) { - wrapper.eq("app_id", appId); - } - if (success != null) { - wrapper.eq("success", success ? 1 : 0); - } - return list(wrapper); - } - - @Override - public PortalPageResponse pageLogs(Long userId, Long appId, Boolean success, int page, int pageSize) { - int safePage = Math.max(1, page); - int safePageSize = Math.max(1, Math.min(pageSize, 200)); - long offset = (long) (safePage - 1) * safePageSize; - - QueryWrapper dataWrapper = new QueryWrapper<>(); - dataWrapper.eq("user_id", userId) - .orderByDesc("created_at") - .last("limit " + safePageSize + " offset " + offset); - if (appId != null) { - dataWrapper.eq("app_id", appId); - } - if (success != null) { - dataWrapper.eq("success", success ? 1 : 0); - } - List records = list(dataWrapper); - - QueryWrapper countWrapper = new QueryWrapper<>(); - countWrapper.eq("user_id", userId); - if (appId != null) { - countWrapper.eq("app_id", appId); - } - if (success != null) { - countWrapper.eq("success", success ? 1 : 0); - } - long total = count(countWrapper); - - return PortalPageResponse.of(records, total, safePage, safePageSize); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java deleted file mode 100644 index b96bfe0..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java +++ /dev/null @@ -1,238 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.fasterxml.jackson.databind.ObjectMapper; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; -import dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload; -import dev.qingzhou.pushserver.manager.wecom.WecomSendResponse; -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; -import dev.qingzhou.pushserver.model.dto.portal.PortalMessageType; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.service.PortalAccessTokenService; -import dev.qingzhou.pushserver.service.PortalCorpConfigService; -import dev.qingzhou.pushserver.service.PortalMessageLogService; -import dev.qingzhou.pushserver.service.PortalMessageService; -import dev.qingzhou.pushserver.service.PortalProxyConfigService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import java.util.ArrayList; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalMessageServiceImpl implements PortalMessageService { - - private final PortalWecomAppService appService; - private final PortalCorpConfigService corpConfigService; - private final PortalAccessTokenService accessTokenService; - private final WecomApiClient wecomApiClient; - private final PortalMessageLogService messageLogService; - private final ObjectMapper objectMapper; - private final PortalProxyConfigService proxyConfigService; - - public PortalMessageServiceImpl( - PortalWecomAppService appService, - PortalCorpConfigService corpConfigService, - PortalAccessTokenService accessTokenService, - WecomApiClient wecomApiClient, - PortalMessageLogService messageLogService, - ObjectMapper objectMapper, - PortalProxyConfigService proxyConfigService - ) { - this.appService = appService; - this.corpConfigService = corpConfigService; - this.accessTokenService = accessTokenService; - this.wecomApiClient = wecomApiClient; - this.messageLogService = messageLogService; - this.objectMapper = objectMapper; - this.proxyConfigService = proxyConfigService; - } - - @Override - public PortalMessageLog send(Long userId, PortalMessageSendRequest request) { - PortalWecomApp app = appService.requireByUser(userId, request.getAppId()); - PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); - PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); - - String accessToken = accessTokenService.getToken(app.getId(), corpConfig.getCorpId(), app.getSecret(), proxyConfig); - WecomMessagePayload payload = buildPayload(app, request); - String requestJson = toJson(payload); - WecomSendResponse response = null; - String responseJson = null; - String errorMessage = null; - boolean success = false; - PortalMessageLog log = null; - try { - response = wecomApiClient.sendMessage(accessToken, payload, proxyConfig); - responseJson = toJson(response); - success = response.isSuccess(); - if (!success) { - errorMessage = response.getErrmsg(); - } - } catch (PortalException ex) { - errorMessage = ex.getMessage(); - throw ex; - } catch (Exception ex) { - errorMessage = ex.getMessage(); - throw new PortalException(PortalStatus.BAD_GATEWAY, "发送消息失败", ex); - } finally { - log = buildLog(userId, app, request, requestJson, responseJson, success, errorMessage); - messageLogService.save(log); - } - if (!success && response != null) { - throw new PortalException( - PortalStatus.BAD_REQUEST, - "企业微信发送失败: " + response.getErrmsg() + " (" + response.getErrcode() + ")" - ); - } - return log; - } - - private WecomMessagePayload buildPayload(PortalWecomApp app, PortalMessageSendRequest request) { - WecomMessagePayload payload = new WecomMessagePayload(); - payload.setMsgtype(request.getMsgType().getValue()); - payload.setAgentid(parseAgentId(app.getAgentId())); - if (request.isToAll()) { - payload.setTouser("@all"); - } else { - payload.setTouser(normalizeTarget(request.getToUser())); - payload.setToparty(normalizeTarget(request.getToParty())); - } - if (!request.isToAll() - && !StringUtils.hasText(payload.getTouser()) - && !StringUtils.hasText(payload.getToparty())) { - throw new PortalException(PortalStatus.BAD_REQUEST, "接收者不能为空"); - } - switch (request.getMsgType()) { - case TEXT -> payload.setText(buildText(request)); - case MARKDOWN -> payload.setMarkdown(buildMarkdown(request)); - case TEXT_CARD -> payload.setTextcard(buildTextCard(request)); - case NEWS -> payload.setNews(buildNews(request)); - default -> throw new PortalException(PortalStatus.BAD_REQUEST, "不支持的消息类型"); - } - return payload; - } - - private WecomMessagePayload.Text buildText(PortalMessageSendRequest request) { - String content = requireText(request.getContent(), "content"); - WecomMessagePayload.Text text = new WecomMessagePayload.Text(); - text.setContent(content); - return text; - } - - private WecomMessagePayload.Markdown buildMarkdown(PortalMessageSendRequest request) { - String content = requireText(request.getContent(), "content"); - WecomMessagePayload.Markdown markdown = new WecomMessagePayload.Markdown(); - markdown.setContent(content); - return markdown; - } - - private WecomMessagePayload.TextCard buildTextCard(PortalMessageSendRequest request) { - String title = requireText(request.getTitle(), "title"); - String description = requireText(request.getDescription(), "description"); - String url = requireText(request.getUrl(), "url"); - WecomMessagePayload.TextCard card = new WecomMessagePayload.TextCard(); - card.setTitle(title); - card.setDescription(description); - card.setUrl(url); - card.setBtnText(request.getBtnText()); - return card; - } - - private WecomMessagePayload.News buildNews(PortalMessageSendRequest request) { - List items = request.getArticles(); - if (items == null || items.isEmpty()) { - throw new PortalException(PortalStatus.BAD_REQUEST, "articles 不能为空"); - } - List articles = new ArrayList<>(items.size()); - for (PortalMessageSendRequest.PortalNewsArticle item : items) { - if (item == null) { - throw new PortalException(PortalStatus.BAD_REQUEST, "article 不能为空"); - } - WecomMessagePayload.Article article = new WecomMessagePayload.Article(); - article.setTitle(requireText(item.getTitle(), "articles.title")); - article.setUrl(requireText(item.getUrl(), "articles.url")); - if (StringUtils.hasText(item.getDescription())) { - article.setDescription(item.getDescription().trim()); - } - if (StringUtils.hasText(item.getPicUrl())) { - article.setPicUrl(item.getPicUrl().trim()); - } - articles.add(article); - } - WecomMessagePayload.News news = new WecomMessagePayload.News(); - news.setArticles(articles); - return news; - } - - private PortalMessageLog buildLog( - Long userId, - PortalWecomApp app, - PortalMessageSendRequest request, - String requestJson, - String responseJson, - boolean success, - String errorMessage - ) { - PortalMessageLog log = new PortalMessageLog(); - log.setUserId(userId); - log.setAppId(app.getId()); - log.setAgentId(app.getAgentId()); - log.setMsgType(request.getMsgType().getValue()); - log.setToUser(request.getToUser()); - log.setToParty(request.getToParty()); - log.setToAll(request.isToAll() ? 1 : 0); - log.setTitle(request.getTitle()); - log.setDescription(request.getDescription()); - log.setUrl(request.getUrl()); - if (request.getMsgType() == PortalMessageType.TEXT || request.getMsgType() == PortalMessageType.MARKDOWN) { - log.setContent(request.getContent()); - } else if (request.getMsgType() == PortalMessageType.NEWS && request.getArticles() != null - && !request.getArticles().isEmpty()) { - PortalMessageSendRequest.PortalNewsArticle first = request.getArticles().get(0); - log.setTitle(first.getTitle()); - log.setDescription(first.getDescription()); - log.setUrl(first.getUrl()); - } - log.setRequestJson(requestJson); - log.setResponseJson(responseJson); - log.setSuccess(success ? 1 : 0); - log.setErrorMessage(errorMessage); - log.setCreatedAt(System.currentTimeMillis()); - return log; - } - - private String normalizeTarget(String value) { - if (!StringUtils.hasText(value)) { - return null; - } - return value.trim(); - } - - private String requireText(String value, String field) { - if (!StringUtils.hasText(value)) { - throw new PortalException(PortalStatus.BAD_REQUEST, field + " 不能为空"); - } - return value.trim(); - } - - private long parseAgentId(String agentId) { - try { - return Long.parseLong(agentId.trim()); - } catch (Exception ex) { - throw new PortalException(PortalStatus.BAD_REQUEST, "无效的 agentId"); - } - } - - private String toJson(Object value) { - try { - return objectMapper.writeValueAsString(value); - } catch (Exception ex) { - return null; - } - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java deleted file mode 100644 index d165e3f..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; -import dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper; -import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import dev.qingzhou.pushserver.service.PortalProxyConfigService; -import org.springframework.stereotype.Service; - -@Service -public class PortalProxyConfigServiceImpl extends ServiceImpl implements PortalProxyConfigService { - - private final WecomApiClient wecomApiClient; - - public PortalProxyConfigServiceImpl(WecomApiClient wecomApiClient) { - this.wecomApiClient = wecomApiClient; - } - - @Override - public PortalProxyConfig getByUserId(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - return getOne(wrapper); - } - - @Override - public PortalProxyConfig upsert(Long userId, PortalProxyConfigRequest request) { - PortalProxyConfig config = getByUserId(userId); - long now = System.currentTimeMillis(); - if (config == null) { - config = new PortalProxyConfig(); - config.setUserId(userId); - config.setCreatedAt(now); - } - - config.setHost(request.getHost()); - config.setPort(request.getPort()); - config.setUsername(request.getUsername()); - config.setPassword(request.getPassword()); - config.setType(request.getType()); - config.setExitIp(request.getExitIp()); - config.setActive(request.getActive()); - config.setUpdatedAt(now); - - // 1. 先保存配置到数据库 - if (config.getId() == null) { - save(config); - } else { - updateById(config); - } - - // 2. 再进行连通性测试(如果启用) - // 即使测试失败抛出异常,数据也已经保存成功,方便用户后续修改 - if (Boolean.TRUE.equals(config.getActive())) { - wecomApiClient.testConnectivity(config); - } - - return config; - } - - @Override - public void deleteByUserId(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - remove(wrapper); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java deleted file mode 100644 index 4b273fb..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java +++ /dev/null @@ -1,85 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.mapper.portal.PortalUserMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalUser; -import dev.qingzhou.pushserver.service.PortalUserService; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalUserServiceImpl extends ServiceImpl implements PortalUserService { - - private final PasswordEncoder passwordEncoder; - - public PortalUserServiceImpl(PasswordEncoder passwordEncoder) { - this.passwordEncoder = passwordEncoder; - } - - @Override - public PortalUser register(String account, String password) { - if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { - throw new PortalException(PortalStatus.BAD_REQUEST, "账号和密码不能为空"); - } - if (existsAccount(account)) { - throw new PortalException(PortalStatus.CONFLICT, "账号已存在"); - } - PortalUser user = new PortalUser(); - user.setAccount(account.trim()); - user.setPasswordHash(passwordEncoder.encode(password)); - long now = System.currentTimeMillis(); - user.setCreatedAt(now); - user.setUpdatedAt(now); - save(user); - return user; - } - - @Override - public PortalUser authenticate(String account, String password) { - if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { - throw new PortalException(PortalStatus.BAD_REQUEST, "账号和密码不能为空"); - } - PortalUser user = findByAccount(account); - if (user == null || !passwordEncoder.matches(password, user.getPasswordHash())) { - throw new PortalException(PortalStatus.UNAUTHORIZED, "凭证无效"); - } - return user; - } - - @Override - public PortalUser findByAccount(String account) { - if (!StringUtils.hasText(account)) { - return null; - } - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("account", account.trim()); - return getOne(wrapper); - } - - @Override - public void updatePassword(Long userId, String oldPassword, String newPassword) { - if (!StringUtils.hasText(oldPassword) || !StringUtils.hasText(newPassword)) { - throw new PortalException(PortalStatus.BAD_REQUEST, "密码字段不能为空"); - } - PortalUser user = getById(userId); - if (user == null) { - throw new PortalException(PortalStatus.NOT_FOUND, "用户未找到"); - } - if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) { - throw new PortalException(PortalStatus.UNAUTHORIZED, "旧密码不匹配"); - } - user.setPasswordHash(passwordEncoder.encode(newPassword)); - user.setUpdatedAt(System.currentTimeMillis()); - updateById(user); - } - - private boolean existsAccount(String account) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("account", account.trim()); - return count(wrapper) > 0; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java deleted file mode 100644 index ca17362..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java +++ /dev/null @@ -1,143 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import dev.qingzhou.pushserver.exception.PortalException; -import dev.qingzhou.pushserver.exception.PortalStatus; -import dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo; -import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; -import dev.qingzhou.pushserver.manager.wecom.WecomToken; -import dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; -import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; -import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; -import dev.qingzhou.pushserver.service.PortalAccessTokenService; -import dev.qingzhou.pushserver.service.PortalCorpConfigService; -import dev.qingzhou.pushserver.service.PortalProxyConfigService; -import dev.qingzhou.pushserver.service.PortalWecomAppService; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PortalWecomAppServiceImpl extends ServiceImpl implements PortalWecomAppService { - - private final WecomApiClient wecomApiClient; - private final PortalAccessTokenService accessTokenService; - private final PortalCorpConfigService corpConfigService; - private final PortalProxyConfigService proxyConfigService; - - public PortalWecomAppServiceImpl( - WecomApiClient wecomApiClient, - PortalAccessTokenService accessTokenService, - PortalCorpConfigService corpConfigService, - PortalProxyConfigService proxyConfigService - ) { - this.wecomApiClient = wecomApiClient; - this.accessTokenService = accessTokenService; - this.corpConfigService = corpConfigService; - this.proxyConfigService = proxyConfigService; - } - - @Override - public PortalWecomApp addApp(Long userId, String agentId, String secret) { - if (!StringUtils.hasText(agentId) || !StringUtils.hasText(secret)) { - throw new PortalException(PortalStatus.BAD_REQUEST, "AgentId 和 Secret 不能为空"); - } - if (existsApp(userId, agentId)) { - throw new PortalException(PortalStatus.CONFLICT, "Agent 已存在"); - } - PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); - PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); - WecomToken token = accessTokenService.fetchToken(corpConfig.getCorpId(), secret, proxyConfig); - WecomAgentInfo info = wecomApiClient.getAgentInfo(token.getAccessToken(), agentId, proxyConfig); - PortalWecomApp app = new PortalWecomApp(); - long now = System.currentTimeMillis(); - app.setUserId(userId); - app.setAgentId(agentId.trim()); - app.setSecret(secret.trim()); - app.setName(info.getName()); - app.setAvatarUrl(info.getAvatarUrl()); - app.setDescription(info.getDescription()); - app.setCreatedAt(now); - app.setUpdatedAt(now); - save(app); - return app; - } - - @Override - public List listByUser(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId) - .orderByDesc("created_at"); - return list(wrapper); - } - - @Override - public PortalWecomApp requireByUser(Long userId, Long appId) { - PortalWecomApp app = getById(appId); - if (app == null || !app.getUserId().equals(userId)) { - throw new PortalException(PortalStatus.NOT_FOUND, "应用未找到"); - } - return app; - } - - @Override - public PortalWecomApp updateApp(Long userId, Long appId, String secret, String token, String encodingAesKey) { - PortalWecomApp app = requireByUser(userId, appId); - boolean changed = false; - - if (StringUtils.hasText(secret) && !secret.equals(app.getSecret())) { - app.setSecret(secret.trim()); - // 如果 Secret 变更,需要清除 Token 缓存 - accessTokenService.evict(app.getId()); - changed = true; - } - - if (token != null && !token.equals(app.getToken())) { - app.setToken(token.trim()); - changed = true; - } - - if (encodingAesKey != null && !encodingAesKey.equals(app.getEncodingAesKey())) { - app.setEncodingAesKey(encodingAesKey.trim()); - changed = true; - } - - if (changed) { - app.setUpdatedAt(System.currentTimeMillis()); - updateById(app); - } - - return app; - } - - @Override - public PortalWecomApp syncApp(Long userId, Long appId) { - PortalWecomApp app = requireByUser(userId, appId); - PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); - PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); - String accessToken = accessTokenService.getToken(app.getId(), corpConfig.getCorpId(), app.getSecret(), proxyConfig); - WecomAgentInfo info = wecomApiClient.getAgentInfo(accessToken, app.getAgentId(), proxyConfig); - app.setName(info.getName()); - app.setAvatarUrl(info.getAvatarUrl()); - app.setDescription(info.getDescription()); - app.setUpdatedAt(System.currentTimeMillis()); - updateById(app); - return app; - } - - @Override - public void deleteApp(Long userId, Long appId) { - PortalWecomApp app = requireByUser(userId, appId); - removeById(app.getId()); - accessTokenService.evict(app.getId()); - } - - private boolean existsApp(Long userId, String agentId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId) - .eq("agent_id", agentId.trim()); - return count(wrapper) > 0; - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java deleted file mode 100644 index cdbd4d5..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import dev.qingzhou.push.core.api.ChannelIds; -import dev.qingzhou.push.core.api.IPushChannel; -import dev.qingzhou.push.core.api.PushChannelFactory; -import dev.qingzhou.push.core.model.PushConfig; -import dev.qingzhou.push.core.model.PushMessage; -import dev.qingzhou.push.core.model.PushResult; -import dev.qingzhou.push.core.model.enums.MessageType; -import dev.qingzhou.pushserver.config.PushProperties; -import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; -import dev.qingzhou.pushserver.service.PushService; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -@Service -public class PushServiceImpl implements PushService { - - private final PushProperties properties; - - public PushServiceImpl(PushProperties properties) { - this.properties = properties; - } - - @Override - public PushResult push(PushRequest request) { - IPushChannel channel = PushChannelFactory.getChannel(ChannelIds.WECOM); - PushMessage message = buildMessage(request); - return channel.send(message, buildConfig()); - } - - private PushMessage buildMessage(PushRequest request) { - String target = requireNonBlank(request.getTarget(), "target"); - MessageType type = parseType(request.getType()); - return switch (type) { - case TEXT -> PushMessage.text(target, requireNonBlank(request.getContent(), "content")); - case MARKDOWN -> PushMessage.markdown( - target, - requireNonBlank(request.getTitle(), "title"), - requireNonBlank(request.getContent(), "content") - ); - case TEXT_CARD -> PushMessage.textCard( - target, - requireNonBlank(request.getTitle(), "title"), - requireNonBlank(request.getContent(), "content"), - requireNonBlank(request.getUrl(), "url") - ); - case IMAGE -> PushMessage.image(target, requireNonBlank(request.getMediaId(), "mediaId")); - case NEWS -> PushMessage.news(target, mapArticles(request.getArticles())); - }; - } - - private MessageType parseType(String type) { - if (!StringUtils.hasText(type)) { - return MessageType.TEXT; - } - try { - String normalized = type.trim().toUpperCase(Locale.ROOT).replace('-', '_'); - return MessageType.valueOf(normalized); - } catch (IllegalArgumentException ex) { - throw new IllegalArgumentException("Unsupported message type: " + type); - } - } - - private List mapArticles(List items) { - if (items == null || items.isEmpty()) { - throw new IllegalArgumentException("articles cannot be empty"); - } - List articles = new ArrayList<>(items.size()); - for (PushRequest.Article item : items) { - if (item == null) { - throw new IllegalArgumentException("article cannot be null"); - } - String title = requireNonBlank(item.getTitle(), "articles.title"); - String url = requireNonBlank(item.getUrl(), "articles.url"); - PushMessage.Article article = new PushMessage.Article(); - article.setTitle(title); - article.setUrl(url); - article.setDescription(item.getDescription()); - article.setPicUrl(item.getPicUrl()); - articles.add(article); - } - return articles; - } - - private String requireNonBlank(String value, String field) { - if (!StringUtils.hasText(value)) { - throw new IllegalArgumentException(field + " cannot be blank"); - } - return value.trim(); - } - - private PushConfig buildConfig() { - PushProperties.Wecom wecom = properties.getWecom(); - return new PushConfig( - wecom.getAppKey(), - wecom.getAppSecret(), - wecom.getAgentId(), - wecom.getWebhookUrl() - ); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java b/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java deleted file mode 100644 index 8c6411d..0000000 --- a/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.qingzhou.pushserver.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper; -import dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig; -import dev.qingzhou.pushserver.service.SystemConfigService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class SystemConfigServiceImpl implements SystemConfigService { - - private static final String KEY_TURNSTILE_ENABLED = "turnstile.enabled"; - private static final String KEY_TURNSTILE_SITE_KEY = "turnstile.site_key"; - private static final String KEY_TURNSTILE_SECRET_KEY = "turnstile.secret_key"; - - private final PortalSystemConfigMapper configMapper; - - public SystemConfigServiceImpl(PortalSystemConfigMapper configMapper) { - this.configMapper = configMapper; - } - - @Override - public String get(String key) { - return get(key, null); - } - - @Override - public String get(String key, String defaultValue) { - PortalSystemConfig config = configMapper.selectOne( - new QueryWrapper().eq("config_key", key) - ); - return config != null ? config.getConfigValue() : defaultValue; - } - - @Override - @Transactional - public void set(String key, String value) { - PortalSystemConfig config = configMapper.selectOne( - new QueryWrapper().eq("config_key", key) - ); - if (config == null) { - config = new PortalSystemConfig(); - config.setConfigKey(key); - config.setConfigValue(value); - config.setUpdatedAt(System.currentTimeMillis()); - configMapper.insert(config); - } else { - config.setConfigValue(value); - config.setUpdatedAt(System.currentTimeMillis()); - configMapper.updateById(config); - } - } - - @Override - public boolean isTurnstileEnabled() { - return "true".equalsIgnoreCase(get(KEY_TURNSTILE_ENABLED, "false")); - } - - @Override - public String getTurnstileSiteKey() { - return get(KEY_TURNSTILE_SITE_KEY, ""); - } - - @Override - public String getTurnstileSecretKey() { - return get(KEY_TURNSTILE_SECRET_KEY, ""); - } - - @Override - @Transactional - public void setTurnstileConfig(boolean enabled, String siteKey, String secretKey) { - set(KEY_TURNSTILE_ENABLED, String.valueOf(enabled)); - set(KEY_TURNSTILE_SITE_KEY, siteKey); - set(KEY_TURNSTILE_SECRET_KEY, secretKey); - } -} diff --git a/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java b/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java deleted file mode 100644 index 8aa0384..0000000 --- a/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java +++ /dev/null @@ -1,55 +0,0 @@ -package dev.qingzhou.pushserver.utils; - -import java.util.concurrent.atomic.AtomicReference; - -public class CasTokenBucket { - - private final long capacity; // 最大令牌数(burst) - private final double refillPerNanos; // 每纳秒生成的令牌数 - private final AtomicReference state; - - public CasTokenBucket(long capacity, long qps) { - if (capacity <= 0 || qps <= 0) throw new IllegalArgumentException("capacity/qps must be > 0"); - this.capacity = capacity; - this.refillPerNanos = qps / 1_000_000_000.0; - long now = System.nanoTime(); - this.state = new AtomicReference<>(new State(capacity, now)); - } - - private record State(double tokens, long lastRefillNanos) {} - - public boolean tryAcquire() { - int spins = 0; - while (true) { - State cur = state.get(); - long now = System.nanoTime(); - - long delta = now - cur.lastRefillNanos; - if (delta < 0) delta = 0; // 理论上 nanoTime 单调,但防御性写一下 - - double newTokens = cur.tokens; - if (delta > 0) { - double generated = delta * refillPerNanos; - newTokens = Math.min(capacity, newTokens + generated); - } - - // 不管成功/失败,都尽量把时间推进,减少下次重复计算 - if (newTokens < 1.0) { - State next = (delta > 0) ? new State(newTokens, now) : cur; - if (next == cur || state.compareAndSet(cur, next)) { - return false; - } - } else { - State next = new State(newTokens - 1.0, now); - if (state.compareAndSet(cur, next)) { - return true; - } - } - - // 轻微退避,避免高竞争 CPU 飙 - if (++spins > 10) { - Thread.onSpinWait(); - } - } - } -} diff --git a/src/main/resources/META-INF/native-image/reachability-metadata.json b/src/main/resources/META-INF/native-image/reachability-metadata.json deleted file mode 100644 index 1c46924..0000000 --- a/src/main/resources/META-INF/native-image/reachability-metadata.json +++ /dev/null @@ -1,10892 +0,0 @@ -{ - "reflection": [ - { - "type": "boolean" - }, - { - "type": "boolean[]", - "jniAccessible": true - }, - { - "type": "ch.qos.logback.classic.BasicConfigurator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "ch.qos.logback.classic.Logger" - }, - { - "type": "ch.qos.logback.classic.LoggerContext" - }, - { - "type": "ch.qos.logback.classic.spi.LogbackServiceProvider" - }, - { - "type": "ch.qos.logback.classic.util.DefaultJoranConfigurator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.annotation.TableName" - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.DdlAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.MybatisDependsOnDatabaseInitializationDetector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.core.io.ResourceLoader", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "mybatisPlusSpringApplicationContextAware", - "parameterTypes": [] - }, - { - "name": "sqlSessionFactory", - "parameterTypes": [ - "javax.sql.DataSource" - ] - }, - { - "name": "sqlSessionTemplate", - "parameterTypes": [ - "org.apache.ibatis.session.SqlSessionFactory" - ] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration$AutoConfiguredMapperScannerRegistrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration" - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.AbstractWrapper", - "methods": [ - { - "name": "getEntity", - "parameterTypes": [] - }, - { - "name": "getParamNameValuePairs", - "parameterTypes": [] - }, - { - "name": "getSqlComment", - "parameterTypes": [] - }, - { - "name": "getSqlFirst", - "parameterTypes": [] - }, - { - "name": "getSqlSegment", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.ISqlSegment" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.Wrapper", - "methods": [ - { - "name": "isEmptyOfNormal", - "parameterTypes": [] - }, - { - "name": "isNonEmptyOfNormal", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Compare" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Func" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Join" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Nested" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.query.Query" - }, - { - "type": "com.baomidou.mybatisplus.core.conditions.query.QueryWrapper", - "methods": [ - { - "name": "getSqlSelect", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.mapper.BaseMapper", - "methods": [ - { - "name": "selectOne", - "parameterTypes": [ - "com.baomidou.mybatisplus.core.conditions.Wrapper" - ] - }, - { - "name": "selectOne", - "parameterTypes": [ - "com.baomidou.mybatisplus.core.conditions.Wrapper", - "boolean" - ] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.mapper.Mapper" - }, - { - "type": "com.baomidou.mybatisplus.core.override.MybatisMapperProxy", - "methods": [ - { - "name": "getMapperInterface", - "parameterTypes": [] - }, - { - "name": "getSqlSession", - "parameterTypes": [] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.core.toolkit.Wrappers$EmptyWrapper" - }, - { - "type": "com.baomidou.mybatisplus.extension.ddl.IDdl" - }, - { - "type": "com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor" - }, - { - "type": "com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor" - }, - { - "type": "com.baomidou.mybatisplus.extension.repository.AbstractRepository", - "methods": [ - { - "name": "getOne", - "parameterTypes": [ - "com.baomidou.mybatisplus.core.conditions.Wrapper", - "boolean" - ] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.extension.repository.CrudRepository", - "fields": [ - { - "name": "baseMapper" - } - ] - }, - { - "type": "com.baomidou.mybatisplus.extension.repository.IRepository", - "methods": [ - { - "name": "count", - "parameterTypes": [] - }, - { - "name": "count", - "parameterTypes": [ - "com.baomidou.mybatisplus.core.conditions.Wrapper" - ] - }, - { - "name": "getById", - "parameterTypes": [ - "java.io.Serializable" - ] - }, - { - "name": "list", - "parameterTypes": [ - "com.baomidou.mybatisplus.core.conditions.Wrapper" - ] - }, - { - "name": "save", - "parameterTypes": [ - "java.lang.Object" - ] - } - ] - }, - { - "type": "com.baomidou.mybatisplus.extension.service.IService" - }, - { - "type": "com.baomidou.mybatisplus.extension.service.impl.ServiceImpl" - }, - { - "type": "com.baomidou.mybatisplus.extension.spi.SpringCompatibleSet" - }, - { - "type": "com.baomidou.mybatisplus.extension.spring.MybatisPlusApplicationContextAware" - }, - { - "type": "com.fasterxml.jackson.core.JsonGenerator" - }, - { - "type": "com.fasterxml.jackson.core.ObjectCodec" - }, - { - "type": "com.fasterxml.jackson.core.TreeCodec" - }, - { - "type": "com.fasterxml.jackson.core.Versioned" - }, - { - "type": "com.fasterxml.jackson.databind.ObjectMapper" - }, - { - "type": "com.fasterxml.jackson.databind.ext.Java7SupportImpl", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.fasterxml.jackson.dataformat.cbor.CBORFactory" - }, - { - "type": "com.fasterxml.jackson.dataformat.smile.SmileFactory" - }, - { - "type": "com.fasterxml.jackson.dataformat.xml.XmlMapper" - }, - { - "type": "com.fasterxml.jackson.dataformat.yaml.YAMLFactory" - }, - { - "type": "com.github.benmanes.caffeine.cache.BLCHeader$DrainStatusRef", - "fields": [ - { - "name": "drainStatus" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueColdProducerFields", - "fields": [ - { - "name": "producerLimit" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueConsumerFields", - "fields": [ - { - "name": "consumerIndex" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueProducerFields", - "fields": [ - { - "name": "producerIndex" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.BoundedLocalCache", - "fields": [ - { - "name": "refreshes" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.PS", - "fields": [ - { - "name": "key" - }, - { - "name": "value" - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.PSW", - "fields": [ - { - "name": "writeTime" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.github.benmanes.caffeine.cache.SSW", - "fields": [ - { - "name": "FACTORY" - }, - { - "name": "expiresAfterWriteNanos" - } - ] - }, - { - "type": "com.google.gson.Gson" - }, - { - "type": "com.rometools.rome.feed.WireFeed" - }, - { - "type": "com.sun.crypto.provider.AESCipher$General", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.ARCFOURCipher", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.DESCipher", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.DESedeCipher", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.DHParameters", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.TlsKeyMaterialGenerator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.crypto.provider.TlsPrfGenerator$V12", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "com.zaxxer.hikari.HikariConfig" - }, - { - "type": "com.zaxxer.hikari.HikariConfigMXBean" - }, - { - "type": "com.zaxxer.hikari.HikariDataSource" - }, - { - "type": "com.zaxxer.hikari.pool.PoolBase" - }, - { - "type": "com.zaxxer.hikari.pool.PoolEntry" - }, - { - "type": "dev.qingzhou.pushserver.PushServerApplication", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "main", - "parameterTypes": [ - "java.lang.String[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.aspect.SecurityInterceptor", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.config.PushProperties" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.common.PortalResponse", - "methods": [ - { - "name": "getData", - "parameterTypes": [] - }, - { - "name": "getMessage", - "parameterTypes": [] - }, - { - "name": "isSuccess", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.JsonDtoPackageHints", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.JsonDtoPackageHints$DtoHints" - }, - { - "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "myBatisBeanFactoryInitializationAotProcessor", - "parameterTypes": [] - }, - { - "name": "myBatisMapperFactoryBeanPostProcessor", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBaitsRuntimeHintsRegistrar" - }, - { - "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBatisBeanFactoryInitializationAotProcessor" - }, - { - "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBatisMapperFactoryBeanPostProcessor" - }, - { - "type": "dev.qingzhou.pushserver.config.PortalDataSourceProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig", - "methods": [ - { - "name": "dataSource", - "parameterTypes": [ - "dev.qingzhou.pushserver.config.PortalDataSourceProperties" - ] - }, - { - "name": "mybatisPlusInterceptor", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$FastClass$$0", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$FastClass$$1", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig", - "methods": [ - { - "name": "objectMapper", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$FastClass$$0", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$FastClass$$1", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalMybatisConfig" - }, - { - "type": "dev.qingzhou.pushserver.config.PortalMybatisConfig$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalSchemaInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [ - "javax.sql.DataSource" - ] - }, - { - "name": "initialize", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig", - "methods": [ - { - "name": "authenticationManager", - "parameterTypes": [ - "dev.qingzhou.pushserver.security.PortalUserDetailsService", - "org.springframework.security.crypto.password.PasswordEncoder" - ] - }, - { - "name": "passwordEncoder", - "parameterTypes": [] - }, - { - "name": "portalJsonLoginAuthenticationFilter", - "parameterTypes": [ - "com.fasterxml.jackson.databind.ObjectMapper", - "dev.qingzhou.pushserver.security.CaptchaService", - "org.springframework.security.authentication.AuthenticationManager", - "org.springframework.security.web.authentication.session.SessionAuthenticationStrategy" - ] - }, - { - "name": "securityFilterChain", - "parameterTypes": [ - "org.springframework.security.config.annotation.web.builders.HttpSecurity", - "dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter" - ] - }, - { - "name": "sessionAuthenticationStrategy", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$FastClass$$0", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$FastClass$$1", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PortalWecomProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PushConfiguration" - }, - { - "type": "dev.qingzhou.pushserver.config.PushConfiguration$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.PushProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.config.WebConfig" - }, - { - "type": "dev.qingzhou.pushserver.config.WebConfig$$SpringCGLIB$$0", - "fields": [ - { - "name": "$$beanFactory" - }, - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.aspect.SecurityInterceptor" - ] - }, - { - "name": "CGLIB$SET_STATIC_CALLBACKS", - "parameterTypes": [ - "org.springframework.cglib.proxy.Callback[]" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.CaptchaController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.SystemConfigService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.DashboardController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.DashboardService" - ] - }, - { - "name": "charts", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "recentLogs", - "parameterTypes": [ - "int", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "stats", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PageController", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "redirect", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalAppController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalWecomAppService", - "dev.qingzhou.pushserver.service.PortalAppApiKeyService" - ] - }, - { - "name": "create", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "createApiKey", - "parameterTypes": [ - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "getApiKey", - "parameterTypes": [ - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "list", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "sync", - "parameterTypes": [ - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "updateApiKey", - "parameterTypes": [ - "java.lang.Long", - "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalAuthController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalUserService" - ] - }, - { - "name": "csrf", - "parameterTypes": [ - "org.springframework.security.web.csrf.CsrfToken" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalCorpController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalCorpConfigService" - ] - }, - { - "name": "getCorp", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalErrorController", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalInitController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalUserService", - "dev.qingzhou.pushserver.service.SystemConfigService" - ] - }, - { - "name": "getInitStatus", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalMeController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalUserService" - ] - }, - { - "name": "me", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalMessageController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalMessageService", - "dev.qingzhou.pushserver.service.PortalMessageLogService" - ] - }, - { - "name": "logs", - "parameterTypes": [ - "java.lang.Integer", - "java.lang.Integer", - "int", - "java.lang.Boolean", - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "send", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalProxyController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalProxyConfigService" - ] - }, - { - "name": "getProxy", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", - "jakarta.servlet.http.HttpSession" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PortalSystemController", - "fields": [ - { - "name": "appVersion" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.SystemConfigService" - ] - }, - { - "name": "getIgnoreVersion", - "parameterTypes": [] - }, - { - "name": "getVersion", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.PushController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PushService", - "dev.qingzhou.pushserver.config.PushProperties" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.openapi.OpenApiMessageController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalAppApiKeyService", - "dev.qingzhou.pushserver.service.PortalMessageService" - ] - }, - { - "name": "send", - "parameterTypes": [ - "java.lang.String", - "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.controller.wecom.WecomCallbackController", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalWecomAppService", - "dev.qingzhou.pushserver.service.PortalCorpConfigService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.exception.GlobalExceptionHandler", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.exception.PortalExceptionHandler", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAvatarUrl", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setName", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.config.PortalWecomProperties" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload", - "methods": [ - { - "name": "getAgentid", - "parameterTypes": [] - }, - { - "name": "getContent", - "parameterTypes": [] - }, - { - "name": "getCreateTime", - "parameterTypes": [] - }, - { - "name": "getDuplicateCheckInterval", - "parameterTypes": [] - }, - { - "name": "getEnableDuplicateCheck", - "parameterTypes": [] - }, - { - "name": "getEnableIdTrans", - "parameterTypes": [] - }, - { - "name": "getEvent", - "parameterTypes": [] - }, - { - "name": "getEventKey", - "parameterTypes": [] - }, - { - "name": "getFromUserName", - "parameterTypes": [] - }, - { - "name": "getMarkdown", - "parameterTypes": [] - }, - { - "name": "getMediaId", - "parameterTypes": [] - }, - { - "name": "getMsgId", - "parameterTypes": [] - }, - { - "name": "getMsgtype", - "parameterTypes": [] - }, - { - "name": "getNews", - "parameterTypes": [] - }, - { - "name": "getPicUrl", - "parameterTypes": [] - }, - { - "name": "getReceiveAgentId", - "parameterTypes": [] - }, - { - "name": "getReceiveMsgType", - "parameterTypes": [] - }, - { - "name": "getSafe", - "parameterTypes": [] - }, - { - "name": "getText", - "parameterTypes": [] - }, - { - "name": "getTextcard", - "parameterTypes": [] - }, - { - "name": "getToUserName", - "parameterTypes": [] - }, - { - "name": "getToparty", - "parameterTypes": [] - }, - { - "name": "getTotag", - "parameterTypes": [] - }, - { - "name": "getTouser", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Article", - "methods": [ - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getPicUrl", - "parameterTypes": [] - }, - { - "name": "getTitle", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Markdown", - "methods": [ - { - "name": "getContent", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$News", - "methods": [ - { - "name": "getArticles", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Text", - "methods": [ - { - "name": "getContent", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$TextCard", - "methods": [ - { - "name": "getBtnText", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getTitle", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomResponse", - "methods": [ - { - "name": "getErrcode", - "parameterTypes": [] - }, - { - "name": "getErrmsg", - "parameterTypes": [] - }, - { - "name": "isSuccess", - "parameterTypes": [] - }, - { - "name": "setErrcode", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setErrmsg", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomSendResponse", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getInvalidParty", - "parameterTypes": [] - }, - { - "name": "getInvalidTag", - "parameterTypes": [] - }, - { - "name": "getInvalidUser", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomToken", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAccessToken", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setExpiresIn", - "parameterTypes": [ - "java.lang.Integer" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" - }, - { - "type": "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setContent", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setMsgType", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" - ] - }, - { - "name": "setToAll", - "parameterTypes": [ - "java.lang.Boolean" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", - "fields": [ - { - "name": "rateLimitPerMinute" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setRateLimitPerMinute", - "parameterTypes": [ - "java.lang.Integer" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", - "fields": [ - { - "name": "agentId" - }, - { - "name": "secret" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAgentId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setSecret", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", - "fields": [ - { - "name": "corpId" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setCorpId", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAccount", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCaptcha", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPassword", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", - "fields": [ - { - "name": "appId" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAppId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setArticles", - "parameterTypes": [ - "java.util.List" - ] - }, - { - "name": "setContent", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setMsgType", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" - ] - }, - { - "name": "setTitle", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setToAll", - "parameterTypes": [ - "java.lang.Boolean" - ] - }, - { - "name": "setUrl", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest$PortalNewsArticle", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPicUrl", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setTitle", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUrl", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", - "fields": [ - { - "name": "host" - }, - { - "name": "port" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setActive", - "parameterTypes": [ - "java.lang.Boolean" - ] - }, - { - "name": "setExitIp", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setHost", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPassword", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPort", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setType", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUsername", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getApiKeyHash", - "parameterTypes": [] - }, - { - "name": "getApiKeyPlain", - "parameterTypes": [] - }, - { - "name": "getAppId", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getRateLimitPerMinute", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "setApiKeyHash", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setApiKeyPlain", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setAppId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setRateLimitPerMinute", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getCorpId", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, - { - "name": "setCorpId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUserId", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getAgentId", - "parameterTypes": [] - }, - { - "name": "getAppId", - "parameterTypes": [] - }, - { - "name": "getContent", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getErrorMessage", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getMsgType", - "parameterTypes": [] - }, - { - "name": "getRequestJson", - "parameterTypes": [] - }, - { - "name": "getResponseJson", - "parameterTypes": [] - }, - { - "name": "getSuccess", - "parameterTypes": [] - }, - { - "name": "getTitle", - "parameterTypes": [] - }, - { - "name": "getToAll", - "parameterTypes": [] - }, - { - "name": "getToParty", - "parameterTypes": [] - }, - { - "name": "getToUser", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, - { - "name": "setAgentId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setAppId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setContent", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setMsgType", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setRequestJson", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setResponseJson", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setSuccess", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setTitle", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setToAll", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setUrl", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUserId", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getActive", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getExitIp", - "parameterTypes": [] - }, - { - "name": "getHost", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getPassword", - "parameterTypes": [] - }, - { - "name": "getPort", - "parameterTypes": [] - }, - { - "name": "getType", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, - { - "name": "getUsername", - "parameterTypes": [] - }, - { - "name": "setActive", - "parameterTypes": [ - "java.lang.Boolean" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setExitIp", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setHost", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setPassword", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPort", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setType", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUsername", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setConfigKey", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setConfigValue", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalUser", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAccount", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setPasswordHash", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getAgentId", - "parameterTypes": [] - }, - { - "name": "getAvatarUrl", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getEncodingAesKey", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getName", - "parameterTypes": [] - }, - { - "name": "getSecret", - "parameterTypes": [] - }, - { - "name": "getToken", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, - { - "name": "setAgentId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setAvatarUrl", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setName", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setSecret", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setUserId", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse", - "methods": [ - { - "name": "getDistribution", - "parameterTypes": [] - }, - { - "name": "getTrend", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse$TrendPoint", - "methods": [ - { - "name": "getCount", - "parameterTypes": [] - }, - { - "name": "getDate", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse", - "methods": [ - { - "name": "getActiveApps", - "parameterTypes": [] - }, - { - "name": "getLastErrorTime", - "parameterTypes": [] - }, - { - "name": "getSuccessRate", - "parameterTypes": [] - }, - { - "name": "getTodayTotal", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppApiKeyResponse", - "methods": [ - { - "name": "getApiKey", - "parameterTypes": [] - }, - { - "name": "getAppId", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getRateLimitPerMinute", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "isHasKey", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppResponse", - "methods": [ - { - "name": "getAgentId", - "parameterTypes": [] - }, - { - "name": "getAvatarUrl", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getName", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse", - "methods": [ - { - "name": "getCorpId", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse", - "methods": [ - { - "name": "getAgentId", - "parameterTypes": [] - }, - { - "name": "getAppId", - "parameterTypes": [] - }, - { - "name": "getContent", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getErrorMessage", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getMsgType", - "parameterTypes": [] - }, - { - "name": "getTitle", - "parameterTypes": [] - }, - { - "name": "getToParty", - "parameterTypes": [] - }, - { - "name": "getToUser", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - }, - { - "name": "isSuccess", - "parameterTypes": [] - }, - { - "name": "isToAll", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse", - "methods": [ - { - "name": "getPage", - "parameterTypes": [] - }, - { - "name": "getPageSize", - "parameterTypes": [] - }, - { - "name": "getRecords", - "parameterTypes": [] - }, - { - "name": "getTotal", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse", - "methods": [ - { - "name": "getActive", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getExitIp", - "parameterTypes": [] - }, - { - "name": "getHost", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getPassword", - "parameterTypes": [] - }, - { - "name": "getPort", - "parameterTypes": [] - }, - { - "name": "getType", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUsername", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse", - "methods": [ - { - "name": "getAccount", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.security.CaptchaService", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.SystemConfigService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter" - }, - { - "type": "dev.qingzhou.pushserver.security.PortalUserDetailsService", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalUserService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.DashboardService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalAccessTokenService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalAppApiKeyService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalCorpConfigService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalMessageLogService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalMessageService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalProxyConfigService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalUserService" - }, - { - "type": "dev.qingzhou.pushserver.service.PortalWecomAppService" - }, - { - "type": "dev.qingzhou.pushserver.service.PushService" - }, - { - "type": "dev.qingzhou.pushserver.service.SystemConfigService" - }, - { - "type": "dev.qingzhou.pushserver.service.impl.DashboardServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalMessageLogService", - "dev.qingzhou.pushserver.service.PortalWecomAppService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalAccessTokenServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalWecomAppService", - "dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter" - ] - }, - { - "name": "findByAppId", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] - }, - { - "name": "requireAppByApiKey", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "rotateKey", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] - }, - { - "name": "updateRateLimit", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long", - "java.lang.Integer" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalCorpConfigServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getByUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "requireByUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "java.lang.Long", - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalCorpConfigServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalMessageLogServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "pageLogs", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long", - "java.lang.Boolean", - "int", - "int" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalMessageLogServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalMessageServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalWecomAppService", - "dev.qingzhou.pushserver.service.PortalCorpConfigService", - "dev.qingzhou.pushserver.service.PortalAccessTokenService", - "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", - "dev.qingzhou.pushserver.service.PortalMessageLogService", - "com.fasterxml.jackson.databind.ObjectMapper", - "dev.qingzhou.pushserver.service.PortalProxyConfigService" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" - ] - }, - { - "name": "getByUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "java.lang.Long", - "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalUserServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.security.crypto.password.PasswordEncoder" - ] - }, - { - "name": "findByAccount", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalUserServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalWecomAppServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", - "dev.qingzhou.pushserver.service.PortalAccessTokenService", - "dev.qingzhou.pushserver.service.PortalCorpConfigService", - "dev.qingzhou.pushserver.service.PortalProxyConfigService" - ] - }, - { - "name": "addApp", - "parameterTypes": [ - "java.lang.Long", - "java.lang.String", - "java.lang.String" - ] - }, - { - "name": "listByUser", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "requireByUser", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] - }, - { - "name": "syncApp", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PortalWecomAppServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.PushServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.config.PushProperties" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.SystemConfigServiceImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" - ] - }, - { - "name": "get", - "parameterTypes": [ - "java.lang.String", - "java.lang.String" - ] - }, - { - "name": "isTurnstileEnabled", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.service.impl.SystemConfigServiceImpl$$SpringCGLIB$$0", - "fields": [ - { - "name": "CGLIB$CALLBACK_FILTER" - }, - { - "name": "CGLIB$FACTORY_DATA" - } - ] - }, - { - "type": "groovy.lang.MetaClass" - }, - { - "type": "int[]" - }, - { - "type": "io.micrometer.context.ContextSnapshot" - }, - { - "type": "io.micrometer.core.instrument.MeterRegistry" - }, - { - "type": "io.micrometer.core.instrument.binder.tomcat.TomcatMetrics" - }, - { - "type": "io.micrometer.observation.Observation" - }, - { - "type": "io.micrometer.observation.ObservationRegistry" - }, - { - "type": "io.r2dbc.spi.ConnectionFactory" - }, - { - "type": "io.reactivex.rxjava3.core.Flowable" - }, - { - "type": "io.smallrye.mutiny.Multi" - }, - { - "type": "io.vavr.control.Try" - }, - { - "type": "jakarta.annotation.PostConstruct" - }, - { - "type": "jakarta.annotation.PreDestroy" - }, - { - "type": "jakarta.annotation.Resource" - }, - { - "type": "jakarta.ejb.EJB" - }, - { - "type": "jakarta.ejb.TransactionAttribute" - }, - { - "type": "jakarta.faces.context.FacesContext" - }, - { - "type": "jakarta.inject.Inject" - }, - { - "type": "jakarta.inject.Named" - }, - { - "type": "jakarta.inject.Provider" - }, - { - "type": "jakarta.inject.Qualifier" - }, - { - "type": "jakarta.json.bind.Jsonb" - }, - { - "type": "jakarta.persistence.EntityManagerFactory" - }, - { - "type": "jakarta.persistence.Persistence" - }, - { - "type": "jakarta.persistence.PersistenceContext" - }, - { - "type": "jakarta.servlet.Filter" - }, - { - "type": "jakarta.servlet.GenericFilter" - }, - { - "type": "jakarta.servlet.GenericServlet" - }, - { - "type": "jakarta.servlet.MultipartConfigElement" - }, - { - "type": "jakarta.servlet.Servlet" - }, - { - "type": "jakarta.servlet.ServletConfig" - }, - { - "type": "jakarta.servlet.ServletContext" - }, - { - "type": "jakarta.servlet.ServletRegistration" - }, - { - "type": "jakarta.servlet.ServletRequest" - }, - { - "type": "jakarta.servlet.http.HttpServlet" - }, - { - "type": "jakarta.servlet.jsp.jstl.core.Config" - }, - { - "type": "jakarta.transaction.Transaction" - }, - { - "type": "jakarta.transaction.TransactionManager" - }, - { - "type": "jakarta.transaction.Transactional" - }, - { - "type": "jakarta.validation.ConstraintValidator" - }, - { - "type": "jakarta.validation.Valid" - }, - { - "type": "jakarta.validation.Validator" - }, - { - "type": "jakarta.validation.ValidatorFactory" - }, - { - "type": "jakarta.validation.bootstrap.GenericBootstrap" - }, - { - "type": "jakarta.validation.constraints.Max", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - }, - { - "name": "value", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.constraints.Min", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - }, - { - "name": "value", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.constraints.NotBlank", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.constraints.NotNull", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.executable.ExecutableValidator" - }, - { - "type": "jakarta.xml.bind.Binder" - }, - { - "type": "jakarta.xml.ws.WebServiceRef" - }, - { - "type": "java.beans.Introspector" - }, - { - "type": "java.beans.PropertyVetoException" - }, - { - "type": "java.io.Closeable" - }, - { - "type": "java.io.Serializable" - }, - { - "type": "java.io.Serializable[]" - }, - { - "type": "java.lang.AutoCloseable" - }, - { - "type": "java.lang.Boolean", - "jniAccessible": true, - "methods": [ - { - "name": "getBoolean", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "java.lang.CharSequence[]" - }, - { - "type": "java.lang.Class", - "methods": [ - { - "name": "getModule", - "parameterTypes": [] - }, - { - "name": "isRecord", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.ClassLoader", - "fields": [ - { - "name": "classLoaderValueMap" - } - ] - }, - { - "type": "java.lang.Class[]" - }, - { - "type": "java.lang.CloneNotSupportedException" - }, - { - "type": "java.lang.Comparable" - }, - { - "type": "java.lang.Comparable[]" - }, - { - "type": "java.lang.Error" - }, - { - "type": "java.lang.Long" - }, - { - "type": "java.lang.Module", - "methods": [ - { - "name": "isNamed", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.Number" - }, - { - "type": "java.lang.Object", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.Object[]" - }, - { - "type": "java.lang.RuntimeException" - }, - { - "type": "java.lang.String" - }, - { - "type": "java.lang.String[]" - }, - { - "type": "java.lang.System" - }, - { - "type": "java.lang.Thread" - }, - { - "type": "java.lang.Thread$Builder" - }, - { - "type": "java.lang.Throwable", - "jniAccessible": true, - "methods": [ - { - "name": "toString", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.WrongThreadException" - }, - { - "type": "java.lang.annotation.Annotation[]" - }, - { - "type": "java.lang.annotation.Documented" - }, - { - "type": "java.lang.annotation.Inherited" - }, - { - "type": "java.lang.annotation.Repeatable" - }, - { - "type": "java.lang.annotation.Retention" - }, - { - "type": "java.lang.annotation.Target" - }, - { - "type": "java.lang.constant.Constable" - }, - { - "type": "java.lang.constant.Constable[]" - }, - { - "type": "java.lang.constant.ConstantDesc" - }, - { - "type": "java.lang.constant.ConstantDesc[]" - }, - { - "type": "java.lang.invoke.MethodHandles", - "methods": [ - { - "name": "privateLookupIn", - "parameterTypes": [ - "java.lang.Class", - "java.lang.invoke.MethodHandles$Lookup" - ] - } - ] - }, - { - "type": "java.lang.invoke.TypeDescriptor$OfField" - }, - { - "type": "java.lang.reflect.AccessibleObject" - }, - { - "type": "java.lang.reflect.AnnotatedElement" - }, - { - "type": "java.lang.reflect.GenericDeclaration" - }, - { - "type": "java.lang.reflect.InvocationHandler" - }, - { - "type": "java.lang.reflect.ParameterizedType", - "methods": [ - { - "name": "getActualTypeArguments", - "parameterTypes": [] - }, - { - "name": "getRawType", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.reflect.Type" - }, - { - "type": "java.lang.reflect.TypeVariable", - "methods": [ - { - "name": "getBounds", - "parameterTypes": [] - } - ] - }, - { - "type": "java.lang.reflect.UndeclaredThrowableException" - }, - { - "type": "java.lang.reflect.WildcardType", - "methods": [ - { - "name": "getLowerBounds", - "parameterTypes": [] - }, - { - "name": "getUpperBounds", - "parameterTypes": [] - } - ] - }, - { - "type": "java.net.http.HttpClient" - }, - { - "type": "java.security.AlgorithmParametersSpi" - }, - { - "type": "java.security.KeyStoreSpi" - }, - { - "type": "java.security.interfaces.RSAPrivateKey" - }, - { - "type": "java.security.interfaces.RSAPublicKey" - }, - { - "type": "java.sql.Date" - }, - { - "type": "java.sql.Driver" - }, - { - "type": "java.sql.DriverManager" - }, - { - "type": "java.sql.SQLException" - }, - { - "type": "java.sql.Statement[]" - }, - { - "type": "java.sql.Timestamp" - }, - { - "type": "java.sql.Wrapper" - }, - { - "type": "java.text.ListFormat" - }, - { - "type": "java.util.AbstractMap" - }, - { - "type": "java.util.ArrayList", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "java.util.Enumeration" - }, - { - "type": "java.util.EventListener" - }, - { - "type": "java.util.HashSet" - }, - { - "type": "java.util.ImmutableCollections$AbstractImmutableMap" - }, - { - "type": "java.util.ImmutableCollections$Map1" - }, - { - "type": "java.util.ImmutableCollections$MapN" - }, - { - "type": "java.util.List" - }, - { - "type": "java.util.Map" - }, - { - "type": "java.util.Map$Entry[]" - }, - { - "type": "java.util.MapBeanInfo" - }, - { - "type": "java.util.MapCustomizer" - }, - { - "type": "java.util.SequencedCollection" - }, - { - "type": "java.util.Set" - }, - { - "type": "java.util.concurrent.Callable" - }, - { - "type": "java.util.concurrent.Executor" - }, - { - "type": "java.util.concurrent.ThreadFactory" - }, - { - "type": "java.util.logging.LogManager" - }, - { - "type": "java.util.logging.SimpleFormatter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "javafx.beans.value.ObservableValue" - }, - { - "type": "javax.money.MonetaryAmount" - }, - { - "type": "javax.naming.InitialContext" - }, - { - "type": "javax.net.ssl.SSLParameters" - }, - { - "type": "javax.security.auth.Subject" - }, - { - "type": "javax.sql.CommonDataSource" - }, - { - "type": "javax.sql.DataSource" - }, - { - "type": "javax.sql.XADataSource" - }, - { - "type": "jdk.crac.management.CRaCMXBean" - }, - { - "type": "jdk.internal.loader.ClassLoaders$AppClassLoader" - }, - { - "type": "jdk.internal.loader.ClassLoaders$PlatformClassLoader" - }, - { - "type": "jdk.internal.misc.Unsafe" - }, - { - "type": "kotlin.Metadata" - }, - { - "type": "kotlin.reflect.full.KClasses" - }, - { - "type": "kotlinx.coroutines.reactor.MonoKt" - }, - { - "type": "kotlinx.serialization.Serializable" - }, - { - "type": "kotlinx.serialization.cbor.Cbor" - }, - { - "type": "kotlinx.serialization.json.Json" - }, - { - "type": "kotlinx.serialization.protobuf.ProtoBuf" - }, - { - "type": "oracle.jdbc.OracleConnection" - }, - { - "type": "oracle.ucp.jdbc.PoolDataSource" - }, - { - "type": "oracle.ucp.jdbc.PoolDataSourceImpl" - }, - { - "type": "org.aopalliance.intercept.MethodInterceptor" - }, - { - "type": "org.apache.catalina.core.ApplicationContextFacade" - }, - { - "type": "org.apache.catalina.loader.JdbcLeakPrevention", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "clearJdbcDriverRegistrations", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.catalina.startup.Tomcat" - }, - { - "type": "org.apache.catalina.util.CharsetMapper", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.commons.dbcp2.BasicDataSource" - }, - { - "type": "org.apache.commons.logging.LogFactory" - }, - { - "type": "org.apache.commons.logging.impl.Slf4jLogFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.commons.logging.impl.WeakHashtable", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.coyote.AbstractProtocol", - "methods": [ - { - "name": "getAddress", - "parameterTypes": [] - }, - { - "name": "getLocalPort", - "parameterTypes": [] - }, - { - "name": "getProperty", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPort", - "parameterTypes": [ - "int" - ] - }, - { - "name": "setProperty", - "parameterTypes": [ - "java.lang.String", - "java.lang.String" - ] - } - ] - }, - { - "type": "org.apache.coyote.UpgradeProtocol" - }, - { - "type": "org.apache.coyote.http11.AbstractHttp11Protocol", - "methods": [ - { - "name": "isSSLEnabled", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.coyote.http11.Http11NioProtocol" - }, - { - "type": "org.apache.derby.jdbc.EmbeddedDriver" - }, - { - "type": "org.apache.el.ExpressionFactoryImpl", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.hc.client5.http.classic.HttpClient" - }, - { - "type": "org.apache.ibatis.annotations.Mapper" - }, - { - "type": "org.apache.ibatis.executor.Executor", - "methods": [ - { - "name": "close", - "parameterTypes": [ - "boolean" - ] - }, - { - "name": "commit", - "parameterTypes": [ - "boolean" - ] - }, - { - "name": "update", - "parameterTypes": [ - "org.apache.ibatis.mapping.MappedStatement", - "java.lang.Object" - ] - } - ] - }, - { - "type": "org.apache.ibatis.executor.statement.StatementHandler", - "methods": [ - { - "name": "parameterize", - "parameterTypes": [ - "java.sql.Statement" - ] - }, - { - "name": "prepare", - "parameterTypes": [ - "java.sql.Connection", - "java.lang.Integer" - ] - }, - { - "name": "query", - "parameterTypes": [ - "java.sql.Statement", - "org.apache.ibatis.session.ResultHandler" - ] - }, - { - "name": "update", - "parameterTypes": [ - "java.sql.Statement" - ] - } - ] - }, - { - "type": "org.apache.ibatis.javassist.ClassPool" - }, - { - "type": "org.apache.ibatis.javassist.util.proxy.ProxyFactory" - }, - { - "type": "org.apache.ibatis.logging.slf4j.Slf4jImpl", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "org.apache.ibatis.ognl.OgnlRuntime$ClassPropertyMethodCache" - }, - { - "type": "org.apache.ibatis.plugin.Interceptor" - }, - { - "type": "org.apache.ibatis.plugin.Interceptor[]" - }, - { - "type": "org.apache.ibatis.plugin.Intercepts" - }, - { - "type": "org.apache.ibatis.plugin.Signature" - }, - { - "type": "org.apache.ibatis.reflection.SystemMetaObject$NullObject" - }, - { - "type": "org.apache.ibatis.scripting.LanguageDriver" - }, - { - "type": "org.apache.ibatis.scripting.defaults.RawLanguageDriver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.ibatis.scripting.xmltags.XMLLanguageDriver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.apache.ibatis.session.SqlSession", - "methods": [ - { - "name": "insert", - "parameterTypes": [ - "java.lang.String", - "java.lang.Object" - ] - }, - { - "name": "selectList", - "parameterTypes": [ - "java.lang.String", - "java.lang.Object" - ] - }, - { - "name": "selectOne", - "parameterTypes": [ - "java.lang.String", - "java.lang.Object" - ] - }, - { - "name": "update", - "parameterTypes": [ - "java.lang.String", - "java.lang.Object" - ] - } - ] - }, - { - "type": "org.apache.ibatis.session.SqlSessionFactory" - }, - { - "type": "org.apache.ibatis.session.defaults.DefaultSqlSessionFactory" - }, - { - "type": "org.apache.jasper.compiler.JspConfig" - }, - { - "type": "org.apache.jasper.servlet.JspServlet" - }, - { - "type": "org.apache.logging.log4j.core.impl.Log4jContextFactory" - }, - { - "type": "org.apache.logging.log4j.util.EnvironmentPropertySource" - }, - { - "type": "org.apache.logging.log4j.util.SystemPropertiesPropertySource" - }, - { - "type": "org.apache.logging.slf4j.SLF4JProvider" - }, - { - "type": "org.apache.tomcat.jdbc.pool.DataSource" - }, - { - "type": "org.apache.tomcat.util.net.AbstractEndpoint", - "methods": [ - { - "name": "setBindOnInit", - "parameterTypes": [ - "boolean" - ] - } - ] - }, - { - "type": "org.apache.tomcat.util.net.NioEndpoint" - }, - { - "type": "org.apache.tomcat.websocket.server.WsFilter" - }, - { - "type": "org.apache.tomcat.websocket.server.WsSci" - }, - { - "type": "org.aspectj.weaver.Advice" - }, - { - "type": "org.crac.Core" - }, - { - "type": "org.crac.Resource" - }, - { - "type": "org.eclipse.core.runtime.FileLocator" - }, - { - "type": "org.eclipse.jetty.client.HttpClient" - }, - { - "type": "org.graalvm.nativeimage.ImageInfo", - "methods": [ - { - "name": "inImageCode", - "parameterTypes": [] - } - ] - }, - { - "type": "org.h2.Driver" - }, - { - "type": "org.hibernate.validator.HibernateValidator" - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMaxValidator" - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMinValidator" - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MaxValidatorForInteger", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MinValidatorForInteger", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.hibernate.validator.internal.engine.AbstractConfigurationImpl", - "methods": [ - { - "name": "externalClassLoader", - "parameterTypes": [ - "java.lang.ClassLoader" - ] - } - ] - }, - { - "type": "org.hibernate.validator.internal.engine.ConfigurationImpl" - }, - { - "type": "org.hibernate.validator.internal.util.logging.Log_$logger", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.jboss.logging.Logger" - ] - } - ] - }, - { - "type": "org.hibernate.validator.internal.util.logging.Log_$logger_zh" - }, - { - "type": "org.hibernate.validator.internal.util.logging.Log_$logger_zh_CN" - }, - { - "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle", - "fields": [ - { - "name": "INSTANCE" - } - ] - }, - { - "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle_zh" - }, - { - "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle_zh_CN" - }, - { - "type": "org.hsqldb.jdbc.JDBCDriver" - }, - { - "type": "org.jboss.logging.Logger" - }, - { - "type": "org.joda.time.ReadableInstant" - }, - { - "type": "org.jspecify.annotations.NullMarked" - }, - { - "type": "org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver" - }, - { - "type": "org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig" - }, - { - "type": "org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver" - }, - { - "type": "org.mybatis.scripting.velocity.Driver" - }, - { - "type": "org.mybatis.scripting.velocity.VelocityLanguageDriver" - }, - { - "type": "org.mybatis.scripting.velocity.VelocityLanguageDriverConfig" - }, - { - "type": "org.mybatis.spring.SqlSessionFactoryBean" - }, - { - "type": "org.mybatis.spring.SqlSessionTemplate" - }, - { - "type": "org.mybatis.spring.annotation.MapperScan" - }, - { - "type": "org.mybatis.spring.annotation.MapperScannerRegistrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.mybatis.spring.mapper.MapperFactoryBean", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Class" - ] - }, - { - "name": "setAddToConfig", - "parameterTypes": [ - "boolean" - ] - }, - { - "name": "setMapperInterface", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "org.mybatis.spring.mapper.MapperScannerConfigurer", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setBasePackage", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setProcessPropertyPlaceHolders", - "parameterTypes": [ - "boolean" - ] - }, - { - "name": "setSqlSessionFactoryBeanName", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "org.mybatis.spring.support.SqlSessionDaoSupport", - "methods": [ - { - "name": "setSqlSessionFactory", - "parameterTypes": [ - "org.apache.ibatis.session.SqlSessionFactory" - ] - } - ] - }, - { - "type": "org.osgi.framework.FrameworkUtil" - }, - { - "type": "org.reactivestreams.Publisher" - }, - { - "type": "org.slf4j.bridge.SLF4JBridgeHandler" - }, - { - "type": "org.slf4j.helpers.Log4jLoggerFactory" - }, - { - "type": "org.slf4j.spi.LocationAwareLogger" - }, - { - "type": "org.springframework.aop.PointcutAdvisor" - }, - { - "type": "org.springframework.aop.SpringProxy" - }, - { - "type": "org.springframework.aop.TargetClassAware" - }, - { - "type": "org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor" - }, - { - "type": "org.springframework.aop.framework.Advised" - }, - { - "type": "org.springframework.aop.framework.AopConfigException" - }, - { - "type": "org.springframework.aop.framework.AopInfrastructureBean" - }, - { - "type": "org.springframework.aop.framework.AopProxyUtils" - }, - { - "type": "org.springframework.aop.framework.ProxyConfig", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setProxyTargetClass", - "parameterTypes": [ - "boolean" - ] - } - ] - }, - { - "type": "org.springframework.aop.framework.ProxyProcessorSupport", - "methods": [ - { - "name": "setOrder", - "parameterTypes": [ - "int" - ] - } - ] - }, - { - "type": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator" - }, - { - "type": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator" - }, - { - "type": "org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor" - }, - { - "type": "org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.aop.scope.ScopedObject" - }, - { - "type": "org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor" - }, - { - "type": "org.springframework.aop.support.AbstractPointcutAdvisor" - }, - { - "type": "org.springframework.aot.hint.annotation.Reflective" - }, - { - "type": "org.springframework.beans.factory.Aware" - }, - { - "type": "org.springframework.beans.factory.BeanClassLoaderAware" - }, - { - "type": "org.springframework.beans.factory.BeanFactoryAware" - }, - { - "type": "org.springframework.beans.factory.BeanNameAware" - }, - { - "type": "org.springframework.beans.factory.DisposableBean" - }, - { - "type": "org.springframework.beans.factory.FactoryBean" - }, - { - "type": "org.springframework.beans.factory.InitializingBean" - }, - { - "type": "org.springframework.beans.factory.SmartInitializingSingleton" - }, - { - "type": "org.springframework.beans.factory.annotation.Autowired" - }, - { - "type": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.beans.factory.annotation.Qualifier" - }, - { - "type": "org.springframework.beans.factory.annotation.Value" - }, - { - "type": "org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor" - }, - { - "type": "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor" - }, - { - "type": "org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter" - }, - { - "type": "org.springframework.beans.factory.config.BeanFactoryPostProcessor" - }, - { - "type": "org.springframework.beans.factory.config.BeanPostProcessor" - }, - { - "type": "org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor" - }, - { - "type": "org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor" - }, - { - "type": "org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor" - }, - { - "type": "org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor" - }, - { - "type": "org.springframework.beans.factory.support.NullBean" - }, - { - "type": "org.springframework.boot.ApplicationProperties", - "methods": [ - { - "name": "setAllowBeanDefinitionOverriding", - "parameterTypes": [ - "boolean" - ] - } - ] - }, - { - "type": "org.springframework.boot.ClearCachesApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.SpringBootConfiguration" - }, - { - "type": "org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint" - }, - { - "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration" - }, - { - "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties" - }, - { - "type": "org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory" - }, - { - "type": "org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository" - }, - { - "type": "org.springframework.boot.ansi.AnsiOutput$Enabled" - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfiguration" - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackage" - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.String[]" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigureAfter" - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigureBefore" - }, - { - "type": "org.springframework.boot.autoconfigure.AutoConfigureOrder" - }, - { - "type": "org.springframework.boot.autoconfigure.EnableAutoConfiguration" - }, - { - "type": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.SpringBootApplication" - }, - { - "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "forceAutoProxyCreatorToUseClassProxying", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "applicationAvailability", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBean" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnCheckpointRestore" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnClass" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnThreading" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type" - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnBeanCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnClassCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnPropertyCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnResourceCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnThreadingCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnWarDeploymentCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.condition.SearchStrategy" - }, - { - "type": "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "defaultLifecycleProcessor", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.context.LifecycleProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.context.LifecycleProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration$ResourceBundleCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "propertySourcesPlaceholderConfigurer", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.info.ProjectInfoProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration$GitResourceAvailableCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.info.ProjectInfoProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.ssl.FileWatcher" - }, - { - "type": "org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.core.io.ResourceLoader", - "org.springframework.boot.autoconfigure.ssl.SslProperties" - ] - }, - { - "name": "fileWatcher", - "parameterTypes": [] - }, - { - "name": "sslBundleRegistry", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "sslPropertiesSslBundleRegistrar", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.ssl.FileWatcher" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar" - }, - { - "type": "org.springframework.boot.autoconfigure.ssl.SslProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.ssl.SslPropertiesBundleRegistrar" - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ApplicationTaskExecutorAsyncConfigurer" - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "applicationTaskExecutorAsyncConfigurer", - "parameterTypes": [ - "org.springframework.beans.factory.BeanFactory" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration" - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "bootstrapExecutorAliasPostProcessor", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$OnExecutorCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "simpleAsyncTaskExecutorBuilder", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "applicationTaskExecutor", - "parameterTypes": [ - "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "threadPoolTaskExecutorBuilder", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "simpleAsyncTaskSchedulerBuilder", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration" - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "threadPoolTaskSchedulerBuilder", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain" - }, - { - "type": "org.springframework.boot.autoconfigure.web.OnEnabledResourceChainCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.web.WebProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getResources", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.web.WebProperties$Resources", - "methods": [ - { - "name": "setStaticLocations", - "parameterTypes": [ - "java.lang.String[]" - ] - } - ] - }, - { - "type": "org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHints" - }, - { - "type": "org.springframework.boot.autoconfigure.web.format.WebConversionService" - }, - { - "type": "org.springframework.boot.availability.ApplicationAvailability" - }, - { - "type": "org.springframework.boot.availability.ApplicationAvailabilityBean" - }, - { - "type": "org.springframework.boot.builder.ParentContextCloserApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.logging.DeferredLogFactory" - ] - } - ] - }, - { - "type": "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.ContextIdApplicationContextInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.FileEncodingApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.TypeExcludeFilter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.logging.DeferredLogFactory", - "org.springframework.boot.bootstrap.ConfigurableBootstrapContext" - ] - } - ] - }, - { - "type": "org.springframework.boot.context.config.ConfigDataLocation[]" - }, - { - "type": "org.springframework.boot.context.config.ConfigDataNotFoundAction" - }, - { - "type": "org.springframework.boot.context.config.ConfigDataProperties" - }, - { - "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLoader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.core.io.ResourceLoader" - ] - } - ] - }, - { - "type": "org.springframework.boot.context.config.StandardConfigDataLoader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.config.StandardConfigDataLocationResolver", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.logging.DeferredLogFactory", - "org.springframework.boot.context.properties.bind.Binder", - "org.springframework.core.io.ResourceLoader" - ] - } - ] - }, - { - "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.event.EventPublishingRunListener", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.SpringApplication", - "java.lang.String[]" - ] - } - ] - }, - { - "type": "org.springframework.boot.context.logging.LoggingApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.properties.BoundConfigurationProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.properties.ConfigurationProperties" - }, - { - "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder$ConfigurationPropertiesBinderFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.properties.ConfigurationPropertiesSource" - }, - { - "type": "org.springframework.boot.context.properties.EnableConfigurationProperties" - }, - { - "type": "org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.context.properties.NestedConfigurationProperty" - }, - { - "type": "org.springframework.boot.context.properties.bind.Name" - }, - { - "type": "org.springframework.boot.context.properties.bind.Nested" - }, - { - "type": "org.springframework.boot.convert.DurationUnit" - }, - { - "type": "org.springframework.boot.env.PropertiesPropertySourceLoader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.env.YamlPropertySourceLoader", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.health.actuate.endpoint.HealthEndpoint" - }, - { - "type": "org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.DefaultClientHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.DefaultServerHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "clientConvertersCustomizer", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "serverConvertersCustomizer", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "stringHttpMessageConvertersCustomizer", - "parameterTypes": [ - "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "jacksonJsonHttpMessageConvertersCustomizer", - "parameterTypes": [ - "tools.jackson.databind.json.JsonMapper" - ] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.JsonbHttpMessageConvertersConfiguration" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.KotlinSerializationHttpMessageConvertersConfiguration" - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.MessageConverterBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer" - }, - { - "type": "org.springframework.boot.io.Base64ProtocolResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.io.ClassPathResourceFilePathResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.io.ProtocolResolverApplicationContextInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jackson.JacksonComponentModule" - }, - { - "type": "org.springframework.boot.jackson.JacksonMixinModule" - }, - { - "type": "org.springframework.boot.jackson.JacksonMixinModuleEntries" - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "jacksonJsonMapper", - "parameterTypes": [ - "tools.jackson.databind.json.JsonMapper$Builder" - ] - }, - { - "name": "jsonComponentModule", - "parameterTypes": [] - }, - { - "name": "jsonMapperBuilder", - "parameterTypes": [ - "java.util.List" - ] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$AbstractMapperBuilderCustomizer" - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "standardJsonMapperBuilderCustomizer", - "parameterTypes": [ - "org.springframework.boot.jackson.autoconfigure.JacksonProperties", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration$StandardJsonMapperBuilderCustomizer" - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonMixinConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "jacksonMixinModule", - "parameterTypes": [ - "org.springframework.context.ApplicationContext", - "org.springframework.boot.jackson.JacksonMixinModuleEntries" - ] - }, - { - "name": "jacksonMixinModuleEntries", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "problemDetailJsonMapperBuilderCustomizer", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer" - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JacksonProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer" - }, - { - "type": "org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.ApplicationDataSourceScriptDatabaseInitializer" - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$EmbeddedDatabaseCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceAvailableCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceCheckpointRestoreConfiguration" - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "dataSourceScriptDatabaseInitializer", - "parameterTypes": [ - "javax.sql.DataSource", - "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints" - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "hikariPoolDataSourceMetadataProvider", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "transactionManager", - "parameterTypes": [ - "org.springframework.core.env.Environment", - "javax.sql.DataSource", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.JdbcClientAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "jdbcClient", - "parameterTypes": [ - "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" - ] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "jdbcTemplate", - "parameterTypes": [ - "javax.sql.DataSource", - "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.autoconfigure.NamedParameterJdbcTemplateConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "namedParameterJdbcTemplate", - "parameterTypes": [ - "org.springframework.jdbc.core.JdbcTemplate" - ] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer" - }, - { - "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" - }, - { - "type": "org.springframework.boot.loader.launch.JarLauncher", - "jniAccessible": true, - "methods": [ - { - "name": "main", - "parameterTypes": [ - "java.lang.String[]" - ] - } - ] - }, - { - "type": "org.springframework.boot.loader.launch.LaunchedClassLoader" - }, - { - "type": "org.springframework.boot.logging.LogLevelEditor" - }, - { - "type": "org.springframework.boot.logging.java.JavaLoggingSystem$Factory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.logging.log4j2.SpringBootPropertySource" - }, - { - "type": "org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.logging.logback.RootLogLevelConfigurator" - }, - { - "type": "org.springframework.boot.persistence.autoconfigure.PersistenceExceptionTranslationAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "persistenceExceptionTranslationPostProcessor", - "parameterTypes": [ - "org.springframework.core.env.Environment" - ] - } - ] - }, - { - "type": "org.springframework.boot.rsocket.server.RSocketServerCustomizer" - }, - { - "type": "org.springframework.boot.security.autoconfigure.MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "authenticationEventPublisher", - "parameterTypes": [ - "org.springframework.context.ApplicationEventPublisher" - ] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.SecurityProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration" - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity" - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.DefaultWebSecurityCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "securityFilterChainRegistration", - "parameterTypes": [ - "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration$EnableWebSecurityConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "pathPatternRequestMatcherBuilder", - "parameterTypes": [ - "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" - ] - } - ] - }, - { - "type": "org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "characterEncodingFilter", - "parameterTypes": [ - "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.servlet.autoconfigure.MultipartProperties" - ] - }, - { - "name": "multipartConfigElement", - "parameterTypes": [] - }, - { - "name": "multipartResolver", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.servlet.autoconfigure.MultipartProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter" - }, - { - "type": "org.springframework.boot.servlet.filter.OrderedFilter" - }, - { - "type": "org.springframework.boot.servlet.filter.OrderedFormContentFilter" - }, - { - "type": "org.springframework.boot.servlet.filter.OrderedRequestContextFilter" - }, - { - "type": "org.springframework.boot.sql.autoconfigure.init.ApplicationScriptDatabaseInitializer" - }, - { - "type": "org.springframework.boot.sql.autoconfigure.init.ConditionalOnSqlInitialization" - }, - { - "type": "org.springframework.boot.sql.autoconfigure.init.OnSqlInitializationCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer" - }, - { - "type": "org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.ssl.DefaultSslBundleRegistry" - }, - { - "type": "org.springframework.boot.ssl.SslBundleRegistry" - }, - { - "type": "org.springframework.boot.ssl.SslBundles" - }, - { - "type": "org.springframework.boot.support.AnsiOutputApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.support.RandomValuePropertySourceEnvironmentPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.logging.DeferredLogFactory" - ] - } - ] - }, - { - "type": "org.springframework.boot.support.SpringApplicationJsonEnvironmentPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.support.SystemEnvironmentPropertySourceEnvironmentPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder" - }, - { - "type": "org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder" - }, - { - "type": "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" - }, - { - "type": "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder" - }, - { - "type": "org.springframework.boot.thread.Threading" - }, - { - "type": "org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory" - }, - { - "type": "org.springframework.boot.tomcat.TomcatWebServerFactory" - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.TomcatBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "tomcatWebServerFactoryCustomizer", - "parameterTypes": [ - "org.springframework.core.env.Environment", - "org.springframework.boot.web.server.autoconfigure.ServerProperties", - "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", - "org.springframework.boot.autoconfigure.web.WebProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration$TomcatWebSocketConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "webSocketWebServerCustomizer", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerFactoryCustomizer" - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.WebSocketTomcatWebServerFactoryCustomizer" - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" - ] - }, - { - "name": "tomcatServletWebServerFactory", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "tomcatServletWebServerFactoryCustomizer", - "parameterTypes": [ - "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerFactoryCustomizer" - }, - { - "type": "org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory" - }, - { - "type": "org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory" - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.ExecutionListenersTransactionManagerCustomizer" - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration$TransactionTemplateConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "transactionTemplate", - "parameterTypes": [ - "org.springframework.transaction.PlatformTransactionManager" - ] - } - ] - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "platformTransactionManagerCustomizers", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "transactionExecutionListeners", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizer" - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers" - }, - { - "type": "org.springframework.boot.transaction.autoconfigure.TransactionProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.validation.autoconfigure.JakartaValidationBackgroundPreinitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.validation.autoconfigure.PrimaryDefaultValidatorPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "defaultValidator", - "parameterTypes": [ - "org.springframework.context.ApplicationContext", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "methodValidationPostProcessor", - "parameterTypes": [ - "org.springframework.core.env.Environment", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.validation.autoconfigure.ValidatorAdapter" - }, - { - "type": "org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor" - }, - { - "type": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", - "methods": [ - { - "name": "byAnnotation", - "parameterTypes": [ - "java.lang.Class" - ] - } - ] - }, - { - "type": "org.springframework.boot.web.context.reactive.FilteredReactiveWebContextResourceFilePathResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.context.servlet.ServletContextResourceFilePathResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.error.ErrorPageRegistrar" - }, - { - "type": "org.springframework.boot.web.error.ErrorPageRegistrarBeanPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.error.ErrorPageRegistry" - }, - { - "type": "org.springframework.boot.web.server.AbstractConfigurableWebServerFactory" - }, - { - "type": "org.springframework.boot.web.server.ConfigurableWebServerFactory" - }, - { - "type": "org.springframework.boot.web.server.WebServerFactory" - }, - { - "type": "org.springframework.boot.web.server.WebServerFactoryCustomizer" - }, - { - "type": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.server.autoconfigure.ServerProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setPort", - "parameterTypes": [ - "java.lang.Integer" - ] - } - ] - }, - { - "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "servletWebServerFactoryCustomizer", - "parameterTypes": [ - "org.springframework.boot.web.server.autoconfigure.ServerProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration$BeanPostProcessorsRegistrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerFactoryCustomizer" - }, - { - "type": "org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory" - }, - { - "type": "org.springframework.boot.web.server.servlet.ServletWebServerFactory" - }, - { - "type": "org.springframework.boot.web.server.servlet.WebListenerRegistry" - }, - { - "type": "org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.web.servlet.AbstractFilterRegistrationBean" - }, - { - "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean" - }, - { - "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1" - }, - { - "type": "org.springframework.boot.web.servlet.DynamicRegistrationBean" - }, - { - "type": "org.springframework.boot.web.servlet.FilterRegistrationBean" - }, - { - "type": "org.springframework.boot.web.servlet.RegistrationBean" - }, - { - "type": "org.springframework.boot.web.servlet.ServletContextInitializer" - }, - { - "type": "org.springframework.boot.web.servlet.ServletRegistrationBean" - }, - { - "type": "org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "dispatcherServlet", - "parameterTypes": [ - "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties" - ] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "dispatcherServletRegistration", - "parameterTypes": [ - "org.springframework.web.servlet.DispatcherServlet", - "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "formContentFilter", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$EnableWebMvcConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", - "org.springframework.boot.autoconfigure.web.WebProperties", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ListableBeanFactory" - ] - }, - { - "name": "flashMapManager", - "parameterTypes": [] - }, - { - "name": "localeResolver", - "parameterTypes": [] - }, - { - "name": "mvcApiVersionStrategy", - "parameterTypes": [] - }, - { - "name": "mvcConversionService", - "parameterTypes": [] - }, - { - "name": "mvcValidator", - "parameterTypes": [] - }, - { - "name": "viewNameTranslator", - "parameterTypes": [] - }, - { - "name": "welcomePageHandlerMapping", - "parameterTypes": [ - "org.springframework.context.ApplicationContext", - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - }, - { - "name": "welcomePageNotAcceptableHandlerMapping", - "parameterTypes": [ - "org.springframework.context.ApplicationContext", - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.web.WebProperties", - "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", - "org.springframework.beans.factory.ListableBeanFactory", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "defaultViewResolver", - "parameterTypes": [] - }, - { - "name": "requestContextFilter", - "parameterTypes": [] - }, - { - "name": "viewResolver", - "parameterTypes": [ - "org.springframework.beans.factory.BeanFactory" - ] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageHandlerMapping" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageNotAcceptableHandlerMapping" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.DefaultErrorViewResolver" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.boot.autoconfigure.web.WebProperties" - ] - }, - { - "name": "errorAttributes", - "parameterTypes": [] - }, - { - "name": "errorPageCustomizer", - "parameterTypes": [ - "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" - ] - }, - { - "name": "preserveErrorControllerTargetClassPostProcessor", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [ - "org.springframework.context.ApplicationContext", - "org.springframework.boot.autoconfigure.web.WebProperties" - ] - }, - { - "name": "conventionErrorViewResolver", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorPageCustomizer" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$StaticView" - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "beanNameViewResolver", - "parameterTypes": [] - }, - { - "name": "defaultErrorView", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver" - }, - { - "type": "org.springframework.boot.webmvc.error.DefaultErrorAttributes" - }, - { - "type": "org.springframework.boot.webmvc.error.ErrorAttributes" - }, - { - "type": "org.springframework.boot.webmvc.error.ErrorController" - }, - { - "type": "org.springframework.cglib.proxy.Dispatcher" - }, - { - "type": "org.springframework.cglib.proxy.MethodInterceptor" - }, - { - "type": "org.springframework.cglib.proxy.NoOp" - }, - { - "type": "org.springframework.cloud.commons.util.InetUtils" - }, - { - "type": "org.springframework.context.ApplicationContextAware" - }, - { - "type": "org.springframework.context.ApplicationEventPublisherAware" - }, - { - "type": "org.springframework.context.ApplicationListener" - }, - { - "type": "org.springframework.context.ApplicationStartupAware" - }, - { - "type": "org.springframework.context.EmbeddedValueResolverAware" - }, - { - "type": "org.springframework.context.EnvironmentAware" - }, - { - "type": "org.springframework.context.Lifecycle" - }, - { - "type": "org.springframework.context.LifecycleProcessor" - }, - { - "type": "org.springframework.context.MessageSourceAware" - }, - { - "type": "org.springframework.context.Phased" - }, - { - "type": "org.springframework.context.ResourceLoaderAware" - }, - { - "type": "org.springframework.context.SmartLifecycle" - }, - { - "type": "org.springframework.context.annotation.AnnotationScopeMetadataResolver", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.context.annotation.AutoProxyRegistrar", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.context.annotation.Bean" - }, - { - "type": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.context.annotation.ComponentScan" - }, - { - "type": "org.springframework.context.annotation.ComponentScan$Filter" - }, - { - "type": "org.springframework.context.annotation.Conditional" - }, - { - "type": "org.springframework.context.annotation.Configuration" - }, - { - "type": "org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration" - }, - { - "type": "org.springframework.context.annotation.ConfigurationClassPostProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setMetadataReaderFactory", - "parameterTypes": [ - "org.springframework.core.type.classreading.MetadataReaderFactory" - ] - } - ] - }, - { - "type": "org.springframework.context.annotation.DependsOn" - }, - { - "type": "org.springframework.context.annotation.Import" - }, - { - "type": "org.springframework.context.annotation.ImportAware" - }, - { - "type": "org.springframework.context.annotation.ImportRuntimeHints" - }, - { - "type": "org.springframework.context.annotation.Lazy" - }, - { - "type": "org.springframework.context.annotation.Primary" - }, - { - "type": "org.springframework.context.annotation.Role" - }, - { - "type": "org.springframework.context.annotation.Scope" - }, - { - "type": "org.springframework.context.event.DefaultEventListenerFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.context.event.EventListenerMethodProcessor", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.context.support.ApplicationObjectSupport" - }, - { - "type": "org.springframework.context.support.DefaultLifecycleProcessor" - }, - { - "type": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer" - }, - { - "type": "org.springframework.core.GenericTypeResolver" - }, - { - "type": "org.springframework.core.Ordered" - }, - { - "type": "org.springframework.core.PriorityOrdered" - }, - { - "type": "org.springframework.core.annotation.AliasFor" - }, - { - "type": "org.springframework.core.annotation.AnnotationAttributes[]" - }, - { - "type": "org.springframework.core.annotation.Order" - }, - { - "type": "org.springframework.core.convert.ConversionService" - }, - { - "type": "org.springframework.core.convert.converter.ConverterRegistry" - }, - { - "type": "org.springframework.core.convert.support.ConfigurableConversionService" - }, - { - "type": "org.springframework.core.convert.support.GenericConversionService" - }, - { - "type": "org.springframework.core.env.EnvironmentCapable" - }, - { - "type": "org.springframework.core.task.AsyncTaskExecutor" - }, - { - "type": "org.springframework.core.task.TaskExecutor" - }, - { - "type": "org.springframework.core.type.classreading.CachingMetadataReaderFactory" - }, - { - "type": "org.springframework.core.type.classreading.MetadataReaderFactory" - }, - { - "type": "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" - }, - { - "type": "org.springframework.dao.support.DaoSupport" - }, - { - "type": "org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter" - }, - { - "type": "org.springframework.format.FormatterRegistry" - }, - { - "type": "org.springframework.format.support.DefaultFormattingConversionService" - }, - { - "type": "org.springframework.format.support.FormattingConversionService" - }, - { - "type": "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter" - }, - { - "type": "org.springframework.http.ProblemDetail" - }, - { - "type": "org.springframework.http.ReactiveHttpInputMessage" - }, - { - "type": "org.springframework.http.converter.HttpMessageConverter" - }, - { - "type": "org.springframework.http.converter.StringHttpMessageConverter" - }, - { - "type": "org.springframework.http.converter.json.JacksonJsonHttpMessageConverter" - }, - { - "type": "org.springframework.jdbc.core.JdbcOperations" - }, - { - "type": "org.springframework.jdbc.core.JdbcTemplate" - }, - { - "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations" - }, - { - "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" - }, - { - "type": "org.springframework.jdbc.core.simple.DefaultJdbcClient" - }, - { - "type": "org.springframework.jdbc.core.simple.JdbcClient" - }, - { - "type": "org.springframework.jdbc.datasource.DataSourceTransactionManager" - }, - { - "type": "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType" - }, - { - "type": "org.springframework.jdbc.datasource.init.DatabasePopulator" - }, - { - "type": "org.springframework.jdbc.support.JdbcAccessor" - }, - { - "type": "org.springframework.jdbc.support.JdbcTransactionManager" - }, - { - "type": "org.springframework.jmx.export.MBeanExporter" - }, - { - "type": "org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler" - }, - { - "type": "org.springframework.scheduling.SchedulingTaskExecutor" - }, - { - "type": "org.springframework.scheduling.annotation.AsyncConfigurer" - }, - { - "type": "org.springframework.scheduling.concurrent.CustomizableThreadFactory" - }, - { - "type": "org.springframework.scheduling.concurrent.ExecutorConfigurationSupport" - }, - { - "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" - }, - { - "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" - }, - { - "type": "org.springframework.security.access.SecurityConfig" - }, - { - "type": "org.springframework.security.access.expression.AbstractSecurityExpressionHandler" - }, - { - "type": "org.springframework.security.access.expression.SecurityExpressionHandler" - }, - { - "type": "org.springframework.security.authentication.AnonymousAuthenticationProvider" - }, - { - "type": "org.springframework.security.authentication.AuthenticationEventPublisher" - }, - { - "type": "org.springframework.security.authentication.AuthenticationManager" - }, - { - "type": "org.springframework.security.authentication.AuthenticationManagerResolver" - }, - { - "type": "org.springframework.security.authentication.AuthenticationProvider" - }, - { - "type": "org.springframework.security.authentication.DefaultAuthenticationEventPublisher" - }, - { - "type": "org.springframework.security.authentication.ProviderManager" - }, - { - "type": "org.springframework.security.authentication.ReactiveAuthenticationManager" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureLockedEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent" - }, - { - "type": "org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent" - }, - { - "type": "org.springframework.security.authorization.AuthorizationManager" - }, - { - "type": "org.springframework.security.config.ObjectPostProcessor" - }, - { - "type": "org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder" - }, - { - "type": "org.springframework.security.config.annotation.AbstractSecurityBuilder" - }, - { - "type": "org.springframework.security.config.annotation.SecurityBuilder" - }, - { - "type": "org.springframework.security.config.annotation.SecurityConfigurer" - }, - { - "type": "org.springframework.security.config.annotation.authentication.ProviderManagerBuilder" - }, - { - "type": "org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "authenticationManagerBuilder", - "parameterTypes": [ - "org.springframework.security.config.ObjectPostProcessor", - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "enableGlobalAuthenticationAutowiredConfigurer", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "initializeAuthenticationProviderBeanManagerConfigurer", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "initializeUserDetailsBeanManagerConfigurer", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "setApplicationContext", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "setGlobalAuthenticationConfigurers", - "parameterTypes": [ - "java.util.List" - ] - }, - { - "name": "setObjectPostProcessor", - "parameterTypes": [ - "org.springframework.security.config.ObjectPostProcessor" - ] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeAuthenticationProviderBeanManagerConfigurer" - }, - { - "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer" - }, - { - "type": "org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor" - }, - { - "type": "org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "objectPostProcessor", - "parameterTypes": [ - "org.springframework.beans.factory.config.AutowireCapableBeanFactory" - ] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.HttpSecurityBuilder" - }, - { - "type": "org.springframework.security.config.annotation.web.builders.HttpSecurity" - }, - { - "type": "org.springframework.security.config.annotation.web.builders.WebSecurity" - }, - { - "type": "org.springframework.security.config.annotation.web.builders.WebSecurity$SecurityExpressionHandlerAdapter" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.EnableWebSecurity" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "httpSecurity", - "parameterTypes": [] - }, - { - "name": "setApplicationContext", - "parameterTypes": [ - "org.springframework.context.ApplicationContext" - ] - }, - { - "name": "setAuthenticationConfiguration", - "parameterTypes": [ - "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration" - ] - }, - { - "name": "setContentNegotiationStrategy", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationStrategy" - ] - }, - { - "name": "setObjectPostProcessor", - "parameterTypes": [ - "org.springframework.security.config.ObjectPostProcessor" - ] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.OAuth2ImportSelector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "authenticationManagerPostProcessor", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "filterChainDecoratorPostProcessor", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "webAuthorizationManagerPostProcessor", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider", - "org.springframework.beans.factory.ObjectProvider" - ] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$1" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$2" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$3" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.ObservationImportSelector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.SpringWebMvcImportSelector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "requestDataValueProcessor", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "conversionServicePostProcessor", - "parameterTypes": [] - }, - { - "name": "delegatingApplicationListener", - "parameterTypes": [] - }, - { - "name": "privilegeEvaluator", - "parameterTypes": [] - }, - { - "name": "setFilterChainProxySecurityConfigurer", - "parameterTypes": [ - "org.springframework.security.config.ObjectPostProcessor", - "org.springframework.beans.factory.config.ConfigurableListableBeanFactory" - ] - }, - { - "name": "setFilterChains", - "parameterTypes": [ - "java.util.List" - ] - }, - { - "name": "springSecurityFilterChain", - "parameterTypes": [ - "org.springframework.beans.factory.ObjectProvider" - ] - }, - { - "name": "springSecurityPathPatternParserBeanDefinitionRegistryPostProcessor", - "parameterTypes": [] - }, - { - "name": "webSecurityExpressionHandler", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$1" - }, - { - "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.util.List" - ] - } - ] - }, - { - "type": "org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor" - }, - { - "type": "org.springframework.security.config.http.SessionCreationPolicy" - }, - { - "type": "org.springframework.security.context.DelegatingApplicationListener" - }, - { - "type": "org.springframework.security.core.userdetails.UserDetailsService" - }, - { - "type": "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" - }, - { - "type": "org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder" - }, - { - "type": "org.springframework.security.crypto.password.PasswordEncoder" - }, - { - "type": "org.springframework.security.data.repository.query.SecurityEvaluationContextExtension" - }, - { - "type": "org.springframework.security.oauth2.client.registration.ClientRegistration" - }, - { - "type": "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository" - }, - { - "type": "org.springframework.security.oauth2.jwt.JwtDecoder" - }, - { - "type": "org.springframework.security.oauth2.server.resource.BearerTokenError" - }, - { - "type": "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" - }, - { - "type": "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" - }, - { - "type": "org.springframework.security.web.DefaultSecurityFilterChain" - }, - { - "type": "org.springframework.security.web.FilterChainProxy" - }, - { - "type": "org.springframework.security.web.SecurityFilterChain" - }, - { - "type": "org.springframework.security.web.access.ExceptionTranslationFilter" - }, - { - "type": "org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator" - }, - { - "type": "org.springframework.security.web.access.WebInvocationPrivilegeEvaluator" - }, - { - "type": "org.springframework.security.web.access.intercept.AuthorizationFilter" - }, - { - "type": "org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager" - }, - { - "type": "org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" - }, - { - "type": "org.springframework.security.web.authentication.logout.LogoutFilter" - }, - { - "type": "org.springframework.security.web.authentication.logout.LogoutHandler" - }, - { - "type": "org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler" - }, - { - "type": "org.springframework.security.web.authentication.session.AbstractSessionFixationProtectionStrategy" - }, - { - "type": "org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy" - }, - { - "type": "org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" - }, - { - "type": "org.springframework.security.web.authentication.session.SessionAuthenticationStrategy" - }, - { - "type": "org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer" - }, - { - "type": "org.springframework.security.web.context.SecurityContextHolderFilter" - }, - { - "type": "org.springframework.security.web.csrf.CsrfFilter" - }, - { - "type": "org.springframework.security.web.header.HeaderWriterFilter" - }, - { - "type": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter" - }, - { - "type": "org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" - }, - { - "type": "org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher$Builder" - }, - { - "type": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" - }, - { - "type": "org.springframework.security.web.session.SessionManagementFilter" - }, - { - "type": "org.springframework.stereotype.Component" - }, - { - "type": "org.springframework.stereotype.Controller" - }, - { - "type": "org.springframework.stereotype.Indexed" - }, - { - "type": "org.springframework.stereotype.Service" - }, - { - "type": "org.springframework.transaction.ConfigurableTransactionManager" - }, - { - "type": "org.springframework.transaction.PlatformTransactionManager" - }, - { - "type": "org.springframework.transaction.ReactiveTransactionManager" - }, - { - "type": "org.springframework.transaction.TransactionDefinition" - }, - { - "type": "org.springframework.transaction.TransactionManager" - }, - { - "type": "org.springframework.transaction.annotation.AbstractTransactionManagementConfiguration", - "methods": [ - { - "name": "transactionAttributeSource", - "parameterTypes": [] - }, - { - "name": "transactionalEventListenerFactory", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" - }, - { - "type": "org.springframework.transaction.annotation.EnableTransactionManagement" - }, - { - "type": "org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "transactionAdvisor", - "parameterTypes": [ - "org.springframework.transaction.interceptor.TransactionAttributeSource", - "org.springframework.transaction.interceptor.TransactionInterceptor" - ] - }, - { - "name": "transactionInterceptor", - "parameterTypes": [ - "org.springframework.transaction.interceptor.TransactionAttributeSource" - ] - } - ] - }, - { - "type": "org.springframework.transaction.annotation.RestrictedTransactionalEventListenerFactory" - }, - { - "type": "org.springframework.transaction.annotation.TransactionManagementConfigurationSelector", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.transaction.annotation.TransactionRuntimeHints" - }, - { - "type": "org.springframework.transaction.annotation.Transactional" - }, - { - "type": "org.springframework.transaction.aspectj.AbstractTransactionAspect" - }, - { - "type": "org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource" - }, - { - "type": "org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor" - }, - { - "type": "org.springframework.transaction.interceptor.TransactionAspectSupport" - }, - { - "type": "org.springframework.transaction.interceptor.TransactionAttributeSource" - }, - { - "type": "org.springframework.transaction.interceptor.TransactionInterceptor" - }, - { - "type": "org.springframework.transaction.support.AbstractPlatformTransactionManager" - }, - { - "type": "org.springframework.transaction.support.DefaultTransactionDefinition" - }, - { - "type": "org.springframework.transaction.support.ResourceTransactionManager" - }, - { - "type": "org.springframework.transaction.support.TransactionOperations" - }, - { - "type": "org.springframework.transaction.support.TransactionTemplate" - }, - { - "type": "org.springframework.util.AntPathMatcher" - }, - { - "type": "org.springframework.util.ConcurrentReferenceHashMap$Segment[]" - }, - { - "type": "org.springframework.util.CustomizableThreadCreator" - }, - { - "type": "org.springframework.util.PathMatcher" - }, - { - "type": "org.springframework.validation.SmartValidator" - }, - { - "type": "org.springframework.validation.Validator" - }, - { - "type": "org.springframework.validation.annotation.Validated" - }, - { - "type": "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" - }, - { - "type": "org.springframework.validation.beanvalidation.MethodValidationPostProcessor" - }, - { - "type": "org.springframework.validation.beanvalidation.SpringValidatorAdapter" - }, - { - "type": "org.springframework.web.accept.ApiVersionStrategy" - }, - { - "type": "org.springframework.web.accept.ApiVersionStrategyEditor" - }, - { - "type": "org.springframework.web.accept.ContentNegotiationManager" - }, - { - "type": "org.springframework.web.accept.ContentNegotiationStrategy" - }, - { - "type": "org.springframework.web.accept.MediaTypeFileExtensionResolver" - }, - { - "type": "org.springframework.web.bind.annotation.ControllerAdvice" - }, - { - "type": "org.springframework.web.bind.annotation.DeleteMapping" - }, - { - "type": "org.springframework.web.bind.annotation.ExceptionHandler" - }, - { - "type": "org.springframework.web.bind.annotation.GetMapping" - }, - { - "type": "org.springframework.web.bind.annotation.Mapping" - }, - { - "type": "org.springframework.web.bind.annotation.PathVariable" - }, - { - "type": "org.springframework.web.bind.annotation.PostMapping" - }, - { - "type": "org.springframework.web.bind.annotation.PutMapping" - }, - { - "type": "org.springframework.web.bind.annotation.RequestBody" - }, - { - "type": "org.springframework.web.bind.annotation.RequestHeader" - }, - { - "type": "org.springframework.web.bind.annotation.RequestMapping" - }, - { - "type": "org.springframework.web.bind.annotation.RequestMethod[]" - }, - { - "type": "org.springframework.web.bind.annotation.RequestParam" - }, - { - "type": "org.springframework.web.bind.annotation.ResponseBody" - }, - { - "type": "org.springframework.web.bind.annotation.RestController" - }, - { - "type": "org.springframework.web.bind.annotation.RestControllerAdvice" - }, - { - "type": "org.springframework.web.client.RestClientException" - }, - { - "type": "org.springframework.web.context.ConfigurableWebApplicationContext" - }, - { - "type": "org.springframework.web.context.ServletContextAware" - }, - { - "type": "org.springframework.web.context.request.RequestContextListener" - }, - { - "type": "org.springframework.web.context.support.GenericWebApplicationContext" - }, - { - "type": "org.springframework.web.context.support.ServletContextResource" - }, - { - "type": "org.springframework.web.context.support.WebApplicationObjectSupport" - }, - { - "type": "org.springframework.web.filter.CharacterEncodingFilter" - }, - { - "type": "org.springframework.web.filter.DelegatingFilterProxy" - }, - { - "type": "org.springframework.web.filter.FormContentFilter" - }, - { - "type": "org.springframework.web.filter.GenericFilterBean" - }, - { - "type": "org.springframework.web.filter.OncePerRequestFilter" - }, - { - "type": "org.springframework.web.filter.RequestContextFilter" - }, - { - "type": "org.springframework.web.filter.ServletRequestPathFilter", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.springframework.web.method.annotation.ExceptionHandlerMethodResolver" - }, - { - "type": "org.springframework.web.method.support.CompositeUriComponentsContributor" - }, - { - "type": "org.springframework.web.method.support.UriComponentsContributor" - }, - { - "type": "org.springframework.web.multipart.MultipartResolver" - }, - { - "type": "org.springframework.web.multipart.support.StandardServletMultipartResolver" - }, - { - "type": "org.springframework.web.reactive.HandlerResult" - }, - { - "type": "org.springframework.web.reactive.function.client.ExchangeFilterFunction" - }, - { - "type": "org.springframework.web.reactive.function.client.WebClientException" - }, - { - "type": "org.springframework.web.servlet.DispatcherServlet" - }, - { - "type": "org.springframework.web.servlet.FlashMapManager" - }, - { - "type": "org.springframework.web.servlet.FrameworkServlet" - }, - { - "type": "org.springframework.web.servlet.HandlerAdapter" - }, - { - "type": "org.springframework.web.servlet.HandlerExceptionResolver" - }, - { - "type": "org.springframework.web.servlet.HandlerInterceptor" - }, - { - "type": "org.springframework.web.servlet.HandlerMapping" - }, - { - "type": "org.springframework.web.servlet.HandlerMappingEditor" - }, - { - "type": "org.springframework.web.servlet.HttpServletBean" - }, - { - "type": "org.springframework.web.servlet.LocaleResolver" - }, - { - "type": "org.springframework.web.servlet.RequestToViewNameTranslator" - }, - { - "type": "org.springframework.web.servlet.View" - }, - { - "type": "org.springframework.web.servlet.ViewResolver" - }, - { - "type": "org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration", - "methods": [ - { - "name": "setConfigurers", - "parameterTypes": [ - "java.util.List" - ] - } - ] - }, - { - "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", - "methods": [ - { - "name": "beanNameHandlerMapping", - "parameterTypes": [ - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - }, - { - "name": "defaultServletHandlerMapping", - "parameterTypes": [] - }, - { - "name": "handlerExceptionResolver", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationManager" - ] - }, - { - "name": "handlerFunctionAdapter", - "parameterTypes": [] - }, - { - "name": "httpRequestHandlerAdapter", - "parameterTypes": [] - }, - { - "name": "mvcContentNegotiationManager", - "parameterTypes": [] - }, - { - "name": "mvcPathMatcher", - "parameterTypes": [] - }, - { - "name": "mvcPatternParser", - "parameterTypes": [] - }, - { - "name": "mvcResourceUrlProvider", - "parameterTypes": [] - }, - { - "name": "mvcUriComponentsContributor", - "parameterTypes": [ - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" - ] - }, - { - "name": "mvcUrlPathHelper", - "parameterTypes": [] - }, - { - "name": "mvcViewResolver", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationManager" - ] - }, - { - "name": "requestMappingHandlerAdapter", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationManager", - "org.springframework.format.support.FormattingConversionService", - "org.springframework.validation.Validator" - ] - }, - { - "name": "requestMappingHandlerMapping", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationManager", - "org.springframework.web.accept.ApiVersionStrategy", - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - }, - { - "name": "resourceHandlerMapping", - "parameterTypes": [ - "org.springframework.web.accept.ContentNegotiationManager", - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - }, - { - "name": "routerFunctionMapping", - "parameterTypes": [ - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider", - "org.springframework.web.accept.ApiVersionStrategy" - ] - }, - { - "name": "simpleControllerHandlerAdapter", - "parameterTypes": [] - }, - { - "name": "viewControllerHandlerMapping", - "parameterTypes": [ - "org.springframework.format.support.FormattingConversionService", - "org.springframework.web.servlet.resource.ResourceUrlProvider" - ] - } - ] - }, - { - "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurer" - }, - { - "type": "org.springframework.web.servlet.function.support.HandlerFunctionAdapter" - }, - { - "type": "org.springframework.web.servlet.function.support.RouterFunctionMapping" - }, - { - "type": "org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.handler.AbstractHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping" - }, - { - "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler" - }, - { - "type": "org.springframework.web.servlet.handler.AbstractUrlHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.handler.HandlerExceptionResolverComposite" - }, - { - "type": "org.springframework.web.servlet.handler.MatchableHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.i18n.AbstractLocaleResolver" - }, - { - "type": "org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" - }, - { - "type": "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" - }, - { - "type": "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" - }, - { - "type": "org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter" - }, - { - "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler" - }, - { - "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" - }, - { - "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" - }, - { - "type": "org.springframework.web.servlet.resource.ResourceUrlProvider" - }, - { - "type": "org.springframework.web.servlet.support.AbstractFlashMapManager" - }, - { - "type": "org.springframework.web.servlet.support.RequestDataValueProcessor" - }, - { - "type": "org.springframework.web.servlet.support.SessionFlashMapManager" - }, - { - "type": "org.springframework.web.servlet.support.WebContentGenerator" - }, - { - "type": "org.springframework.web.servlet.view.AbstractCachingViewResolver" - }, - { - "type": "org.springframework.web.servlet.view.AbstractUrlBasedView" - }, - { - "type": "org.springframework.web.servlet.view.AbstractView" - }, - { - "type": "org.springframework.web.servlet.view.BeanNameViewResolver" - }, - { - "type": "org.springframework.web.servlet.view.ContentNegotiatingViewResolver" - }, - { - "type": "org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator" - }, - { - "type": "org.springframework.web.servlet.view.InternalResourceView" - }, - { - "type": "org.springframework.web.servlet.view.InternalResourceViewResolver" - }, - { - "type": "org.springframework.web.servlet.view.UrlBasedViewResolver" - }, - { - "type": "org.springframework.web.servlet.view.ViewResolverComposite" - }, - { - "type": "org.springframework.web.util.UrlPathHelper" - }, - { - "type": "org.springframework.web.util.pattern.PathPatternParser" - }, - { - "type": "org.sqlite.BusyHandler", - "jniAccessible": true, - "methods": [ - { - "name": "callback", - "parameterTypes": [ - "int" - ] - } - ] - }, - { - "type": "org.sqlite.Collation", - "jniAccessible": true, - "methods": [ - { - "name": "xCompare", - "parameterTypes": [ - "java.lang.String", - "java.lang.String" - ] - } - ] - }, - { - "type": "org.sqlite.Function", - "jniAccessible": true, - "fields": [ - { - "name": "args" - }, - { - "name": "context" - }, - { - "name": "value" - } - ], - "methods": [ - { - "name": "xFunc", - "parameterTypes": [] - } - ] - }, - { - "type": "org.sqlite.Function$Aggregate", - "jniAccessible": true, - "methods": [ - { - "name": "clone", - "parameterTypes": [] - }, - { - "name": "xFinal", - "parameterTypes": [] - }, - { - "name": "xStep", - "parameterTypes": [] - } - ] - }, - { - "type": "org.sqlite.Function$Window", - "jniAccessible": true, - "methods": [ - { - "name": "xInverse", - "parameterTypes": [] - }, - { - "name": "xValue", - "parameterTypes": [] - } - ] - }, - { - "type": "org.sqlite.JDBC", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.sqlite.ProgressHandler", - "jniAccessible": true, - "methods": [ - { - "name": "progress", - "parameterTypes": [] - } - ] - }, - { - "type": "org.sqlite.core.DB", - "jniAccessible": true, - "methods": [ - { - "name": "onCommit", - "parameterTypes": [ - "boolean" - ] - }, - { - "name": "onUpdate", - "parameterTypes": [ - "int", - "java.lang.String", - "java.lang.String", - "long" - ] - }, - { - "name": "throwex", - "parameterTypes": [] - }, - { - "name": "throwex", - "parameterTypes": [ - "int" - ] - } - ] - }, - { - "type": "org.sqlite.core.DB$ProgressObserver", - "jniAccessible": true, - "methods": [ - { - "name": "progress", - "parameterTypes": [ - "int", - "int" - ] - } - ] - }, - { - "type": "org.sqlite.core.NativeDB", - "jniAccessible": true, - "fields": [ - { - "name": "busyHandler" - }, - { - "name": "commitListener" - }, - { - "name": "pointer" - }, - { - "name": "progressHandler" - }, - { - "name": "updateListener" - } - ], - "methods": [ - { - "name": "stringToUtf8ByteArray", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "throwex", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "type": "org.webjars.WebJarAssetLocator" - }, - { - "type": "org.webjars.WebJarVersionLocator" - }, - { - "type": "org.yaml.snakeyaml.Yaml" - }, - { - "type": "reactor.core.publisher.Flux" - }, - { - "type": "reactor.core.publisher.Mono" - }, - { - "type": "reactor.netty.http.client.HttpClient" - }, - { - "type": "sun.launcher.LauncherHelper", - "jniAccessible": true, - "fields": [ - { - "name": "isStaticMain" - }, - { - "name": "noArgMain" - } - ], - "methods": [ - { - "name": "getApplicationClass", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.management.VMManagementImpl", - "jniAccessible": true, - "fields": [ - { - "name": "compTimeMonitoringSupport" - }, - { - "name": "currentThreadCpuTimeSupport" - }, - { - "name": "objectMonitorUsageSupport" - }, - { - "name": "otherThreadCpuTimeSupport" - }, - { - "name": "remoteDiagnosticCommandsSupport" - }, - { - "name": "synchronizerUsageSupport" - }, - { - "name": "threadAllocatedMemorySupport" - }, - { - "name": "threadContentionMonitoringSupport" - } - ] - }, - { - "type": "sun.misc.Unsafe", - "fields": [ - { - "name": "theUnsafe" - } - ] - }, - { - "type": "sun.reflect.ReflectionFactory", - "methods": [ - { - "name": "getReflectionFactory", - "parameterTypes": [] - }, - { - "name": "newConstructorForSerialization", - "parameterTypes": [ - "java.lang.Class", - "java.lang.reflect.Constructor" - ] - } - ] - }, - { - "type": "sun.security.pkcs12.PKCS12KeyStore", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.DRBG", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.security.SecureRandomParameters" - ] - } - ] - }, - { - "type": "sun.security.provider.DSA$SHA224withDSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.DSA$SHA256withDSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.JavaKeyStore$DualFormatJKS", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.JavaKeyStore$JKS", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SHA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SHA2$SHA224", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SHA2$SHA256", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SHA5$SHA384", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SHA5$SHA512", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.SecureRandom", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.X509Factory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.provider.certpath.PKIXCertPathValidator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.rsa.PSSParameters", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.rsa.RSAKeyFactory$Legacy", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.rsa.RSAPSSSignature", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.rsa.RSASignature$SHA224withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.rsa.RSASignature$SHA256withRSA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "sun.security.x509.AuthorityInfoAccessExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.AuthorityKeyIdentifierExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.BasicConstraintsExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.CRLDistributionPointsExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.CertificatePoliciesExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.ExtendedKeyUsageExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.KeyUsageExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.NetscapeCertTypeExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.PrivateKeyUsageExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.SubjectAlternativeNameExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.security.x509.SubjectKeyIdentifierExtension", - "methods": [ - { - "name": "", - "parameterTypes": [ - "java.lang.Boolean", - "java.lang.Object" - ] - } - ] - }, - { - "type": "sun.text.resources.cldr.FormatData" - }, - { - "type": "sun.text.resources.cldr.FormatData_en" - }, - { - "type": "sun.text.resources.cldr.FormatData_en_US" - }, - { - "type": "sun.util.resources.cldr.CalendarData" - }, - { - "type": "sun.util.resources.cldr.TimeZoneNames" - }, - { - "type": "sun.util.resources.cldr.TimeZoneNames_en" - }, - { - "type": "sun.util.resources.cldr.TimeZoneNames_en_US" - }, - { - "type": "tools.jackson.core.TreeCodec" - }, - { - "type": "tools.jackson.core.Versioned" - }, - { - "type": "tools.jackson.databind.JacksonModule" - }, - { - "type": "tools.jackson.databind.ObjectMapper" - }, - { - "type": "tools.jackson.databind.cfg.MapperBuilder" - }, - { - "type": "tools.jackson.databind.deser.Deserializers[]" - }, - { - "type": "tools.jackson.databind.deser.KeyDeserializers[]" - }, - { - "type": "tools.jackson.databind.deser.ValueInstantiators[]" - }, - { - "type": "tools.jackson.databind.json.JsonMapper" - }, - { - "type": "tools.jackson.databind.json.JsonMapper$Builder" - }, - { - "type": "tools.jackson.databind.module.SimpleModule" - }, - { - "type": "tools.jackson.databind.ser.Serializers[]" - }, - { - "type": "tools.jackson.dataformat.cbor.CBORMapper" - }, - { - "type": "tools.jackson.dataformat.smile.SmileMapper" - }, - { - "type": "tools.jackson.dataformat.xml.XmlMapper" - }, - { - "type": "tools.jackson.dataformat.yaml.YAMLMapper" - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" - ] - } - }, - { - "type": { - "proxy": [ - "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" - ] - } - }, - { - "type": { - "proxy": [ - "java.lang.reflect.ParameterizedType", - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - "java.io.Serializable" - ] - } - }, - { - "type": { - "proxy": [ - "java.lang.reflect.TypeVariable", - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - "java.io.Serializable" - ] - } - }, - { - "type": { - "proxy": [ - "java.lang.reflect.WildcardType", - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - "java.io.Serializable" - ] - } - }, - { - "type": { - "proxy": [ - "java.sql.Connection" - ] - } - }, - { - "type": { - "proxy": [ - "org.apache.ibatis.executor.Executor" - ] - } - }, - { - "type": { - "proxy": [ - "org.apache.ibatis.executor.statement.StatementHandler" - ] - } - }, - { - "type": { - "proxy": [ - "org.apache.ibatis.session.SqlSession" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.boot.context.properties.ConfigurationProperties" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.ControllerAdvice" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.ExceptionHandler" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.PathVariable" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.RequestHeader" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.RequestMapping" - ] - } - }, - { - "type": { - "proxy": [ - "org.springframework.web.bind.annotation.RequestParam" - ] - } - }, - { - "type": { - "lambda": { - "declaringClass": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", - "interfaces": [ - "org.springframework.beans.factory.config.BeanFactoryPostProcessor" - ] - } - } - }, - { - "type": { - "lambda": { - "declaringClass": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", - "interfaces": [ - "org.springframework.beans.factory.config.BeanFactoryPostProcessor" - ] - } - } - }, - { - "type": { - "lambda": { - "declaringClass": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", - "interfaces": [ - "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" - ] - } - } - }, - { - "type": { - "lambda": { - "declaringClass": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", - "interfaces": [ - "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter" - ] - } - } - } - ], - "resources": [ - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.xml" - }, - { - "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.xml" - }, - { - "glob": "META-INF/build-info.properties" - }, - { - "glob": "META-INF/maven/org.xerial/sqlite-jdbc/pom.properties" - }, - { - "glob": "META-INF/services/ch.qos.logback.classic.spi.Configurator" - }, - { - "glob": "META-INF/services/com.baomidou.mybatisplus.core.spi.CompatibleSet" - }, - { - "glob": "META-INF/services/jakarta.el.ExpressionFactory" - }, - { - "glob": "META-INF/services/jakarta.validation.ConstraintValidator" - }, - { - "glob": "META-INF/services/jakarta.validation.spi.ValidationProvider" - }, - { - "glob": "META-INF/services/jakarta.validation.valueextraction.ValueExtractor" - }, - { - "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" - }, - { - "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider" - }, - { - "glob": "META-INF/services/java.nio.channels.spi.SelectorProvider" - }, - { - "glob": "META-INF/services/java.sql.Driver" - }, - { - "glob": "META-INF/services/java.time.zone.ZoneRulesProvider" - }, - { - "glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider" - }, - { - "glob": "META-INF/services/javax.xml.parsers.DocumentBuilderFactory" - }, - { - "glob": "META-INF/services/javax.xml.xpath.XPathFactory" - }, - { - "glob": "META-INF/services/org.apache.commons.logging.LogFactory" - }, - { - "glob": "META-INF/services/org.apache.juli.logging.Log" - }, - { - "glob": "META-INF/services/org.apache.logging.log4j.util.PropertySource" - }, - { - "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" - }, - { - "glob": "META-INF/services/tools.jackson.databind.JacksonModule" - }, - { - "glob": "META-INF/spring-autoconfigure-metadata.properties" - }, - { - "glob": "META-INF/spring.components" - }, - { - "glob": "META-INF/spring.factories" - }, - { - "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports" - }, - { - "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements" - }, - { - "glob": "META-INF/validation.xml" - }, - { - "glob": "application-prod.properties" - }, - { - "glob": "application-prod.xml" - }, - { - "glob": "application-prod.yaml" - }, - { - "glob": "application-prod.yml" - }, - { - "glob": "application.properties" - }, - { - "glob": "application.xml" - }, - { - "glob": "application.yaml" - }, - { - "glob": "application.yml" - }, - { - "glob": "banner.txt" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/DdlAutoConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration$AutoConfiguredMapperScannerRegistrar.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration$MapperScannerRegistrarNotFoundConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusInnerInterceptorAutoConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$FreeMarkerConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$LegacyFreeMarkerConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$LegacyVelocityConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$ThymeleafConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$VelocityConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration.class" - }, - { - "glob": "com/baomidou/mybatisplus/extension/repository/AbstractRepository.class" - }, - { - "glob": "com/baomidou/mybatisplus/extension/repository/CrudRepository.class" - }, - { - "glob": "com/baomidou/mybatisplus/extension/repository/IRepository.class" - }, - { - "glob": "com/baomidou/mybatisplus/extension/service/IService.class" - }, - { - "glob": "com/baomidou/mybatisplus/extension/service/impl/ServiceImpl.class" - }, - { - "glob": "commons-logging.properties" - }, - { - "glob": "config/application-prod.properties" - }, - { - "glob": "config/application-prod.xml" - }, - { - "glob": "config/application-prod.yaml" - }, - { - "glob": "config/application-prod.yml" - }, - { - "glob": "config/application.properties" - }, - { - "glob": "config/application.xml" - }, - { - "glob": "config/application.yaml" - }, - { - "glob": "config/application.yml" - }, - { - "glob": "data-all.sql" - }, - { - "glob": "data.sql" - }, - { - "glob": "dev/qingzhou/pushserver" - }, - { - "glob": "dev/qingzhou/pushserver/aspect/SecurityInterceptor.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/JsonDtoPackageHints$DtoHints.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/JsonDtoPackageHints.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBaitsRuntimeHintsRegistrar.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisBeanFactoryInitializationAotProcessor.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisMapperFactoryBeanPostProcessor.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisMapperTypeUtils.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PortalDatabaseConfig.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PortalJacksonConfig.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PortalMybatisConfig.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PortalSchemaInitializer.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PortalSecurityConfig.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/PushConfiguration.class" - }, - { - "glob": "dev/qingzhou/pushserver/config/WebConfig.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/CaptchaController$CaptchaResponse.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/CaptchaController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/DashboardController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PageController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalAppController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalAuthController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalCorpController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalErrorController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalInitController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalMeController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalMessageController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalProxyController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PortalSystemController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/PushController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.class" - }, - { - "glob": "dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.class" - }, - { - "glob": "dev/qingzhou/pushserver/exception/GlobalExceptionHandler.class" - }, - { - "glob": "dev/qingzhou/pushserver/exception/PortalExceptionHandler.class" - }, - { - "glob": "dev/qingzhou/pushserver/manager/wecom/WecomApiClient.class" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.xml" - }, - { - "glob": "dev/qingzhou/pushserver/security/CaptchaService.class" - }, - { - "glob": "dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter$WindowCounter.class" - }, - { - "glob": "dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.class" - }, - { - "glob": "dev/qingzhou/pushserver/security/PortalUserDetailsService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/DashboardService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalAccessTokenService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalAppApiKeyService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalCorpConfigService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalMessageLogService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalMessageService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalProxyConfigService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalUserService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PortalWecomAppService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/PushService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/SystemConfigService.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl$CachedToken.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/PushServiceImpl.class" - }, - { - "glob": "dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.class" - }, - { - "glob": "git.properties" - }, - { - "glob": "jakarta/servlet/LocalStrings.properties" - }, - { - "glob": "jakarta/servlet/LocalStrings_zh.properties" - }, - { - "glob": "jakarta/servlet/LocalStrings_zh_CN.properties" - }, - { - "glob": "jakarta/servlet/LocalStrings_zh_Hans.properties" - }, - { - "glob": "jakarta/servlet/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "jakarta/servlet/http/LocalStrings.properties" - }, - { - "glob": "jakarta/servlet/http/LocalStrings_zh.properties" - }, - { - "glob": "jakarta/servlet/http/LocalStrings_zh_CN.properties" - }, - { - "glob": "jakarta/servlet/http/LocalStrings_zh_Hans.properties" - }, - { - "glob": "jakarta/servlet/http/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "jndi.properties" - }, - { - "glob": "log4j2.StatusLogger.properties" - }, - { - "glob": "log4j2.component.properties" - }, - { - "glob": "log4j2.system.properties" - }, - { - "glob": "logback-spring.groovy" - }, - { - "glob": "logback-spring.xml" - }, - { - "glob": "logback-test-spring.groovy" - }, - { - "glob": "logback-test-spring.xml" - }, - { - "glob": "logback-test.groovy" - }, - { - "glob": "logback-test.xml" - }, - { - "glob": "logback.groovy" - }, - { - "glob": "logback.xml" - }, - { - "glob": "mapper" - }, - { - "glob": "messages.properties" - }, - { - "glob": "org/apache/catalina/authenticator/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/authenticator/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/authenticator/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/connector/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/connector/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/connector/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/core/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/core/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/core/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/core/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/core/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/core/RestrictedFilters.properties" - }, - { - "glob": "org/apache/catalina/core/RestrictedListeners.properties" - }, - { - "glob": "org/apache/catalina/core/RestrictedServlets.properties" - }, - { - "glob": "org/apache/catalina/deploy/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/deploy/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/deploy/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/loader/JdbcLeakPrevention.class" - }, - { - "glob": "org/apache/catalina/loader/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/loader/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/loader/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/mapper/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/mapper/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/mapper/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/mbeans/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/mbeans/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/mbeans/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/realm/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/realm/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/realm/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/session/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/session/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/session/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/session/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/session/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/startup/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/startup/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/startup/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/util/CharsetMapperDefault.properties" - }, - { - "glob": "org/apache/catalina/util/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/util/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/util/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/util/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/util/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/util/ServerInfo.properties" - }, - { - "glob": "org/apache/catalina/valves/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/valves/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/valves/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/catalina/webresources/LocalStrings.properties" - }, - { - "glob": "org/apache/catalina/webresources/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/catalina/webresources/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/coyote/LocalStrings.properties" - }, - { - "glob": "org/apache/coyote/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/coyote/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/coyote/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/coyote/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/coyote/http11/LocalStrings.properties" - }, - { - "glob": "org/apache/coyote/http11/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/coyote/http11/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/coyote/http11/filters/LocalStrings.properties" - }, - { - "glob": "org/apache/coyote/http11/filters/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/naming/LocalStrings.properties" - }, - { - "glob": "org/apache/naming/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/naming/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/naming/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/naming/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/jni/AprStatus.class" - }, - { - "glob": "org/apache/tomcat/util/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/buf/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/buf/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/compat/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/compat/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/http/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/http/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/http/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/http/parser/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/modeler/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/net/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/net/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/net/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/scan/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/scan/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/threads/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/util/threads/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/websocket/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/websocket/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/websocket/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/apache/tomcat/websocket/server/LocalStrings.properties" - }, - { - "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh.properties" - }, - { - "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties" - }, - { - "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans.properties" - }, - { - "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans_CN.properties" - }, - { - "glob": "org/mybatis/spring/annotation/MapperScan.class" - }, - { - "glob": "org/mybatis/spring/annotation/MapperScannerRegistrar.class" - }, - { - "glob": "org/springframework/aot/hint/annotation/Reflective.class" - }, - { - "glob": "org/springframework/beans/factory/Aware.class" - }, - { - "glob": "org/springframework/beans/factory/BeanFactoryAware.class" - }, - { - "glob": "org/springframework/beans/factory/InitializingBean.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/AutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/AutoConfigureAfter.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/AutoConfigureBefore.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/AutoConfigureOrder.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$ClassProxyingConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBean.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBooleanProperty.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnClass.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingClass.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnNotWarDeployment.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnResource.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplication.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration$GitResourceAvailableCondition.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$BootstrapExecutorConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ExecutorBeanCondition.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ModelCondition.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$TaskExecutorConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$TaskSchedulerConfiguration.class" - }, - { - "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration.class" - }, - { - "glob": "org/springframework/boot/context/properties/EnableConfigurationProperties.class" - }, - { - "glob": "org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.class" - }, - { - "glob": "org/springframework/boot/gson/autoconfigure$GsonAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/gson/autoconfigure/GsonAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition$ReactiveWebApplication.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2XmlMessageConvertersCustomizer.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2XmlHttpMessageConverterConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition$Jackson2Preferred.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition$JacksonUnavailable.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConverterConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConvertersCustomizer.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.class" - }, - { - "glob": "org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$AbstractMapperBuilderCustomizer.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$CborConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration$StandardJsonMapperBuilderCustomizer.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonMixinConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$XmlConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson2/autoconfigure$Jackson2AutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jackson2/autoconfigure/Jackson2AutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseCondition.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceAvailableCondition.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$ExplicitType.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$PooledDataSourceAvailable.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceCheckpointRestoreConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Dbcp2.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Generic.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Hikari.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$OracleUcp.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Tomcat.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$CommonsDbcp2PoolDataSourceMetadataProviderConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$OracleUcpPoolDataSourceMetadataProviderConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$TomcatDataSourcePoolMetadataProviderConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcClientAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/JndiDataSourceAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jdbc/autoconfigure/NamedParameterJdbcTemplateConfiguration.class" - }, - { - "glob": "org/springframework/boot/jsonb/autoconfigure$JsonbAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/jsonb/autoconfigure/JsonbAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure$KotlinxSerializationJsonAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$CompositeMeterRegistryAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$MetricsAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/CompositeMeterRegistryAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple$SimpleMetricsExportAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple/SimpleMetricsExportAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/observation/autoconfigure$ObservationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/persistence/autoconfigure/PersistenceExceptionTranslationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/rsocket/autoconfigure$RSocketMessagingAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$MissingAlternative.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$NameConfigured.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$PasswordConfigured.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$RSocketSecurityEnabledCondition.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$ReactiveWebApplicationCondition.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration$SecurityDataConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/UserDetailsServiceAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ConditionalOnDefaultWebSecurity.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Beans.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Classes.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$EnableWebSecurityConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$SecurityFilterChainConfiguration.class" - }, - { - "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/servlet/autoconfigure/HttpEncodingAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/servlet/autoconfigure/MultipartAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/sql/autoconfigure/init/ConditionalOnSqlInitialization.class" - }, - { - "glob": "org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.class" - }, - { - "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration$TomcatWebSocketConfiguration.class" - }, - { - "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration.class" - }, - { - "glob": "org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$AspectJTransactionManagementConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$JdkDynamicAutoProxyConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$TransactionTemplateConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/transaction/autoconfigure/TransactionManagerCustomizationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/validation/autoconfigure/PrimaryDefaultValidatorPostProcessor.class" - }, - { - "glob": "org/springframework/boot/validation/autoconfigure/ValidationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration$BeanPostProcessorsRegistrar.class" - }, - { - "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainCustomizerConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainResourceHandlerRegistrationCustomizer.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceHandlerRegistrationCustomizer.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WelcomePageHandlerMappingFactory.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration$MeterFilterConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorPageCustomizer.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$StaticView.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.class" - }, - { - "glob": "org/springframework/boot/webmvc/error/ErrorController.class" - }, - { - "glob": "org/springframework/context/ApplicationContextAware.class" - }, - { - "glob": "org/springframework/context/EnvironmentAware.class" - }, - { - "glob": "org/springframework/context/ResourceLoaderAware.class" - }, - { - "glob": "org/springframework/context/annotation/AdviceModeImportSelector.class" - }, - { - "glob": "org/springframework/context/annotation/AutoProxyRegistrar.class" - }, - { - "glob": "org/springframework/context/annotation/Conditional.class" - }, - { - "glob": "org/springframework/context/annotation/Configuration.class" - }, - { - "glob": "org/springframework/context/annotation/Import.class" - }, - { - "glob": "org/springframework/context/annotation/ImportAware.class" - }, - { - "glob": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class" - }, - { - "glob": "org/springframework/context/annotation/ImportRuntimeHints.class" - }, - { - "glob": "org/springframework/context/annotation/Lazy.class" - }, - { - "glob": "org/springframework/context/annotation/Role.class" - }, - { - "glob": "org/springframework/core/annotation/Order.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$AuthenticationManagerDelegator.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$LazyPasswordEncoder.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.class" - }, - { - "glob": "org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.class" - }, - { - "glob": "org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$LazyPasswordEncoder.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/ObservationConfiguration.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$AnnotationAwareOrderComparator.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$CompositeFilterChainProxy.class" - }, - { - "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class" - }, - { - "glob": "org/springframework/security/core/userdetails/UserDetailsService.class" - }, - { - "glob": "org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.class" - }, - { - "glob": "org/springframework/transaction/annotation/EnableTransactionManagement.class" - }, - { - "glob": "org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class" - }, - { - "glob": "org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.class" - }, - { - "glob": "org/springframework/web/bind/annotation/ControllerAdvice.class" - }, - { - "glob": "org/springframework/web/bind/annotation/Mapping.class" - }, - { - "glob": "org/springframework/web/bind/annotation/RequestMapping.class" - }, - { - "glob": "org/springframework/web/bind/annotation/ResponseBody.class" - }, - { - "glob": "org/springframework/web/bind/annotation/RestController.class" - }, - { - "glob": "org/springframework/web/bind/annotation/RestControllerAdvice.class" - }, - { - "glob": "org/springframework/web/context/ServletContextAware.class" - }, - { - "glob": "org/springframework/web/servlet/HandlerInterceptor.class" - }, - { - "glob": "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class" - }, - { - "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport$NoOpValidator.class" - }, - { - "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.class" - }, - { - "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurer.class" - }, - { - "glob": "org/sqlite/native/Windows/x86_64/sqlitejdbc.dll" - }, - { - "glob": "schema-all.sql" - }, - { - "glob": "schema.sql" - }, - { - "glob": "spring.properties" - }, - { - "glob": "sqlite-jdbc.properties" - }, - { - "glob": "static/.well-known/appspecific/com.chrome.devtools.json" - }, - { - "glob": "static/assets/DashboardView-BKGHLnEP.js" - }, - { - "glob": "static/assets/DashboardView-DHdOv7sv.css" - }, - { - "glob": "static/assets/ProxyView-BNsKsUAn.css" - }, - { - "glob": "static/assets/ProxyView-CdQhhSdP.js" - }, - { - "glob": "static/assets/index-C9wU1arO.js" - }, - { - "glob": "static/assets/index-sMwk8kPl.css" - }, - { - "glob": "static/favicon.png" - }, - { - "glob": "static/index.html" - }, - { - "glob": "static/logo.png" - }, - { - "glob": "static/system/content.css.map" - }, - { - "glob": "static/system/sidebar.css.map" - }, - { - "module": "java.base", - "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" - }, - { - "module": "java.base", - "glob": "jdk/internal/icu/impl/data/icudt76b/uprops.icu" - }, - { - "module": "java.base", - "glob": "sun/net/idn/uidna.spp" - }, - { - "bundle": "jakarta.servlet.LocalStrings" - }, - { - "bundle": "jakarta.servlet.http.LocalStrings" - }, - { - "bundle": "org.apache.catalina.authenticator.LocalStrings" - }, - { - "bundle": "org.apache.catalina.authenticator.jaspic.LocalStrings" - }, - { - "bundle": "org.apache.catalina.connector.LocalStrings" - }, - { - "bundle": "org.apache.catalina.core.LocalStrings" - }, - { - "bundle": "org.apache.catalina.deploy.LocalStrings" - }, - { - "bundle": "org.apache.catalina.loader.LocalStrings" - }, - { - "bundle": "org.apache.catalina.mapper.LocalStrings" - }, - { - "bundle": "org.apache.catalina.mbeans.LocalStrings" - }, - { - "bundle": "org.apache.catalina.realm.LocalStrings" - }, - { - "bundle": "org.apache.catalina.session.LocalStrings" - }, - { - "bundle": "org.apache.catalina.startup.LocalStrings" - }, - { - "bundle": "org.apache.catalina.util.LocalStrings" - }, - { - "bundle": "org.apache.catalina.valves.LocalStrings" - }, - { - "bundle": "org.apache.catalina.webresources.LocalStrings" - }, - { - "bundle": "org.apache.coyote.LocalStrings" - }, - { - "bundle": "org.apache.coyote.http11.LocalStrings" - }, - { - "bundle": "org.apache.coyote.http11.filters.LocalStrings" - }, - { - "bundle": "org.apache.naming.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.buf.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.compat.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.descriptor.web.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.http.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.http.parser.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.modeler.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.net.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.scan.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.util.threads.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.websocket.LocalStrings" - }, - { - "bundle": "org.apache.tomcat.websocket.server.LocalStrings" - } - ] -} \ No newline at end of file diff --git a/src/main/resources/META-INF/native-image/reflect-config.json b/src/main/resources/META-INF/native-image/reflect-config.json deleted file mode 100644 index 94583e7..0000000 --- a/src/main/resources/META-INF/native-image/reflect-config.json +++ /dev/null @@ -1,38 +0,0 @@ -[ - { - "name": "com.baomidou.mybatisplus.core.override.MybatisMapperProxy", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, - { - "name": "org.apache.ibatis.binding.MapperProxy", - "allDeclaredFields": true, - "allDeclaredMethods": true, - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.SSW", - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.PSW", - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.PS", - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.SS", - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.SSL", - "allDeclaredConstructors": true - }, - { - "name": "com.github.benmanes.caffeine.cache.PSL", - "allDeclaredConstructors": true - } -] \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml deleted file mode 100644 index 8e97390..0000000 --- a/src/main/resources/application-dev.yml +++ /dev/null @@ -1,16 +0,0 @@ -push: - auth: - key: 替换为自己的key - security: - block-minutes: 30 - fail-window-minutes: 5 - max-fails: 5 - rate-limit-capacity: 10 - rate-limit-qps: 1 - wecom: - app-key: - app-secret: - agent-id: - webhook-url: -server: - port: 8000 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml deleted file mode 100644 index 8e97390..0000000 --- a/src/main/resources/application-prod.yml +++ /dev/null @@ -1,16 +0,0 @@ -push: - auth: - key: 替换为自己的key - security: - block-minutes: 30 - fail-window-minutes: 5 - max-fails: 5 - rate-limit-capacity: 10 - rate-limit-qps: 1 - wecom: - app-key: - app-secret: - agent-id: - webhook-url: -server: - port: 8000 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 5f77ccd..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,24 +0,0 @@ -spring: - application: - name: push-server - profiles: - active: ${SPRING_PROFILES_ACTIVE:@spring.profiles.active@} - config: - additional-location: "optional:file:./config/" - main: - allow-bean-definition-overriding: true - web: - resources: - static-locations: "file:./dist/, classpath:/static/" # 优先找本地 dist,找不到找包内 static - -info: - app: - version: @project.version@ - -server: - port: 8000 -# yDEHjQUcqGgD -logging: - level: - org.springframework.security: DEBUG - org.springframework.web: DEBUG diff --git a/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java b/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java deleted file mode 100644 index b761230..0000000 --- a/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.qingzhou.pushserver; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class PushServerApplicationTests { - - @Test - void contextLoads() { - } - -} From 82a7f3fde320a004ac8f8bf8635468c577c5b4d0 Mon Sep 17 00:00:00 2001 From: ma Date: Mon, 2 Feb 2026 18:33:23 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=BB=93=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E5=BD=93=E5=88=9D=E6=B2=A1=E6=83=B3=E8=83=BD=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E5=88=B0=E7=8E=B0=E5=9C=A8=E8=BF=99=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gemini/settings.json | 5 + push-server-api/pom.xml | 19 + push-server-core/pom.xml | 158 + .../pushserver/PushServerApplication.java | 22 + .../aspect/SecurityInterceptor.java | 88 + .../pushserver/common/PortalResponse.java | 44 + .../pushserver/common/PortalSessionKeys.java | 9 + .../common/PortalSessionSupport.java | 41 + .../config/JsonDtoPackageHints.java | 61 + .../config/MyBatisNativeConfiguration.java | 336 + .../config/PortalAdminInitializer.java | 47 + .../config/PortalDataSourceProperties.java | 35 + .../config/PortalDatabaseConfig.java | 48 + .../config/PortalJacksonConfig.java | 14 + .../config/PortalMybatisConfig.java | 9 + .../config/PortalSchemaInitializer.java | 139 + .../config/PortalSecurityConfig.java | 154 + .../config/PortalWecomProperties.java | 17 + .../pushserver/config/PushConfiguration.java | 13 + .../pushserver/config/PushProperties.java | 135 + .../qingzhou/pushserver/config/WebConfig.java | 30 + .../controller/CaptchaController.java | 28 + .../controller/DashboardController.java | 47 + .../pushserver/controller/PageController.java | 19 + .../controller/PortalAppController.java | 165 + .../controller/PortalAuthController.java | 56 + .../controller/PortalCorpController.java | 49 + .../controller/PortalErrorController.java | 38 + .../controller/PortalInitController.java | 51 + .../controller/PortalMeController.java | 57 + .../controller/PortalMessageController.java | 77 + .../controller/PortalProxyController.java | 46 + .../controller/PortalSystemController.java | 41 + .../pushserver/controller/PushController.java | 55 + .../openapi/OpenApiMessageController.java | 69 + .../wecom/WecomCallbackController.java | 104 + .../exception/GlobalExceptionHandler.java | 31 + .../pushserver/exception/PortalException.java | 20 + .../exception/PortalExceptionHandler.java | 43 + .../pushserver/exception/PortalStatus.java | 22 + .../manager/wecom/AesException.java | 54 + .../manager/wecom/WXBizMsgCrypt.java | 448 + .../manager/wecom/WecomAgentInfo.java | 37 + .../manager/wecom/WecomApiClient.java | 172 + .../manager/wecom/WecomMessageParser.java | 68 + .../manager/wecom/WecomMessagePayload.java | 93 + .../manager/wecom/WecomResponse.java | 32 + .../manager/wecom/WecomSendResponse.java | 39 + .../pushserver/manager/wecom/WecomToken.java | 28 + .../mapper/portal/PortalAppApiKeyMapper.java | 9 + .../mapper/portal/PortalCorpConfigMapper.java | 9 + .../mapper/portal/PortalMessageLogMapper.java | 9 + .../portal/PortalProxyConfigMapper.java | 9 + .../portal/PortalSystemConfigMapper.java | 9 + .../mapper/portal/PortalUserMapper.java | 9 + .../mapper/portal/PortalWecomAppMapper.java | 9 + .../openapi/OpenApiMessageSendRequest.java | 142 + .../model/dto/openapi/PushRequest.java | 109 + .../portal/PortalAppApiKeyUpdateRequest.java | 17 + .../dto/portal/PortalAppCreateRequest.java | 28 + .../dto/portal/PortalAppUpdateRequest.java | 10 + .../dto/portal/PortalCorpConfigRequest.java | 17 + .../model/dto/portal/PortalInitRequest.java | 17 + .../model/dto/portal/PortalLoginRequest.java | 39 + .../dto/portal/PortalMessageSendRequest.java | 151 + .../model/dto/portal/PortalMessageType.java | 18 + .../portal/PortalPasswordUpdateRequest.java | 28 + .../dto/portal/PortalProxyConfigRequest.java | 29 + .../dto/portal/PortalRegisterRequest.java | 28 + .../model/entity/portal/PortalAppApiKey.java | 33 + .../model/entity/portal/PortalCorpConfig.java | 27 + .../model/entity/portal/PortalMessageLog.java | 58 + .../entity/portal/PortalProxyConfig.java | 39 + .../entity/portal/PortalSystemConfig.java | 24 + .../model/entity/portal/PortalUser.java | 26 + .../model/entity/portal/PortalWecomApp.java | 41 + .../vo/portal/DashboardChartsResponse.java | 83 + .../model/vo/portal/DashboardLogResponse.java | 50 + .../vo/portal/DashboardStatsResponse.java | 41 + .../vo/portal/PortalAppApiKeyResponse.java | 59 + .../model/vo/portal/PortalAppResponse.java | 68 + .../model/vo/portal/PortalCorpResponse.java | 14 + .../vo/portal/PortalMessageLogConverter.java | 28 + .../vo/portal/PortalMessageLogResponse.java | 131 + .../model/vo/portal/PortalPageResponse.java | 57 + .../vo/portal/PortalProxyConfigResponse.java | 36 + .../model/vo/portal/PortalUserResponse.java | 41 + .../pushserver/security/CaptchaService.java | 47 + .../security/PortalAppApiKeyRateLimiter.java | 56 + .../PortalJsonLoginAuthenticationFilter.java | 71 + .../security/PortalUserDetails.java | 61 + .../security/PortalUserDetailsService.java | 31 + .../pushserver/service/DashboardService.java | 15 + .../service/PortalAccessTokenService.java | 13 + .../service/PortalAppApiKeyService.java | 21 + .../service/PortalCorpConfigService.java | 13 + .../service/PortalMessageLogService.java | 13 + .../service/PortalMessageService.java | 9 + .../service/PortalProxyConfigService.java | 14 + .../pushserver/service/PortalUserService.java | 15 + .../service/PortalWecomAppService.java | 20 + .../pushserver/service/PushService.java | 9 + .../service/SystemConfigService.java | 14 + .../service/impl/DashboardServiceImpl.java | 243 + .../impl/PortalAccessTokenServiceImpl.java | 64 + .../impl/PortalAppApiKeyServiceImpl.java | 130 + .../impl/PortalCorpConfigServiceImpl.java | 53 + .../impl/PortalMessageLogServiceImpl.java | 61 + .../impl/PortalMessageServiceImpl.java | 238 + .../impl/PortalProxyConfigServiceImpl.java | 69 + .../service/impl/PortalUserServiceImpl.java | 85 + .../impl/PortalWecomAppServiceImpl.java | 143 + .../service/impl/PushServiceImpl.java | 105 + .../service/impl/SystemConfigServiceImpl.java | 77 + .../pushserver/utils/CasTokenBucket.java | 55 + .../native-image/reachability-metadata.json | 10892 ++++++++++++++++ .../META-INF/native-image/reflect-config.json | 38 + .../src/main/resources/application-dev.yml | 16 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application.yml | 24 + .../PushServerApplicationTests.java | 13 + .../service/DashboardServiceTest.java | 43 + 122 files changed, 17752 insertions(+) create mode 100644 .gemini/settings.json create mode 100644 push-server-api/pom.xml create mode 100644 push-server-core/pom.xml create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PageController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PushController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PushService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java create mode 100644 push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json create mode 100644 push-server-core/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 push-server-core/src/main/resources/application-dev.yml create mode 100644 push-server-core/src/main/resources/application-prod.yml create mode 100644 push-server-core/src/main/resources/application.yml create mode 100644 push-server-core/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java create mode 100644 push-server-core/src/test/java/dev/qingzhou/pushserver/service/DashboardServiceTest.java diff --git a/.gemini/settings.json b/.gemini/settings.json new file mode 100644 index 0000000..b9cfd51 --- /dev/null +++ b/.gemini/settings.json @@ -0,0 +1,5 @@ +{ + "general": { + "previewFeatures": true + } +} \ No newline at end of file diff --git a/push-server-api/pom.xml b/push-server-api/pom.xml new file mode 100644 index 0000000..29731cf --- /dev/null +++ b/push-server-api/pom.xml @@ -0,0 +1,19 @@ + + + + push-server + dev.qingzhou + 0.1.3 + + 4.0.0 + + push-server-api + + + 25 + 25 + + + diff --git a/push-server-core/pom.xml b/push-server-core/pom.xml new file mode 100644 index 0000000..5a9d8c8 --- /dev/null +++ b/push-server-core/pom.xml @@ -0,0 +1,158 @@ + + + + push-server + dev.qingzhou + 0.1.3 + + 4.0.0 + + push-server-core + + + + dev.qingzhou + push-server-api + ${project.version} + + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-jdbc + + + com.baomidou + mybatis-plus-spring-boot4-starter + + + org.xerial + sqlite-jdbc + + + org.springframework.security + spring-security-crypto + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation-test + test + + + org.springframework.boot + spring-boot-starter-webmvc-test + test + + + + com.github.ben-manes.caffeine + caffeine + + + + dev.qingzhou + push-core + + + org.projectlombok + lombok + provided + + + + + + + src/main/resources + true + + application.yml + application.properties + **/*.json + **/*.xml + + + + + src/main/resources + false + + static/** + templates/** + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-configuration-processor + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.graalvm.buildtools + native-maven-plugin + + dev.qingzhou.pushserver.PushServerApplication + false + + -Djava.specification.version=25 + -march=compatibility + --initialize-at-build-time=org.sqlite.util.ProcessRunner + --initialize-at-build-time=org.sqlite.util.OSInfo + --initialize-at-run-time=org.apache.ibatis + + --initialize-at-run-time=org.mybatis + --initialize-at-run-time=org.apache.ibatis.logging + --initialize-at-run-time=org.apache.ibatis.logging.LogFactory + + + + + org.apache.maven.plugins + maven-resources-plugin + + + @ + + false + + + + + diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java new file mode 100644 index 0000000..e7997de --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/PushServerApplication.java @@ -0,0 +1,22 @@ +package dev.qingzhou.pushserver; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import java.io.File; + +@SpringBootApplication +public class PushServerApplication { + + public static void main(String[] args) { + // 允许 HTTP 代理进行 Basic 认证(解决 HTTPS 隧道建立时的 407 错误) + System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); + System.setProperty("jdk.http.auth.proxying.disabledSchemes", ""); + + if (System.getProperty("java.home") == null) { + System.setProperty("java.home", new File(".").getAbsolutePath()); + } + SpringApplication.run(PushServerApplication.class, args); + } + +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java new file mode 100644 index 0000000..c20f3fd --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/aspect/SecurityInterceptor.java @@ -0,0 +1,88 @@ +package dev.qingzhou.pushserver.aspect; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import dev.qingzhou.pushserver.config.PushProperties; +import dev.qingzhou.pushserver.utils.CasTokenBucket; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.concurrent.TimeUnit; + +@Component +public class SecurityInterceptor implements HandlerInterceptor { + + private static final Logger log = LoggerFactory.getLogger(SecurityInterceptor.class); + + private final PushProperties properties; + private final int maxFails; + + // 1. 封禁名单缓存:Key=IP, Value=封禁截止时间戳 + private final Cache blockList; + + // 2. 失败计数缓存:Key=IP, Value=失败次数 + private final Cache failCounts; + + private final CasTokenBucket casTokenBucket; + + public SecurityInterceptor(PushProperties properties) { + this.properties = properties; + PushProperties.Security security = properties.getSecurity(); + this.blockList = Caffeine.newBuilder() + .expireAfterWrite(security.getBlockMinutes(), TimeUnit.MINUTES) + .build(); + this.failCounts = Caffeine.newBuilder() + .expireAfterWrite(security.getFailWindowMinutes(), TimeUnit.MINUTES) + .build(); + this.maxFails = security.getMaxFails(); + this.casTokenBucket = new CasTokenBucket(security.getRateLimitCapacity(), security.getRateLimitQps()); + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String clientIp = getClientIp(request); + + // --- 1. 检查是否在小黑屋,或者是否大量无意义请求,都需要限制 --- + if (!casTokenBucket.tryAcquire() || blockList.getIfPresent(clientIp) != null) { + log.warn("Blocked request from IP: {}", clientIp); + response.setStatus(429); // Too Many Requests + response.getWriter().write("Too Many Requests."); + return false; + } + + // --- 2. 执行鉴权逻辑 --- + String apiKey = request.getHeader("X-API-Key"); + if (!isAuthorized(apiKey)) { + // 鉴权失败 -> 计数器 +1 + Integer count = failCounts.get(clientIp, k -> 0); + failCounts.put(clientIp, count + 1); + log.warn("Auth failed for IP: {}, count: {}", clientIp, count + 1); + if (count + 1 >= maxFails) { + blockList.put(clientIp, System.currentTimeMillis()); + log.error("IP {} has been blocked due to brute force attempts.", clientIp); + } + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("Unauthorized"); + return false; + } + return true; + } + + private boolean isAuthorized(String apiKey) { + return StringUtils.hasText(apiKey) && apiKey.equals(properties.getAuth().getKey()); + } + + // 获取真实IP(如果前面有Nginx,需要取 X-Forwarded-For) + private String getClientIp(HttpServletRequest request) { + String xfHeader = request.getHeader("X-Forwarded-For"); + if (xfHeader == null) { + return request.getRemoteAddr(); + } + return xfHeader.split(",")[0].trim(); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java new file mode 100644 index 0000000..e88cd07 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalResponse.java @@ -0,0 +1,44 @@ +package dev.qingzhou.pushserver.common; + +public class PortalResponse { + + private boolean success; + private String message; + private T data; + + public static PortalResponse ok(T data) { + PortalResponse response = new PortalResponse<>(); + response.success = true; + response.message = "成功"; + response.data = data; + return response; + } + + public static PortalResponse ok(String message, T data) { + PortalResponse response = new PortalResponse<>(); + response.success = true; + response.message = message; + response.data = data; + return response; + } + + public static PortalResponse fail(String message) { + PortalResponse response = new PortalResponse<>(); + response.success = false; + response.message = message; + response.data = null; + return response; + } + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } + + public T getData() { + return data; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java new file mode 100644 index 0000000..422ca21 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionKeys.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.common; + +public final class PortalSessionKeys { + + public static final String USER_ID = "portal_user_id"; + + private PortalSessionKeys() { + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java new file mode 100644 index 0000000..d2908ee --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/common/PortalSessionSupport.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.common; + +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.security.PortalUserDetails; +import jakarta.servlet.http.HttpSession; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +public final class PortalSessionSupport { + + private PortalSessionSupport() { + } + + public static Long requireUserId(HttpSession session) { + if (session != null) { + Object value = session.getAttribute(PortalSessionKeys.USER_ID); + if (value instanceof Long userId) { + return userId; + } + } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.isAuthenticated()) { + Object principal = authentication.getPrincipal(); + if (principal instanceof PortalUserDetails userDetails) { + Long userId = userDetails.getUserId(); + if (session != null) { + session.setAttribute(PortalSessionKeys.USER_ID, userId); + } + return userId; + } + if (principal instanceof Long userId) { + if (session != null) { + session.setAttribute(PortalSessionKeys.USER_ID, userId); + } + return userId; + } + } + throw new PortalException(PortalStatus.UNAUTHORIZED, "未授权"); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java new file mode 100644 index 0000000..75bfc42 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/JsonDtoPackageHints.java @@ -0,0 +1,61 @@ +package dev.qingzhou.pushserver.config; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.util.ClassUtils; + +@Configuration(proxyBeanMethods = false) +@ImportRuntimeHints(JsonDtoPackageHints.DtoHints.class) +public class JsonDtoPackageHints { + + static class DtoHints implements RuntimeHintsRegistrar { + + // 你要批量注册的 DTO 包(可加多个) + private static final String[] DTO_PACKAGES = { + "dev.qingzhou.pushserver.model.dto", + "dev.qingzhou.pushserver.model.dto.portal" + }; + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // 不使用默认过滤器(默认只扫 @Component 那些) + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + + // 放开:所有“独立类”(顶级类/静态内部类)都进来 + TypeFilter includeAllIndependent = (metadataReader, metadataReaderFactory) -> + metadataReader.getClassMetadata().isIndependent(); + + scanner.addIncludeFilter(includeAllIndependent); + + for (String basePackage : DTO_PACKAGES) { + for (var bd : scanner.findCandidateComponents(basePackage)) { + String className = bd.getBeanClassName(); + if (className == null) continue; + + // 过滤一些你不想注册的(可按需调整) + if (className.endsWith("package-info")) continue; + + try { + Class clazz = ClassUtils.forName(className, classLoader); + + // Jackson 绑定常用:构造器/方法/字段 + hints.reflection().registerType( + clazz, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS + ); + } catch (Throwable ignored) { + // 某些类可能因为缺依赖加载失败:这里忽略,避免打包中断 + // 你也可以改成打印日志帮助定位 + } + } + } + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java new file mode 100644 index 0000000..0ca0a98 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.java @@ -0,0 +1,336 @@ +package dev.qingzhou.pushserver.config; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import com.baomidou.mybatisplus.core.MybatisParameterHandler; +import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver; +import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.handlers.CompositeEnumTypeHandler; +import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler; +import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; +import com.baomidou.mybatisplus.extension.handlers.GsonTypeHandler; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; +import org.apache.ibatis.cache.decorators.FifoCache; +import org.apache.ibatis.cache.decorators.LruCache; +import org.apache.ibatis.cache.decorators.SoftCache; +import org.apache.ibatis.cache.decorators.WeakCache; +import org.apache.ibatis.cache.impl.PerpetualCache; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.executor.statement.BaseStatementHandler; +import org.apache.ibatis.executor.statement.RoutingStatementHandler; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.javassist.util.proxy.ProxyFactory; +import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; +import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; +import org.apache.ibatis.logging.log4j2.Log4j2Impl; +import org.apache.ibatis.logging.nologging.NoLoggingImpl; +import org.apache.ibatis.logging.slf4j.Slf4jImpl; +import org.apache.ibatis.logging.stdout.StdOutImpl; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.reflection.TypeParameterResolver; +import org.apache.ibatis.scripting.defaults.RawLanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.mybatis.spring.mapper.MapperScannerConfigurer; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportRuntimeHints; +import org.springframework.core.ResolvableType; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * This configuration will move to mybatis-spring-native. + */ +@Configuration(proxyBeanMethods = false) +@ImportRuntimeHints(MyBatisNativeConfiguration.MyBaitsRuntimeHintsRegistrar.class) +public class MyBatisNativeConfiguration { + + @Bean + MyBatisBeanFactoryInitializationAotProcessor myBatisBeanFactoryInitializationAotProcessor() { + return new MyBatisBeanFactoryInitializationAotProcessor(); + } + + @Bean + static MyBatisMapperFactoryBeanPostProcessor myBatisMapperFactoryBeanPostProcessor() { + return new MyBatisMapperFactoryBeanPostProcessor(); + } + + static class MyBaitsRuntimeHintsRegistrar implements RuntimeHintsRegistrar { + + // 你要批量注册的 DTO 包(可加多个) + private static final String[] DTO_PACKAGES = { + "dev.qingzhou.pushserver.model.dto", + "dev.qingzhou.pushserver.model.dto.portal" + }; + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + Stream.of(RawLanguageDriver.class, + // TODO 增加了MybatisXMLLanguageDriver.class + XMLLanguageDriver.class, MybatisXMLLanguageDriver.class, + RuntimeSupport.class, + ProxyFactory.class, + Slf4jImpl.class, + Log.class, + JakartaCommonsLoggingImpl.class, + Log4j2Impl.class, + Jdk14LoggingImpl.class, + StdOutImpl.class, + NoLoggingImpl.class, + SqlSessionFactory.class, + PerpetualCache.class, + FifoCache.class, + LruCache.class, + SoftCache.class, + WeakCache.class, + //TODO 增加了MybatisSqlSessionFactoryBean.class + SqlSessionFactoryBean.class, MybatisSqlSessionFactoryBean.class, + ArrayList.class, + HashMap.class, + TreeSet.class, + HashSet.class + ).forEach(x -> hints.reflection().registerType(x, MemberCategory.values())); + Stream.of( + "org/apache/ibatis/builder/xml/*.dtd", + "org/apache/ibatis/builder/xml/*.xsd" + ).forEach(hints.resources()::registerPattern); + + hints.serialization().registerType(java.lang.invoke.SerializedLambda.class); + hints.reflection().registerType(java.lang.invoke.SerializedLambda.class); + + hints.proxies().registerJdkProxy(StatementHandler.class); + hints.proxies().registerJdkProxy(Executor.class); + hints.proxies().registerJdkProxy(ResultSetHandler.class); + hints.proxies().registerJdkProxy(ParameterHandler.class); + +// hints.reflection().registerType(MybatisPlusInterceptor.class); + hints.reflection().registerType(AbstractWrapper.class,MemberCategory.values()); + hints.reflection().registerType(UpdateWrapper.class,MemberCategory.values()); + hints.reflection().registerType(QueryWrapper.class,MemberCategory.values()); + + hints.reflection().registerType(BoundSql.class,MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(RoutingStatementHandler.class,MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(BaseStatementHandler.class,MemberCategory.DECLARED_FIELDS); + hints.reflection().registerType(MybatisParameterHandler.class,MemberCategory.DECLARED_FIELDS); + + + hints.reflection().registerType(IEnum.class,MemberCategory.INVOKE_PUBLIC_METHODS); + // register typeHandler + hints.reflection().registerType(CompositeEnumTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + hints.reflection().registerType(FastjsonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + hints.reflection().registerType(GsonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + hints.reflection().registerType(JacksonTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + hints.reflection().registerType(MybatisEnumTypeHandler.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + + } + } + + static class MyBatisBeanFactoryInitializationAotProcessor + implements BeanFactoryInitializationAotProcessor, BeanRegistrationExcludeFilter { + + private final Set> excludeClasses = new HashSet<>(); + + MyBatisBeanFactoryInitializationAotProcessor() { + excludeClasses.add(MapperScannerConfigurer.class); + } + + @Override public boolean isExcludedFromAotProcessing(RegisteredBean registeredBean) { + return excludeClasses.contains(registeredBean.getBeanClass()); + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + String[] beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class); + if (beanNames.length == 0) { + return null; + } + return (context, code) -> { + RuntimeHints hints = context.getRuntimeHints(); + for (String beanName : beanNames) { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1)); + PropertyValue mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface"); + if (mapperInterface != null && mapperInterface.getValue() != null) { + Class mapperInterfaceType = (Class) mapperInterface.getValue(); + if (mapperInterfaceType != null) { + registerReflectionTypeIfNecessary(mapperInterfaceType, hints); + hints.proxies().registerJdkProxy(mapperInterfaceType); + hints.resources() + .registerPattern(mapperInterfaceType.getName().replace('.', '/').concat(".xml")); + registerMapperRelationships(mapperInterfaceType, hints); + } + } + } + }; + } + + private void registerMapperRelationships(Class mapperInterfaceType, RuntimeHints hints) { + Method[] methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType); + for (Method method : methods) { + if (method.getDeclaringClass() != Object.class) { + ReflectionUtils.makeAccessible(method); + registerSqlProviderTypes(method, hints, SelectProvider.class, SelectProvider::value, SelectProvider::type); + registerSqlProviderTypes(method, hints, InsertProvider.class, InsertProvider::value, InsertProvider::type); + registerSqlProviderTypes(method, hints, UpdateProvider.class, UpdateProvider::value, UpdateProvider::type); + registerSqlProviderTypes(method, hints, DeleteProvider.class, DeleteProvider::value, DeleteProvider::type); + Class returnType = MyBatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method); + registerReflectionTypeIfNecessary(returnType, hints); + MyBatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method) + .forEach(x -> registerReflectionTypeIfNecessary(x, hints)); + } + } + } + + @SafeVarargs + private void registerSqlProviderTypes( + Method method, RuntimeHints hints, Class annotationType, Function>... providerTypeResolvers) { + for (T annotation : method.getAnnotationsByType(annotationType)) { + for (Function> providerTypeResolver : providerTypeResolvers) { + registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), hints); + } + } + } + + private void registerReflectionTypeIfNecessary(Class type, RuntimeHints hints) { + if (!type.isPrimitive() && !type.getName().startsWith("java")) { + hints.reflection().registerType(type, MemberCategory.values()); + } + } + + } + + static class MyBatisMapperTypeUtils { + private MyBatisMapperTypeUtils() { + // NOP + } + + static Class resolveReturnClass(Class mapperInterface, Method method) { + Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); + return typeToClass(resolvedReturnType, method.getReturnType()); + } + + static Set> resolveParameterClasses(Class mapperInterface, Method method) { + return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface)) + .map(x -> typeToClass(x, x instanceof Class ? (Class) x : Object.class)).collect(Collectors.toSet()); + } + + private static Class typeToClass(Type src, Class fallback) { + Class result = null; + if (src instanceof Class) { + if (((Class) src).isArray()) { + result = ((Class) src).getComponentType(); + } else { + result = (Class) src; + } + } else if (src instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) src; + int index = (parameterizedType.getRawType() instanceof Class + && Map.class.isAssignableFrom((Class) parameterizedType.getRawType()) + && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0; + Type actualType = parameterizedType.getActualTypeArguments()[index]; + result = typeToClass(actualType, fallback); + } + if (result == null) { + result = fallback; + } + return result; + } + + } + + static class MyBatisMapperFactoryBeanPostProcessor implements MergedBeanDefinitionPostProcessor, BeanFactoryAware { + + private static final org.apache.commons.logging.Log LOG = LogFactory.getLog( + MyBatisMapperFactoryBeanPostProcessor.class); + + private static final String MAPPER_FACTORY_BEAN = "org.mybatis.spring.mapper.MapperFactoryBean"; + + private ConfigurableBeanFactory beanFactory; + + @Override + public void setBeanFactory(BeanFactory beanFactory) { + this.beanFactory = (ConfigurableBeanFactory) beanFactory; + } + + @Override + public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class beanType, String beanName) { + if (ClassUtils.isPresent(MAPPER_FACTORY_BEAN, this.beanFactory.getBeanClassLoader())) { + resolveMapperFactoryBeanTypeIfNecessary(beanDefinition); + } + } + + private void resolveMapperFactoryBeanTypeIfNecessary(RootBeanDefinition beanDefinition) { + if (!beanDefinition.hasBeanClass() || !MapperFactoryBean.class.isAssignableFrom(beanDefinition.getBeanClass())) { + return; + } + if (beanDefinition.getResolvableType().hasUnresolvableGenerics()) { + Class mapperInterface = getMapperInterface(beanDefinition); + if (mapperInterface != null) { + // Exposes a generic type information to context for prevent early initializing + ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); + constructorArgumentValues.addGenericArgumentValue(mapperInterface); + beanDefinition.setConstructorArgumentValues(constructorArgumentValues); + beanDefinition.setTargetType(ResolvableType.forClassWithGenerics(beanDefinition.getBeanClass(), mapperInterface)); + } + } + } + + private Class getMapperInterface(RootBeanDefinition beanDefinition) { + try { + return (Class) beanDefinition.getPropertyValues().get("mapperInterface"); + } + catch (Exception e) { + LOG.debug("Fail getting mapper interface type.", e); + return null; + } + } + + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java new file mode 100644 index 0000000..a90e96c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalAdminInitializer.java @@ -0,0 +1,47 @@ +package dev.qingzhou.pushserver.config; + +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import dev.qingzhou.pushserver.service.PortalUserService; +import java.security.SecureRandom; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +//@Component +public class PortalAdminInitializer implements ApplicationRunner { + + private static final Logger log = LoggerFactory.getLogger(PortalAdminInitializer.class); + + private static final String ADMIN_ACCOUNT = "admin"; + private static final String PASSWORD_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; + private static final int PASSWORD_LENGTH = 12; + + private final PortalUserService portalUserService; + private final SecureRandom random = new SecureRandom(); + + public PortalAdminInitializer(PortalUserService portalUserService) { + this.portalUserService = portalUserService; + } + + @Override + public void run(ApplicationArguments args) { + PortalUser existing = portalUserService.findByAccount(ADMIN_ACCOUNT); + if (existing != null) { + return; + } + String password = generatePassword(); + portalUserService.register(ADMIN_ACCOUNT, password); + log.info("Initialized admin user: account={}, password={}", ADMIN_ACCOUNT, password); + } + + private String generatePassword() { + StringBuilder builder = new StringBuilder(PASSWORD_LENGTH); + for (int i = 0; i < PASSWORD_LENGTH; i++) { + int index = random.nextInt(PASSWORD_CHARS.length()); + builder.append(PASSWORD_CHARS.charAt(index)); + } + return builder.toString(); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java new file mode 100644 index 0000000..1d84136 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDataSourceProperties.java @@ -0,0 +1,35 @@ +package dev.qingzhou.pushserver.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "push.portal.datasource") +public class PortalDataSourceProperties { + + private String url; + private String filePath = "./data/push-server-portal.db"; + private Integer maxPoolSize = 5; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public Integer getMaxPoolSize() { + return maxPoolSize; + } + + public void setMaxPoolSize(Integer maxPoolSize) { + this.maxPoolSize = maxPoolSize; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java new file mode 100644 index 0000000..74560f4 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java @@ -0,0 +1,48 @@ +package dev.qingzhou.pushserver.config; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.zaxxer.hikari.HikariDataSource; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.util.StringUtils; + +@Configuration +@EnableTransactionManagement +public class PortalDatabaseConfig { + + @Bean + public DataSource dataSource(PortalDataSourceProperties properties) { + String jdbcUrl = properties.getUrl(); + if (!StringUtils.hasText(jdbcUrl)) { + jdbcUrl = buildSqliteUrl(properties.getFilePath()); + } + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setJdbcUrl(jdbcUrl); + dataSource.setDriverClassName("org.sqlite.JDBC"); + dataSource.setMaximumPoolSize(properties.getMaxPoolSize()); + return dataSource; + } + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + return new MybatisPlusInterceptor(); + } + + private String buildSqliteUrl(String filePath) { + Path path = Paths.get(filePath).toAbsolutePath(); + Path parent = path.getParent(); + if (parent != null) { + try { + Files.createDirectories(parent); + } catch (Exception ex) { + throw new IllegalStateException("Failed to create sqlite directory: " + parent, ex); + } + } + return "jdbc:sqlite:" + path; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java new file mode 100644 index 0000000..4fa6a95 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalJacksonConfig.java @@ -0,0 +1,14 @@ +package dev.qingzhou.pushserver.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class PortalJacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java new file mode 100644 index 0000000..2c96afa --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalMybatisConfig.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan(basePackages = "dev.qingzhou.pushserver.mapper.portal", sqlSessionFactoryRef = "sqlSessionFactory") +public class PortalMybatisConfig { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java new file mode 100644 index 0000000..4f9f586 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java @@ -0,0 +1,139 @@ +package dev.qingzhou.pushserver.config; + +import jakarta.annotation.PostConstruct; +import java.sql.Connection; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import javax.sql.DataSource; +import org.springframework.stereotype.Component; + +@Component +public class PortalSchemaInitializer { + + private final DataSource dataSource; + + public PortalSchemaInitializer(DataSource dataSource) { + this.dataSource = dataSource; + } + + @PostConstruct + public void initialize() { + List statements = new ArrayList<>(); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_system_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + config_key TEXT NOT NULL UNIQUE, + config_value TEXT, + updated_at INTEGER NOT NULL + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account TEXT NOT NULL UNIQUE, + password_hash TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_corp_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL UNIQUE, + corp_id TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_wecom_app ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + agent_id TEXT NOT NULL, + secret TEXT NOT NULL, + token TEXT, + encoding_aes_key TEXT, + name TEXT, + avatar_url TEXT, + description TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + UNIQUE(user_id, agent_id) + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_app_api_key ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + app_id INTEGER NOT NULL UNIQUE, + api_key_hash TEXT NOT NULL, + api_key_plain TEXT NOT NULL, + rate_limit_per_minute INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_proxy_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL UNIQUE, + host TEXT NOT NULL, + port INTEGER NOT NULL, + username TEXT, + password TEXT, + type TEXT NOT NULL DEFAULT 'HTTP', + exit_ip TEXT, + active INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + """); + statements.add(""" + CREATE UNIQUE INDEX IF NOT EXISTS idx_v2_app_api_key_hash + ON v2_app_api_key(api_key_hash) + """); + List alterStatements = new ArrayList<>(); + alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN api_key_plain TEXT NOT NULL DEFAULT ''"); + alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN rate_limit_per_minute INTEGER NOT NULL DEFAULT 0"); + alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN token TEXT"); + alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN encoding_aes_key TEXT"); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_message_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + app_id INTEGER NOT NULL, + agent_id TEXT NOT NULL, + msg_type TEXT NOT NULL, + to_user TEXT, + to_party TEXT, + to_all INTEGER NOT NULL DEFAULT 0, + title TEXT, + description TEXT, + url TEXT, + content TEXT, + request_json TEXT, + response_json TEXT, + success INTEGER NOT NULL, + error_message TEXT, + created_at INTEGER NOT NULL + ) + """); + + try (Connection connection = dataSource.getConnection()) { + try (Statement statement = connection.createStatement()) { + for (String sql : statements) { + statement.execute(sql); + } + for (String sql : alterStatements) { + try { + statement.execute(sql); + } catch (Exception ignored) { + // Column may already exist; ignore migration errors to stay backward compatible. + } + } + } + } catch (Exception ex) { + throw new IllegalStateException("Failed to initialize portal schema", ex); + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java new file mode 100644 index 0000000..33b8a7c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSecurityConfig.java @@ -0,0 +1,154 @@ +package dev.qingzhou.pushserver.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.qingzhou.pushserver.common.PortalSessionKeys; +import dev.qingzhou.pushserver.security.CaptchaService; +import dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter; +import dev.qingzhou.pushserver.security.PortalUserDetails; +import dev.qingzhou.pushserver.security.PortalUserDetailsService; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; + +import java.io.PrintWriter; +import java.util.Map; + +@Configuration +public class PortalSecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager( + PortalUserDetailsService userDetailsService, + PasswordEncoder passwordEncoder + ) { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); + provider.setPasswordEncoder(passwordEncoder); + return new ProviderManager(provider); + } + + + @Bean + public SessionAuthenticationStrategy sessionAuthenticationStrategy() { + return new ChangeSessionIdAuthenticationStrategy(); + } + + @Bean + public PortalJsonLoginAuthenticationFilter portalJsonLoginAuthenticationFilter( + ObjectMapper objectMapper, + CaptchaService captchaService, // 确保 CaptchaService 加了 @Service 注解 + AuthenticationManager authenticationManager, + SessionAuthenticationStrategy sessionAuthenticationStrategy + ) { + // 创建 Filter + PortalJsonLoginAuthenticationFilter filter = + new PortalJsonLoginAuthenticationFilter(objectMapper, captchaService); + + // 注入必要的组件 + filter.setAuthenticationManager(authenticationManager); + filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); + + SecurityContextRepository contextRepository = new HttpSessionSecurityContextRepository(); + filter.setSecurityContextRepository(contextRepository); + + // 设置 JSON 成功处理器 (解决 302 问题) + filter.setAuthenticationSuccessHandler((request, response, authentication) -> { + HttpSession session = request.getSession(true); + Object principal = authentication.getPrincipal(); + if (principal instanceof PortalUserDetails userDetails) { + session.setAttribute(PortalSessionKeys.USER_ID, userDetails.getUserId()); + } + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter out = response.getWriter(); + out.write(objectMapper.writeValueAsString(Map.of( + "code", 200, + "msg", "登录成功", + "username", authentication.getName() + ))); + }); + + filter.setAuthenticationFailureHandler((request, response, exception) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + PrintWriter out = response.getWriter(); + out.write(objectMapper.writeValueAsString(Map.of( + "code", 401, + "msg", "登录失败: " + exception.getMessage() + ))); + }); + + return filter; + } + + @Bean + public SecurityFilterChain securityFilterChain( + HttpSecurity http, + PortalJsonLoginAuthenticationFilter portalJsonLoginAuthenticationFilter + ) throws Exception { // 注意这里要抛出异常 + http.authorizeHttpRequests(authorize -> authorize + // 静态资源和登录接口放行 + .requestMatchers("/","/api/login", "/login", "/index.html", "/assets/**", "/logo.png","/favicon.ico", "/api/captcha").permitAll() + .requestMatchers("/api/public/**").permitAll() + .requestMatchers("/api/system/version").permitAll() + .requestMatchers("/error").permitAll() + .requestMatchers("/api/v2/openapi/**").permitAll() + .requestMatchers("/api/v2/auth/register", "/api/v2/auth/csrf").permitAll() + .requestMatchers("/api/v2/wecom/**").permitAll() + .requestMatchers("/api/v1/**").permitAll() + .anyRequest().authenticated() + ) + .csrf(csrf -> csrf + .spa() + .ignoringRequestMatchers("/api/v1/**", "/api/login", "/api/captcha", "/api/v2/openapi/**", "/api/v2/wecom/**") + ) + .sessionManagement(session -> session + .sessionCreationPolicy(org.springframework.security.config.http.SessionCreationPolicy.IF_REQUIRED) + ) + + .exceptionHandling(exceptionHandling -> + exceptionHandling.authenticationEntryPoint((request, response, authException) -> { + String requestURI = request.getRequestURI(); + // 判断:如果是 API 请求,或者是 AJAX 请求 + if (requestURI.startsWith("/api/") || "XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { + // 方案 A:对接口返回 401 JSON + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 + response.getWriter().write("{\"code\": 401, \"msg\": \"未登录或会话已过期\"}"); + } else { + // 方案 B:对普通页面访问,重定向到登录页 + // 注意:这里不要跳首页 /,应该跳 /login,否则可能死循环 + response.sendRedirect("/login"); + } + }) + ) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessHandler((request, response, authentication) -> { + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\": 200, \"msg\": \"退出成功\"}"); + }) + ) + // 你的自定义 Filter + .addFilterAt(portalJsonLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java new file mode 100644 index 0000000..8830ee8 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalWecomProperties.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "push.portal.wecom") +public class PortalWecomProperties { + + private String baseUrl = "https://qyapi.weixin.qq.com"; + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java new file mode 100644 index 0000000..50caf6d --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushConfiguration.java @@ -0,0 +1,13 @@ +package dev.qingzhou.pushserver.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({ + PushProperties.class, + PortalWecomProperties.class, + PortalDataSourceProperties.class +}) +public class PushConfiguration { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java new file mode 100644 index 0000000..9449dd4 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PushProperties.java @@ -0,0 +1,135 @@ +package dev.qingzhou.pushserver.config; + +import jakarta.validation.constraints.NotBlank; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Validated +@ConfigurationProperties(prefix = "push") +public class PushProperties { + + private final Auth auth = new Auth(); + private final Wecom wecom = new Wecom(); + private final Security security = new Security(); + + public Auth getAuth() { + return auth; + } + + public Wecom getWecom() { + return wecom; + } + + public Security getSecurity() { + return security; + } + + public static class Auth { + @NotBlank + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + } + + public static class Wecom { + @NotBlank + private String appKey; + @NotBlank + private String appSecret; + @NotBlank + private String agentId; + private String webhookUrl; + + public String getAppKey() { + return appKey; + } + + public void setAppKey(String appKey) { + this.appKey = appKey; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getWebhookUrl() { + return webhookUrl; + } + + public void setWebhookUrl(String webhookUrl) { + this.webhookUrl = webhookUrl; + } + } + + public static class Security { + public static final int DEFAULT_BLOCK_MINUTES = 30; + public static final int DEFAULT_FAIL_WINDOW_MINUTES = 5; + public static final int DEFAULT_MAX_FAILS = 5; + public static final long DEFAULT_RATE_LIMIT_CAPACITY = 10; + public static final long DEFAULT_RATE_LIMIT_QPS = 1; + + private Integer blockMinutes; + private Integer failWindowMinutes; + private Integer maxFails; + private Long rateLimitCapacity; + private Long rateLimitQps; + + public int getBlockMinutes() { + return blockMinutes != null ? blockMinutes : DEFAULT_BLOCK_MINUTES; + } + + public void setBlockMinutes(Integer blockMinutes) { + this.blockMinutes = blockMinutes; + } + + public int getFailWindowMinutes() { + return failWindowMinutes != null ? failWindowMinutes : DEFAULT_FAIL_WINDOW_MINUTES; + } + + public void setFailWindowMinutes(Integer failWindowMinutes) { + this.failWindowMinutes = failWindowMinutes; + } + + public int getMaxFails() { + return maxFails != null ? maxFails : DEFAULT_MAX_FAILS; + } + + public void setMaxFails(Integer maxFails) { + this.maxFails = maxFails; + } + + public long getRateLimitCapacity() { + return rateLimitCapacity != null ? rateLimitCapacity : DEFAULT_RATE_LIMIT_CAPACITY; + } + + public void setRateLimitCapacity(Long rateLimitCapacity) { + this.rateLimitCapacity = rateLimitCapacity; + } + + public long getRateLimitQps() { + return rateLimitQps != null ? rateLimitQps : DEFAULT_RATE_LIMIT_QPS; + } + + public void setRateLimitQps(Long rateLimitQps) { + this.rateLimitQps = rateLimitQps; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java new file mode 100644 index 0000000..86dcbe3 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/WebConfig.java @@ -0,0 +1,30 @@ +package dev.qingzhou.pushserver.config; + +import dev.qingzhou.pushserver.aspect.SecurityInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final SecurityInterceptor securityInterceptor; + + public WebConfig(SecurityInterceptor securityInterceptor) { + this.securityInterceptor = securityInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(securityInterceptor) + .addPathPatterns("/v1/**"); + } + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + // 给所有标有 @RestController 注解的类,统一添加 "/api" 前缀 + configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(RestController.class)); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java new file mode 100644 index 0000000..f7d6cf2 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/CaptchaController.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.service.SystemConfigService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CaptchaController { + + private final SystemConfigService configService; + + public CaptchaController(SystemConfigService configService) { + this.configService = configService; + } + + public record CaptchaResponse(boolean enabled, String siteKey) { + } + + @GetMapping(value = "/captcha", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity captcha() { + return ResponseEntity.ok(new CaptchaResponse( + configService.isTurnstileEnabled(), + configService.getTurnstileSiteKey() + )); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java new file mode 100644 index 0000000..07ab0bb --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/DashboardController.java @@ -0,0 +1,47 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; +import dev.qingzhou.pushserver.service.DashboardService; +import jakarta.servlet.http.HttpSession; +import java.util.List; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/dashboard") +public class DashboardController { + + private final DashboardService dashboardService; + + public DashboardController(DashboardService dashboardService) { + this.dashboardService = dashboardService; + } + + @GetMapping("/stats") + public PortalResponse stats(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + return PortalResponse.ok(dashboardService.fetchStats(userId)); + } + + @GetMapping("/charts") + public PortalResponse charts(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + return PortalResponse.ok(dashboardService.fetchCharts(userId)); + } + + @GetMapping("/recent-logs") + public PortalResponse> recentLogs( + @RequestParam(defaultValue = "10") int limit, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + int safeLimit = Math.max(1, Math.min(limit, 100)); + return PortalResponse.ok(dashboardService.fetchRecentLogs(userId, safeLimit)); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PageController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PageController.java new file mode 100644 index 0000000..77d1bfd --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PageController.java @@ -0,0 +1,19 @@ +package dev.qingzhou.pushserver.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class PageController { + + @RequestMapping(value = { + "/{path:[^\\.]*}", // 匹配一级路径,如 /login, /dashboard + "/**/{path:[^\\.]*}" // 匹配多级路径,如 /system/users, /user/profile/edit + }) + public String redirect() { + // 转发到根目录的 index.html,让 Vue Router 在前端接管路由 + return "forward:/index.html"; + } + +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java new file mode 100644 index 0000000..8647af3 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppController.java @@ -0,0 +1,165 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest; +import dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest; +import dev.qingzhou.pushserver.model.dto.portal.PortalAppUpdateRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.model.vo.portal.PortalAppApiKeyResponse; +import dev.qingzhou.pushserver.model.vo.portal.PortalAppResponse; +import dev.qingzhou.pushserver.service.PortalAppApiKeyService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/apps") +public class PortalAppController { + + private final PortalWecomAppService appService; + private final PortalAppApiKeyService apiKeyService; + + public PortalAppController(PortalWecomAppService appService, PortalAppApiKeyService apiKeyService) { + this.appService = appService; + this.apiKeyService = apiKeyService; + } + + @PostMapping + public PortalResponse create( + @Valid @RequestBody PortalAppCreateRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalWecomApp app = appService.addApp(userId, request.getAgentId(), request.getSecret()); + return PortalResponse.ok(toResponse(app)); + } + + @PutMapping("/{appId}") + public PortalResponse update( + @PathVariable Long appId, + @RequestBody PortalAppUpdateRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalWecomApp app = appService.updateApp( + userId, appId, + request.getSecret(), + request.getToken(), + request.getEncodingAesKey() + ); + return PortalResponse.ok(toResponse(app)); + } + + @GetMapping + public PortalResponse> list(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + List apps = appService.listByUser(userId).stream() + .map(this::toResponse) + .collect(Collectors.toList()); + return PortalResponse.ok(apps); + } + + @DeleteMapping("/{appId}") + public PortalResponse delete(@PathVariable Long appId, HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + appService.deleteApp(userId, appId); + apiKeyService.removeByAppId(appId); + return PortalResponse.ok("已删除", null); + } + + @PostMapping("/{appId}/sync") + public PortalResponse sync(@PathVariable Long appId, HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalWecomApp app = appService.syncApp(userId, appId); + return PortalResponse.ok(toResponse(app)); + } + + @GetMapping("/{appId}/api-key") + public PortalResponse getApiKey( + @PathVariable Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalAppApiKey record = apiKeyService.findByAppId(userId, appId); + return PortalResponse.ok(toApiKeyResponse(appId, record)); + } + + @PostMapping("/{appId}/api-key/reset") + public PortalResponse resetApiKey( + @PathVariable Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalAppApiKey record = apiKeyService.rotateKey(userId, appId); + return PortalResponse.ok(toApiKeyResponse(appId, record)); + } + + @PostMapping("/{appId}/api-key") + public PortalResponse createApiKey( + @PathVariable Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalAppApiKey record = apiKeyService.rotateKey(userId, appId); + return PortalResponse.ok(toApiKeyResponse(appId, record)); + } + + @PutMapping("/{appId}/api-key") + public PortalResponse updateApiKey( + @PathVariable Long appId, + @Valid @RequestBody PortalAppApiKeyUpdateRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalAppApiKey record = apiKeyService.updateRateLimit(userId, appId, request.getRateLimitPerMinute()); + return PortalResponse.ok(toApiKeyResponse(appId, record)); + } + + @DeleteMapping("/{appId}/api-key") + public PortalResponse deleteApiKey( + @PathVariable Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + appService.requireByUser(userId, appId); + apiKeyService.removeByAppId(appId); + return PortalResponse.ok("已删除", null); + } + + private PortalAppResponse toResponse(PortalWecomApp app) { + PortalAppResponse response = new PortalAppResponse(); + response.setId(app.getId()); + response.setAgentId(app.getAgentId()); + response.setName(app.getName()); + response.setAvatarUrl(app.getAvatarUrl()); + response.setDescription(app.getDescription()); + response.setCreatedAt(app.getCreatedAt()); + response.setUpdatedAt(app.getUpdatedAt()); + return response; + } + + private PortalAppApiKeyResponse toApiKeyResponse(Long appId, PortalAppApiKey record) { + PortalAppApiKeyResponse response = new PortalAppApiKeyResponse(); + response.setAppId(appId); + response.setHasKey(record != null); + if (record != null) { + response.setApiKey(record.getApiKeyPlain()); + response.setRateLimitPerMinute(record.getRateLimitPerMinute()); + response.setCreatedAt(record.getCreatedAt()); + response.setUpdatedAt(record.getUpdatedAt()); + } + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java new file mode 100644 index 0000000..c33cd39 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAuthController.java @@ -0,0 +1,56 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.model.dto.portal.PortalRegisterRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse; +import dev.qingzhou.pushserver.service.PortalUserService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/auth") +public class PortalAuthController { + + private final PortalUserService userService; + + public PortalAuthController(PortalUserService userService) { + this.userService = userService; + } + + @PostMapping("/register") + public PortalResponse register( + @Valid @RequestBody PortalRegisterRequest request + ) { + PortalUser user = userService.register(request.getAccount(), request.getPassword()); + return PortalResponse.ok(toResponse(user)); + } + + @GetMapping("/csrf") + public PortalResponse csrf(CsrfToken token) { + return PortalResponse.ok(token.getToken()); + } + + @PostMapping("/logout") + public PortalResponse logout(HttpSession session) { + if (session != null) { + session.invalidate(); + } + return PortalResponse.ok("已退出登录", null); + } + + private PortalUserResponse toResponse(PortalUser user) { + PortalUserResponse response = new PortalUserResponse(); + response.setId(user.getId()); + response.setAccount(user.getAccount()); + response.setCreatedAt(user.getCreatedAt()); + response.setUpdatedAt(user.getUpdatedAt()); + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java new file mode 100644 index 0000000..344e641 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalCorpController.java @@ -0,0 +1,49 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse; +import dev.qingzhou.pushserver.service.PortalCorpConfigService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/corp") +public class PortalCorpController { + + private final PortalCorpConfigService corpConfigService; + + public PortalCorpController(PortalCorpConfigService corpConfigService) { + this.corpConfigService = corpConfigService; + } + + @GetMapping + public PortalResponse getCorp(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalCorpConfig config = corpConfigService.getByUserId(userId); + PortalCorpResponse response = new PortalCorpResponse(); + if (config != null) { + response.setCorpId(config.getCorpId()); + } + return PortalResponse.ok(response); + } + + @PutMapping + public PortalResponse upsert( + @Valid @RequestBody PortalCorpConfigRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalCorpConfig config = corpConfigService.upsert(userId, request.getCorpId()); + PortalCorpResponse response = new PortalCorpResponse(); + response.setCorpId(config.getCorpId()); + return PortalResponse.ok(response); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java new file mode 100644 index 0000000..8b21a93 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalErrorController.java @@ -0,0 +1,38 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.boot.webmvc.error.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class PortalErrorController implements ErrorController { + + @RequestMapping("/error") + public ResponseEntity> handleError(HttpServletRequest request) { + Object status = request.getAttribute("jakarta.servlet.error.status_code"); + String message = "系统发生错误"; + + if (status != null) { + int statusCode = Integer.parseInt(status.toString()); + if (statusCode == HttpStatus.NOT_FOUND.value()) { + message = "接口不存在"; + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(PortalResponse.fail(message)); + } else if (statusCode == HttpStatus.UNAUTHORIZED.value()) { + message = "未授权或会话过期"; + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(PortalResponse.fail(message)); + } else if (statusCode == HttpStatus.FORBIDDEN.value()) { + message = "没有访问权限"; + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(PortalResponse.fail(message)); + } else if (statusCode >= 500) { + message = "系统内部错误"; + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(PortalResponse.fail(message)); + } + } + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(PortalResponse.fail(message)); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java new file mode 100644 index 0000000..018580c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalInitController.java @@ -0,0 +1,51 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.model.dto.portal.PortalInitRequest; +import dev.qingzhou.pushserver.service.PortalUserService; +import dev.qingzhou.pushserver.service.SystemConfigService; +import jakarta.validation.Valid; +import java.util.Map; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/public") +public class PortalInitController { + + private final PortalUserService userService; + private final SystemConfigService configService; + + public PortalInitController(PortalUserService userService, SystemConfigService configService) { + this.userService = userService; + this.configService = configService; + } + + @GetMapping("/init-status") + public ResponseEntity> getInitStatus() { + boolean initialized = userService.count() > 0; + return ResponseEntity.ok(Map.of("initialized", initialized)); + } + + @PostMapping("/init") + public ResponseEntity> initialize(@Valid @RequestBody PortalInitRequest request) { + if (userService.count() > 0) { + return ResponseEntity.badRequest().body(Map.of("msg", "System already initialized")); + } + + // Create Admin User + userService.register(request.getUsername(), request.getPassword()); + + // Save Turnstile Config + configService.setTurnstileConfig( + request.isTurnstileEnabled(), + request.getTurnstileSiteKey(), + request.getTurnstileSecretKey() + ); + + return ResponseEntity.ok(Map.of("msg", "Initialization successful")); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java new file mode 100644 index 0000000..06d19ab --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMeController.java @@ -0,0 +1,57 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.model.dto.portal.PortalPasswordUpdateRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse; +import dev.qingzhou.pushserver.service.PortalUserService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/me") +public class PortalMeController { + + private final PortalUserService userService; + + public PortalMeController(PortalUserService userService) { + this.userService = userService; + } + + @GetMapping + public PortalResponse me(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalUser user = userService.getById(userId); + if (user == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "用户未找到"); + } + return PortalResponse.ok(toResponse(user)); + } + + @PutMapping("/password") + public PortalResponse updatePassword( + @Valid @RequestBody PortalPasswordUpdateRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + userService.updatePassword(userId, request.getOldPassword(), request.getNewPassword()); + return PortalResponse.ok("密码已更新", null); + } + + private PortalUserResponse toResponse(PortalUser user) { + PortalUserResponse response = new PortalUserResponse(); + response.setId(user.getId()); + response.setAccount(user.getAccount()); + response.setCreatedAt(user.getCreatedAt()); + response.setUpdatedAt(user.getUpdatedAt()); + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java new file mode 100644 index 0000000..a4c8102 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalMessageController.java @@ -0,0 +1,77 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogConverter; +import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse; +import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; +import dev.qingzhou.pushserver.service.PortalMessageLogService; +import dev.qingzhou.pushserver.service.PortalMessageService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/messages") +public class PortalMessageController { + + private final PortalMessageService messageService; + private final PortalMessageLogService messageLogService; + + public PortalMessageController(PortalMessageService messageService, PortalMessageLogService messageLogService) { + this.messageService = messageService; + this.messageLogService = messageLogService; + } + + @PostMapping("/send") + public PortalResponse send( + @Valid @RequestBody PortalMessageSendRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalMessageLog log = messageService.send(userId, request); + return PortalResponse.ok(PortalMessageLogConverter.toResponse(log)); + } + + @GetMapping("/logs") + public PortalResponse logs( + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer pageSize, + @RequestParam(defaultValue = "20") int limit, + @RequestParam(required = false) Boolean success, + @RequestParam(required = false) Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + boolean usePagination = page != null || pageSize != null; + if (usePagination) { + int resolvedPage = page == null ? 1 : page; + int resolvedPageSize = pageSize == null ? 20 : pageSize; + PortalPageResponse pagedLogs = messageLogService.pageLogs(userId, appId, success, resolvedPage, resolvedPageSize); + List records = pagedLogs.getRecords().stream() + .map(PortalMessageLogConverter::toResponse) + .collect(Collectors.toList()); + PortalPageResponse response = PortalPageResponse.of( + records, + pagedLogs.getTotal(), + pagedLogs.getPage(), + pagedLogs.getPageSize() + ); + return PortalResponse.ok(response); + } + + List logs = messageLogService.listRecent(userId, limit, appId, success).stream() + .map(PortalMessageLogConverter::toResponse) + .collect(Collectors.toList()); + return PortalResponse.ok(logs); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java new file mode 100644 index 0000000..98452fa --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalProxyController.java @@ -0,0 +1,46 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse; +import dev.qingzhou.pushserver.service.PortalProxyConfigService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/v2/proxy") +public class PortalProxyController { + + private final PortalProxyConfigService proxyConfigService; + + public PortalProxyController(PortalProxyConfigService proxyConfigService) { + this.proxyConfigService = proxyConfigService; + } + + @GetMapping + public PortalResponse getProxy(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalProxyConfig config = proxyConfigService.getByUserId(userId); + return PortalResponse.ok(PortalProxyConfigResponse.from(config)); + } + + @PutMapping + public PortalResponse upsert( + @Valid @RequestBody PortalProxyConfigRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + PortalProxyConfig config = proxyConfigService.upsert(userId, request); + return PortalResponse.ok(PortalProxyConfigResponse.from(config)); + } + + @DeleteMapping + public PortalResponse delete(HttpSession session) { + Long userId = PortalSessionSupport.requireUserId(session); + proxyConfigService.deleteByUserId(userId); + return PortalResponse.ok(null); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java new file mode 100644 index 0000000..07daa58 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.service.SystemConfigService; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; +import java.util.Map; + +@RestController +@RequestMapping("/system") +public class PortalSystemController { + + private final SystemConfigService systemConfigService; + + @Value("${info.app.version:unknown}") + private String appVersion; + + public PortalSystemController(SystemConfigService systemConfigService) { + this.systemConfigService = systemConfigService; + } + + @GetMapping("/version") + public PortalResponse> getVersion() { + return PortalResponse.ok(Map.of("version", appVersion)); + } + + @GetMapping("/version/ignore") + public PortalResponse> getIgnoreVersion() { + String version = systemConfigService.get("ignore_version", ""); + return PortalResponse.ok(Map.of("version", version)); + } + + @PostMapping("/version/ignore") + public PortalResponse setIgnoreVersion(@RequestBody Map body) { + String version = body.get("version"); + if (version != null) { + systemConfigService.set("ignore_version", version); + } + return PortalResponse.ok(null); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PushController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PushController.java new file mode 100644 index 0000000..af97c71 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PushController.java @@ -0,0 +1,55 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.push.core.model.PushResult; +import dev.qingzhou.pushserver.config.PushProperties; +import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; +import dev.qingzhou.pushserver.service.PushService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v1") +public class PushController { + + private final PushService pushService; + private final PushProperties properties; + + public PushController(PushService pushService, PushProperties properties) { + this.pushService = pushService; + this.properties = properties; + } + + @PostMapping("/push") + public ResponseEntity push( + @RequestHeader(value = "X-API-Key", required = false) String apiKey, + @RequestBody PushRequest request + ) { + if (!isAuthorized(apiKey)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(PushResult.fail("未授权")); + } + try { + PushResult result = pushService.push(request); + return ResponseEntity.ok(result); + } catch (IllegalArgumentException ex) { + return ResponseEntity.badRequest() + .body(PushResult.fail(ex.getMessage())); + } catch (IllegalStateException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(PushResult.fail(ex.getMessage())); + } + } + + private boolean isAuthorized(String apiKey) { + if (!StringUtils.hasText(apiKey)) { + return false; + } + return apiKey.equals(properties.getAuth().getKey()); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java new file mode 100644 index 0000000..b52fdb0 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.java @@ -0,0 +1,69 @@ +package dev.qingzhou.pushserver.controller.openapi; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest; +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogConverter; +import dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse; +import dev.qingzhou.pushserver.service.PortalAppApiKeyService; +import dev.qingzhou.pushserver.service.PortalMessageService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/openapi/messages") +public class OpenApiMessageController { + + private final PortalAppApiKeyService apiKeyService; + private final PortalMessageService messageService; + + public OpenApiMessageController( + PortalAppApiKeyService apiKeyService, + PortalMessageService messageService + ) { + this.apiKeyService = apiKeyService; + this.messageService = messageService; + } + + @PostMapping("/send") + public ResponseEntity> send( + @RequestHeader(value = "X-API-Key", required = false) String apiKey, + @RequestBody OpenApiMessageSendRequest request + ) { + try { + PortalAppApiKeyService.AppAuthContext ctx = apiKeyService.requireAppByApiKey(apiKey); + PortalMessageSendRequest portalRequest = toPortalRequest(request, ctx.app().getId()); + PortalMessageLog log = messageService.send(ctx.app().getUserId(), portalRequest); + return ResponseEntity.ok(PortalResponse.ok(PortalMessageLogConverter.toResponse(log))); + } catch (PortalException ex) { + return ResponseEntity.status(ex.getStatus().getHttpStatus()) + .body(PortalResponse.fail(ex.getMessage())); + } catch (Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(PortalResponse.fail("系统内部错误")); + } + } + + private PortalMessageSendRequest toPortalRequest(OpenApiMessageSendRequest request, Long appId) { + PortalMessageSendRequest target = new PortalMessageSendRequest(); + target.setAppId(appId); + target.setToUser(request.getToUser()); + target.setToParty(request.getToParty()); + target.setToAll(request.getToAll()); + target.setMsgType(request.getMsgType()); + target.setContent(request.getContent()); + target.setTitle(request.getTitle()); + target.setDescription(request.getDescription()); + target.setUrl(request.getUrl()); + target.setBtnText(request.getBtnText()); + target.setArticles(request.getArticles()); + return target; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java new file mode 100644 index 0000000..e20ec99 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java @@ -0,0 +1,104 @@ +package dev.qingzhou.pushserver.controller.wecom; + +import dev.qingzhou.pushserver.manager.wecom.AesException; +import dev.qingzhou.pushserver.manager.wecom.WXBizMsgCrypt; +import dev.qingzhou.pushserver.manager.wecom.WecomMessageParser; +import dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.service.PortalCorpConfigService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/v2/wecom/callback/{appId}") +public class WecomCallbackController { + + private final PortalWecomAppService wecomAppService; + private final PortalCorpConfigService corpConfigService; + + public WecomCallbackController(PortalWecomAppService wecomAppService, PortalCorpConfigService corpConfigService) { + this.wecomAppService = wecomAppService; + this.corpConfigService = corpConfigService; + } + + /** + * 企业微信回调 URL 验证 (GET) + */ + @GetMapping + public String verify( + @PathVariable("appId") Long appId, + @RequestParam("msg_signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestParam("echostr") String echostr) { + + log.info("Received WeCom callback verification for appId={}: signature={}, timestamp={}, nonce={}, echostr={}", + appId, signature, timestamp, nonce, echostr); + + try { + WXBizMsgCrypt wxcpt = getWxCrypt(appId); + return wxcpt.VerifyURL(signature, timestamp, nonce, echostr); + } catch (AesException e) { + log.error("WeCom verification failed", e); + return "FAILED: " + e.getMessage(); + } catch (Exception e) { + log.error("System error during verification", e); + return "ERROR"; + } + } + + /** + * 企业微信消息/事件推送 (POST) + */ + @PostMapping + public String handleMessage( + @PathVariable("appId") Long appId, + @RequestParam("msg_signature") String signature, + @RequestParam("timestamp") String timestamp, + @RequestParam("nonce") String nonce, + @RequestBody String body) { + + log.info("Received WeCom message for appId={}: signature={}, timestamp={}, nonce={}", appId, signature, timestamp, nonce); + + try { + WXBizMsgCrypt wxcpt = getWxCrypt(appId); + String decryptedMsg = wxcpt.DecryptMsg(signature, timestamp, nonce, body); + log.info("Decrypted XML: {}", decryptedMsg); + + WecomMessagePayload payload = WecomMessageParser.parse(decryptedMsg); + log.info("Parsed Payload: {}", payload); + + // TODO: 后续可以根据 payload.getMsgType() 或 payload.getEvent() 分发到不同的处理器 + + return "success"; + } catch (AesException e) { + log.error("WeCom message decryption failed", e); + return "FAILED"; // 企业微信要求处理失败不返回 success,会重试 + } catch (Exception e) { + log.error("System error during message handling", e); + return "ERROR"; + } + } + + private WXBizMsgCrypt getWxCrypt(Long appId) throws AesException { + PortalWecomApp app = wecomAppService.getById(appId); + if (app == null) { + throw new AesException(AesException.IllegalAesKey, "App not found"); + } + + // 校验配置是否完整 + if (app.getToken() == null || app.getEncodingAesKey() == null) { + throw new AesException(AesException.IllegalAesKey, "Token or EncodingAESKey not configured for this app"); + } + + PortalCorpConfig corpConfig = corpConfigService.getByUserId(app.getUserId()); + if (corpConfig == null || corpConfig.getCorpId() == null) { + throw new AesException(AesException.ValidateCorpidError, "CorpConfig not found"); + } + + return new WXBizMsgCrypt(app.getToken(), app.getEncodingAesKey(), corpConfig.getCorpId()); + } +} \ No newline at end of file diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..f046a26 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/GlobalExceptionHandler.java @@ -0,0 +1,31 @@ +package dev.qingzhou.pushserver.exception; + +import dev.qingzhou.push.core.model.PushResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Objects; + +@RestControllerAdvice(basePackages = "dev.qingzhou.pushserver.controller.openapi") +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + + @ExceptionHandler(Exception.class) + public PushResult handleException(Exception e) { + // 1. 只有服务端自己看日志 + log.error("Unknown error occurred", e); + // 2. 告诉前端“系统异常”,别告诉他具体哪行代码错了 + return PushResult.fail("系统内部错误: " + e.getClass().getSimpleName()); + } + + // 可以专门捕获参数校验异常,返回具体字段错误 + @ExceptionHandler(MethodArgumentNotValidException.class) + public PushResult handleValidException(org.springframework.web.bind.MethodArgumentNotValidException e) { + return PushResult.fail("参数错误: " + Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java new file mode 100644 index 0000000..39c040f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalException.java @@ -0,0 +1,20 @@ +package dev.qingzhou.pushserver.exception; + +public class PortalException extends RuntimeException { + + private final PortalStatus status; + + public PortalException(PortalStatus status, String message) { + super(message); + this.status = status; + } + + public PortalException(PortalStatus status, String message, Throwable cause) { + super(message, cause); + this.status = status; + } + + public PortalStatus getStatus() { + return status; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java new file mode 100644 index 0000000..de0e853 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalExceptionHandler.java @@ -0,0 +1,43 @@ +package dev.qingzhou.pushserver.exception; + +import dev.qingzhou.pushserver.common.PortalResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice(basePackages = "dev.qingzhou.pushserver.controller") +public class PortalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(PortalExceptionHandler.class); + + @ExceptionHandler(PortalException.class) + public ResponseEntity> handlePortalException(PortalException ex) { + return ResponseEntity.status(ex.getStatus().getHttpStatus()) + .body(PortalResponse.fail(ex.getMessage())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidation(MethodArgumentNotValidException ex) { + String message = "校验失败"; + if (ex.getBindingResult().getFieldError() != null) { + message = ex.getBindingResult().getFieldError().getDefaultMessage(); + } + return ResponseEntity.badRequest().body(PortalResponse.fail(message)); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity> handleNotReadable(HttpMessageNotReadableException ex) { + return ResponseEntity.badRequest().body(PortalResponse.fail("请求参数格式错误")); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleException(Exception ex) { + log.error("Unhandled portal error", ex); + return ResponseEntity.internalServerError() + .body(PortalResponse.fail("服务器内部错误")); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java new file mode 100644 index 0000000..56ad363 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/exception/PortalStatus.java @@ -0,0 +1,22 @@ +package dev.qingzhou.pushserver.exception; + +import org.springframework.http.HttpStatus; + +public enum PortalStatus { + BAD_REQUEST(HttpStatus.BAD_REQUEST), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED), + NOT_FOUND(HttpStatus.NOT_FOUND), + CONFLICT(HttpStatus.CONFLICT), + TOO_MANY_REQUESTS(HttpStatus.TOO_MANY_REQUESTS), + BAD_GATEWAY(HttpStatus.BAD_GATEWAY); + + private final HttpStatus httpStatus; + + PortalStatus(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java new file mode 100644 index 0000000..bf2b572 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/AesException.java @@ -0,0 +1,54 @@ +package dev.qingzhou.pushserver.manager.wecom; + +@SuppressWarnings("serial") +public class AesException extends Exception { + + public final static int OK = 0; + public final static int ValidateSignatureError = -40001; + public final static int ParseXmlError = -40002; + public final static int ComputeSignatureError = -40003; + public final static int IllegalAesKey = -40004; + public final static int ValidateCorpidError = -40005; + public final static int EncryptAESError = -40006; + public final static int DecryptAESError = -40007; + public final static int IllegalBuffer = -40008; + + private int code; + + public AesException(int code) { + super(getMessage(code)); + this.code = code; + } + + public AesException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return code; + } + + private static String getMessage(int code) { + switch (code) { + case ValidateSignatureError: + return "签名验证错误"; + case ParseXmlError: + return "xml解析失败"; + case ComputeSignatureError: + return "sha加密生成签名失败"; + case IllegalAesKey: + return "SymmetricKey非法"; + case ValidateCorpidError: + return "corpid校验失败"; + case EncryptAESError: + return "aes加密失败"; + case DecryptAESError: + return "aes解密失败"; + case IllegalBuffer: + return "解密后得到的buffer非法"; + default: + return null; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java new file mode 100644 index 0000000..d0f7ab8 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WXBizMsgCrypt.java @@ -0,0 +1,448 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Base64; +import java.util.Random; + +/** + * 提供接收和推送给企业微信消息的加解密接口(v1.1).
  1. 第三方回复加密消息给企业微信
  2. 第三方收到企业微信发送的消息,验证签名,解密
+ * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432125.html
  2. + *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. + *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. + *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
+ */ +public class WXBizMsgCrypt { + static Charset CHARSET = StandardCharsets.UTF_8; + Base64.Decoder base64Decoder = Base64.getDecoder(); + Base64.Encoder base64Encoder = Base64.getEncoder(); + byte[] aesKey; + String token; + String receiveId; + + /** + * 构造函数 + * + * @param token 企业微信后台,开发者设置的token + * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey + * @param receiveId 不同场景含义不同,appId或者corpId + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String receiveId) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + this.token = token; + this.receiveId = receiveId; + aesKey = base64Decoder.decode(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + String getRandomStr() { + String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 16; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException aes加密失败 + */ + String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] receiveIdBytes = receiveId.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + receiveId + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + byteCollector.addBytes(receiveIdBytes); + + // ... + pad: 使用自定义的填充方式对明文进行补位填充 + byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); + byteCollector.addBytes(padBytes); + + // 获得最终的字节流, 未加密 + byte[] unencrypted = byteCollector.toBytes(); + + try { + // 设置加密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); + + // 加密 + byte[] encrypted = cipher.doFinal(unencrypted); + + // 使用BASE64对加密后的字符串进行编码 + return base64Encoder.encodeToString(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException aes解密失败 + */ + String decrypt(String text) throws AesException { + byte[] original; + try { + // 设置解密模式为AES的CBC模式 + Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); + SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); + IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); + cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); + + // 使用BASE64对密文进行解码 + byte[] encrypted = base64Decoder.decode(text); + + // 解密 + original = cipher.doFinal(encrypted); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent, from_receiveId; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和receiveId + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + from_receiveId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.IllegalBuffer); + } + + // receiveId不相同的情况 + if (!from_receiveId.equals(receiveId)) { + throw new AesException(AesException.ValidateCorpidError); + } + return xmlContent; + + } + + /** + * 将公众平台回复用户的消息加密打包. + *
    + *
  1. 对要发送的消息进行AES-CBC加密
  2. + *
  3. 生成安全签名
  4. + *
  5. 将消息密文和时间戳、随机数字、安全签名打包成xml格式
  6. + *
+ * + * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串 + * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce + * @return 加密后的可以直接回复用户的xml + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if (timeStamp == "") { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt); + + // 生成发送的xml + String result = XMLParse.generate(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 验证URL + * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param echoStr 随机串,对应URL参数的echostr + * @return 解密之后的echostr + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr) throws AesException { + String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr); + + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + String result = decrypt(echoStr); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
    + *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. + *
  3. 若验证通过,则提取xml中的加密消息
  4. + *
  5. 对消息进行解密
  6. + *
+ * + * @param msgSignature 签名串,对应URL参数的msg_signature + * @param timeStamp 时间戳,对应URL参数的timestamp + * @param nonce 随机串,对应URL参数的nonce + * @param postData 密文,对应POST请求的数据 + * @return 解密后的原文 + * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData) throws AesException { + // 提取密文 + Object[] encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString()); + + // 和URL中的签名比较是否相等 + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 解密 + String result = decrypt(encrypt[1].toString()); + return result; + } + + // ----------------------------------------------------------- + // 内部类: PKCS7Encoder + // ----------------------------------------------------------- + static class PKCS7Encoder { + static Charset CHARSET = StandardCharsets.UTF_8; + static int BLOCK_SIZE = 32; + + /** + * 获得对明文进行补位填充的字节. + * + * @param count 需要进行填充补位操作的明文长度 + * @return 补齐用的字节数组 + */ + static byte[] encode(int count) { + // 计算需要填充的位数 + int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); + if (amountToPad == 0) { + amountToPad = BLOCK_SIZE; + } + // 获得补位所用的字符 + char padChr = chr(amountToPad); + String tmp = new String(); + for (int index = 0; index < amountToPad; index++) { + tmp += padChr; + } + return tmp.getBytes(CHARSET); + } + + /** + * 删除解密后明文的补位字符 + * + * @param decrypted 解密后的明文 + * @return 删除补位字符后的明文 + */ + static byte[] decode(byte[] decrypted) { + int pad = (int) decrypted[decrypted.length - 1]; + if (pad < 1 || pad > 32) { + pad = 0; + } + return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); + } + + /** + * 将数字转化成ASCII码对应的字符,用于对明文进行补码 + * + * @param a 需要转化的数字 + * @return 转化得到的字符 + */ + static char chr(int a) { + byte target = (byte) (a & 0xFF); + return (char) target; + } + } + + // ----------------------------------------------------------- + // 内部类: ByteGroup + // ----------------------------------------------------------- + static class ByteGroup { + java.util.ArrayList byteContainer = new java.util.ArrayList(); + + public byte[] toBytes() { + byte[] bytes = new byte[byteContainer.size()]; + for (int i = 0; i < byteContainer.size(); i++) { + bytes[i] = byteContainer.get(i); + } + return bytes; + } + + public ByteGroup addBytes(byte[] bytes) { + for (byte b : bytes) { + byteContainer.add(b); + } + return this; + } + + public int size() { + return byteContainer.size(); + } + } + + // ----------------------------------------------------------- + // 内部类: SHA1 + // ----------------------------------------------------------- + static class SHA1 { + /** + * 用SHA1算法生成安全签名 + * + * @param token 票据 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @param encrypt 密文 + * @return 安全签名 + * @throws AesException + */ + public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws AesException { + try { + String[] array = new String[]{token, timestamp, nonce, encrypt}; + StringBuffer sb = new StringBuffer(); + // 字符串排序 + Arrays.sort(array); + for (int i = 0; i < 4; i++) { + sb.append(array[i]); + } + String str = sb.toString(); + // SHA1签名生成 + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(str.getBytes()); + byte[] digest = md.digest(); + + StringBuffer hexstr = new StringBuffer(); + String shaHex = ""; + for (int i = 0; i < digest.length; i++) { + shaHex = Integer.toHexString(digest[i] & 0xFF); + if (shaHex.length() < 2) { + hexstr.append(0); + } + hexstr.append(shaHex); + } + return hexstr.toString(); + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ComputeSignatureError); + } + } + } + + // ----------------------------------------------------------- + // 内部类: XMLParse + // ----------------------------------------------------------- + static class XMLParse { + /** + * 提取出xml数据包中的加密消息 + * + * @param xmltext 待提取的xml字符串 + * @return 提取出的加密消息字符串 + * @throws AesException + */ + public static Object[] extract(String xmltext) throws AesException { + Object[] result = new Object[3]; + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + + // XXE 防护 + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xmltext); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + NodeList nodelist1 = root.getElementsByTagName("Encrypt"); + result[0] = 0; + result[1] = nodelist1.item(0).getTextContent(); + //result[2] = root.getElementsByTagName("ToUserName").item(0).getTextContent(); + return result; + } catch (Exception e) { + e.printStackTrace(); + throw new AesException(AesException.ParseXmlError); + } + } + + /** + * 生成xml消息 + * + * @param encrypt 加密后的消息密文 + * @param signature 安全签名 + * @param timestamp 时间戳 + * @param nonce 随机字符串 + * @return 生成的xml字符串 + */ + public static String generate(String encrypt, String signature, String timestamp, String nonce) { + String format = "\n" + "\n" + + "\n" + + "%3$s\n" + "\n" + ""; + return String.format(format, encrypt, signature, timestamp, nonce); + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java new file mode 100644 index 0000000..41acd93 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomAgentInfo.java @@ -0,0 +1,37 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class WecomAgentInfo extends WecomResponse { + + private String name; + + private String description; + + @JsonProperty("square_logo_url") + private String avatarUrl; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java new file mode 100644 index 0000000..109ca75 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomApiClient.java @@ -0,0 +1,172 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import dev.qingzhou.pushserver.config.PortalWecomProperties; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import org.springframework.http.MediaType; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientException; + +import java.io.IOException; +import java.net.*; +import java.net.http.HttpClient; +import java.util.Collections; +import java.util.List; + +@Component +public class WecomApiClient { + + private final PortalWecomProperties properties; + private final RestClient defaultClient; + + public WecomApiClient(PortalWecomProperties properties) { + this.properties = properties; + this.defaultClient = RestClient.builder() + .baseUrl(properties.getBaseUrl()) + .build(); + } + + private RestClient getClient(PortalProxyConfig proxyConfig) { + if (proxyConfig == null || !Boolean.TRUE.equals(proxyConfig.getActive())) { + return defaultClient; + } + + Proxy.Type proxyType = "SOCKS5".equalsIgnoreCase(proxyConfig.getType()) ? Proxy.Type.SOCKS : Proxy.Type.HTTP; + InetSocketAddress proxyAddr = new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()); + + HttpClient.Builder builder = HttpClient.newBuilder() + .proxy(new ProxySelector() { + @Override + public List select(URI uri) { + return Collections.singletonList(new Proxy(proxyType, proxyAddr)); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + // 忽略连接失败回调 + } + }); + + if (StringUtils.hasText(proxyConfig.getUsername())) { + builder.authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + char[] password = proxyConfig.getPassword() != null ? proxyConfig.getPassword().toCharArray() : new char[0]; + return new PasswordAuthentication(proxyConfig.getUsername(), password); + } + }); + } + + return RestClient.builder() + .baseUrl(properties.getBaseUrl()) + .requestFactory(new JdkClientHttpRequestFactory(builder.build())) + .build(); + } + + public WecomToken getToken(String corpId, String secret, PortalProxyConfig proxyConfig) { + try { + WecomToken response = getClient(proxyConfig).get() + .uri(uriBuilder -> uriBuilder + .path("/cgi-bin/gettoken") + .queryParam("corpid", corpId) + .queryParam("corpsecret", secret) + .build()) + .retrieve() + .body(WecomToken.class); + return requireSuccess(response, "gettoken"); + } catch (RestClientException ex) { + throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 gettoken 接口失败", ex); + } + } + + public WecomAgentInfo getAgentInfo(String accessToken, String agentId, PortalProxyConfig proxyConfig) { + try { + WecomAgentInfo response = getClient(proxyConfig).get() + .uri(uriBuilder -> uriBuilder + .path("/cgi-bin/agent/get") + .queryParam("access_token", accessToken) + .queryParam("agentid", agentId) + .build()) + .retrieve() + .body(WecomAgentInfo.class); + return requireSuccess(response, "agent/get"); + } catch (RestClientException ex) { + throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 agent/get 接口失败", ex); + } + } + + public WecomSendResponse sendMessage(String accessToken, Object payload, PortalProxyConfig proxyConfig) { + try { + WecomSendResponse response = getClient(proxyConfig).post() + .uri(uriBuilder -> uriBuilder + .path("/cgi-bin/message/send") + .queryParam("access_token", accessToken) + .build()) + .contentType(MediaType.APPLICATION_JSON) + .body(payload) + .retrieve() + .body(WecomSendResponse.class); + if (response == null) { + throw new PortalException(PortalStatus.BAD_GATEWAY, "企业微信 message/send 响应为空"); + } + return response; + } catch (RestClientException ex) { + throw new PortalException(PortalStatus.BAD_GATEWAY, "调用企业微信 message/send 接口失败", ex); + } + } + + public void testConnectivity(PortalProxyConfig proxyConfig) { + if (proxyConfig == null) { + return; + } + try { + // 尝试访问企业微信根域名,仅测试网络连通性 + getClient(proxyConfig).get() + .uri("/cgi-bin/gettoken") + .retrieve() + .toBodilessEntity(); + } catch (Exception ex) { + String msg = ex.getMessage(); + if (ex.getCause() != null) { + msg += " (" + ex.getCause().getMessage() + ")"; + } + throw new PortalException(PortalStatus.BAD_REQUEST, "代理连接测试失败: " + msg); + } + } + + private T requireSuccess( + T response, + String action + ) { + if (response == null) { + throw new PortalException(PortalStatus.BAD_GATEWAY, "企业微信 " + action + " 响应为空"); + } + if (!response.isSuccess()) { + if (Integer.valueOf(60020).equals(response.getErrcode())) { + String ip = "unknown"; + String errmsg = response.getErrmsg(); + if (errmsg != null && errmsg.contains("from ip: ")) { + int start = errmsg.indexOf("from ip: ") + 9; + int end = errmsg.indexOf(",", start); + if (end == -1) { + end = errmsg.length(); + } + ip = errmsg.substring(start, end).trim(); + } + throw new PortalException( + PortalStatus.BAD_REQUEST, + "企业微信 IP 白名单校验失败。请在企业微信应用设置的“企业可信 IP”列表中添加本服务器 IP [" + ip + "]。" + ); + } + throw new PortalException( + PortalStatus.BAD_REQUEST, + "企业微信 " + action + " 失败: " + response.getErrmsg() + " (" + response.getErrcode() + ")" + ); + } + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java new file mode 100644 index 0000000..6c44faf --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessageParser.java @@ -0,0 +1,68 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +public class WecomMessageParser { + + public static WecomMessagePayload parse(String xml) { + WecomMessagePayload payload = new WecomMessagePayload(); + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + + DocumentBuilder db = dbf.newDocumentBuilder(); + StringReader sr = new StringReader(xml); + InputSource is = new InputSource(sr); + Document document = db.parse(is); + + Element root = document.getDocumentElement(); + + payload.setToUserName(getElementValue(root, "ToUserName")); + payload.setFromUserName(getElementValue(root, "FromUserName")); + payload.setReceiveMsgType(getElementValue(root, "MsgType")); // 使用 receiveMsgType + payload.setContent(getElementValue(root, "Content")); + payload.setReceiveAgentId(getElementValue(root, "AgentID")); // 使用 receiveAgentId + payload.setEvent(getElementValue(root, "Event")); + payload.setEventKey(getElementValue(root, "EventKey")); + + // 图片消息字段 + payload.setPicUrl(getElementValue(root, "PicUrl")); + payload.setMediaId(getElementValue(root, "MediaId")); + + String createTime = getElementValue(root, "CreateTime"); + if (createTime != null && !createTime.isEmpty()) { + payload.setCreateTime(Long.parseLong(createTime)); + } + + String msgId = getElementValue(root, "MsgId"); + if (msgId != null && !msgId.isEmpty()) { + payload.setMsgId(Long.parseLong(msgId)); + } + + } catch (Exception e) { + e.printStackTrace(); + // 解析失败返回空对象或部分对象 + } + return payload; + } + + private static String getElementValue(Element root, String tagName) { + NodeList nodeList = root.getElementsByTagName(tagName); + if (nodeList != null && nodeList.getLength() > 0) { + return nodeList.item(0).getTextContent(); + } + return null; + } +} \ No newline at end of file diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java new file mode 100644 index 0000000..d3efdbe --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomMessagePayload.java @@ -0,0 +1,93 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.util.List; + +@Data +public class WecomMessagePayload { + + // ========================================== + // 发送消息字段 (JSON) + // ========================================== + private String touser; + private String toparty; + private String totag; + private String msgtype; + private Long agentid; + private Integer safe; + + @JsonProperty("enable_id_trans") + private Integer enableIdTrans; + + @JsonProperty("enable_duplicate_check") + private Integer enableDuplicateCheck; + + @JsonProperty("duplicate_check_interval") + private Integer duplicateCheckInterval; + + private Text text; + private Markdown markdown; + private TextCard textcard; + private News news; + + // ========================================== + // 接收回调字段 (XML 解析后赋值) + // ========================================== + private String toUserName; + private String fromUserName; + private Long createTime; + // 注意:接收时的 MsgType 和发送时的 msgtype 可能大小写不同, + // 但通常我们可以复用 msgtype 字段,或者分开。 + // 为了不破坏现有发送逻辑,发送用 msgtype (全小写)。 + // 接收到的 XML MsgType 也是 "text" 等小写 (在 XML 值里),但标签是 PascalCase。 + // 这里我们额外定义字段用于接收,避免混淆 + private String receiveMsgType; + + private String content; // 接收到的文本内容 + private Long msgId; + private String receiveAgentId; // 接收到的 AgentID + private String event; + private String eventKey; + + // 图片消息字段 + private String picUrl; + private String mediaId; + + // ========================================== + // 内部类定义 (用于发送消息) + // ========================================== + + @Data + public static class Text { + private String content; + } + + @Data + public static class Markdown { + private String content; + } + + @Data + public static class TextCard { + private String title; + private String description; + private String url; + @JsonProperty("btntxt") + private String btnText; + } + + @Data + public static class News { + private List
articles; + } + + @Data + public static class Article { + private String title; + private String description; + private String url; + @JsonProperty("picurl") + private String picUrl; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java new file mode 100644 index 0000000..ae481fc --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomResponse.java @@ -0,0 +1,32 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class WecomResponse { + + @JsonProperty("errcode") + private Integer errcode; + + @JsonProperty("errmsg") + private String errmsg; + + public Integer getErrcode() { + return errcode; + } + + public void setErrcode(Integer errcode) { + this.errcode = errcode; + } + + public String getErrmsg() { + return errmsg; + } + + public void setErrmsg(String errmsg) { + this.errmsg = errmsg; + } + + public boolean isSuccess() { + return errcode != null && errcode == 0; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java new file mode 100644 index 0000000..9c9e90f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomSendResponse.java @@ -0,0 +1,39 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class WecomSendResponse extends WecomResponse { + + @JsonProperty("invaliduser") + private String invalidUser; + + @JsonProperty("invalidparty") + private String invalidParty; + + @JsonProperty("invalidtag") + private String invalidTag; + + public String getInvalidUser() { + return invalidUser; + } + + public void setInvalidUser(String invalidUser) { + this.invalidUser = invalidUser; + } + + public String getInvalidParty() { + return invalidParty; + } + + public void setInvalidParty(String invalidParty) { + this.invalidParty = invalidParty; + } + + public String getInvalidTag() { + return invalidTag; + } + + public void setInvalidTag(String invalidTag) { + this.invalidTag = invalidTag; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java new file mode 100644 index 0000000..ae36b3f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/manager/wecom/WecomToken.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.manager.wecom; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class WecomToken extends WecomResponse { + + @JsonProperty("access_token") + private String accessToken; + + @JsonProperty("expires_in") + private Integer expiresIn; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java new file mode 100644 index 0000000..8ebdc65 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalAppApiKeyMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java new file mode 100644 index 0000000..b92d0aa --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalCorpConfigMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java new file mode 100644 index 0000000..ea13641 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalMessageLogMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java new file mode 100644 index 0000000..e8ffd52 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalProxyConfigMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java new file mode 100644 index 0000000..3615920 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalSystemConfigMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java new file mode 100644 index 0000000..7a99fbc --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalUserMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java new file mode 100644 index 0000000..0600567 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalWecomAppMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java new file mode 100644 index 0000000..4c8d260 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/OpenApiMessageSendRequest.java @@ -0,0 +1,142 @@ +package dev.qingzhou.pushserver.model.dto.openapi; + +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageType; +import java.util.List; + +public class OpenApiMessageSendRequest { + + // Compatibility fields for v1 + private String target; + private String type; + + // v2 fields + private String toUser; + private String toParty; + private Boolean toAll; + private PortalMessageType msgType = PortalMessageType.TEXT; + private String content; + private String title; + private String description; + private String url; + private String btnText; + private List articles; + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + if (type != null) { + try { + this.msgType = PortalMessageType.valueOf(type.toUpperCase()); + } catch (IllegalArgumentException e) { + // Ignore invalid type string, fallback to default or existing msgType + } + } + } + + public String getToUser() { + String effectiveUser = (toUser == null && target != null) ? target : toUser; + if ("@all".equalsIgnoreCase(effectiveUser)) { + return null; // Handled by getToAll() + } + return effectiveUser; + } + + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public String getToParty() { + return toParty; + } + + public void setToParty(String toParty) { + this.toParty = toParty; + } + + public boolean isToAll() { + return Boolean.TRUE.equals(getToAll()); + } + + public Boolean getToAll() { + if (Boolean.TRUE.equals(toAll)) { + return true; + } + // Support "@all" in target or toUser + if ("@all".equalsIgnoreCase(target) || "@all".equalsIgnoreCase(toUser)) { + return true; + } + return false; + } + + public void setToAll(Boolean toAll) { + this.toAll = toAll; + } + + public PortalMessageType getMsgType() { + return msgType != null ? msgType : PortalMessageType.TEXT; + } + + public void setMsgType(PortalMessageType msgType) { + this.msgType = msgType; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getBtnText() { + return btnText; + } + + public void setBtnText(String btnText) { + this.btnText = btnText; + } + + public List getArticles() { + return articles; + } + + public void setArticles(List articles) { + this.articles = articles; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java new file mode 100644 index 0000000..00685f2 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/openapi/PushRequest.java @@ -0,0 +1,109 @@ +package dev.qingzhou.pushserver.model.dto.openapi; + +import java.util.List; + +public class PushRequest { + + private String target; + private String type; + private String title; + private String content; + private String url; + private String mediaId; + private List
articles; + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public List
getArticles() { + return articles; + } + + public void setArticles(List
articles) { + this.articles = articles; + } + + public static class Article { + private String title; + private String description; + private String url; + private String picUrl; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPicUrl() { + return picUrl; + } + + public void setPicUrl(String picUrl) { + this.picUrl = picUrl; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java new file mode 100644 index 0000000..524775b --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppApiKeyUpdateRequest.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.Min; + +public class PortalAppApiKeyUpdateRequest { + + @Min(0) + private Integer rateLimitPerMinute; + + public Integer getRateLimitPerMinute() { + return rateLimitPerMinute; + } + + public void setRateLimitPerMinute(Integer rateLimitPerMinute) { + this.rateLimitPerMinute = rateLimitPerMinute; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java new file mode 100644 index 0000000..d45f007 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppCreateRequest.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; + +public class PortalAppCreateRequest { + + @NotBlank(message = "AgentId 不能为空") + private String agentId; + + @NotBlank(message = "Secret 不能为空") + private String secret; + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java new file mode 100644 index 0000000..76ab441 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalAppUpdateRequest.java @@ -0,0 +1,10 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import lombok.Data; + +@Data +public class PortalAppUpdateRequest { + private String secret; + private String token; + private String encodingAesKey; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java new file mode 100644 index 0000000..c5ac88f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalCorpConfigRequest.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; + +public class PortalCorpConfigRequest { + + @NotBlank(message = "CorpId is required") + private String corpId; + + public String getCorpId() { + return corpId; + } + + public void setCorpId(String corpId) { + this.corpId = corpId; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java new file mode 100644 index 0000000..96614ad --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalInitRequest.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PortalInitRequest { + @NotBlank + private String username; + + @NotBlank + private String password; + + private boolean turnstileEnabled; + private String turnstileSiteKey; + private String turnstileSecretKey; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java new file mode 100644 index 0000000..1c98ac9 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalLoginRequest.java @@ -0,0 +1,39 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; + +public class PortalLoginRequest { + + @NotBlank(message = "账号不能为空") + private String account; + + @NotBlank(message = "密码不能为空") + private String password; + + @NotBlank(message = "验证码不能为空") + private String captcha; + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getCaptcha() { + return captcha; + } + + public void setCaptcha(String captcha) { + this.captcha = captcha; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java new file mode 100644 index 0000000..ea5b275 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageSendRequest.java @@ -0,0 +1,151 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public class PortalMessageSendRequest { + + @NotNull(message = "应用 ID 不能为空") + private Long appId; + private String toUser; + private String toParty; + private Boolean toAll; + private PortalMessageType msgType = PortalMessageType.TEXT; + private String content; + private String title; + private String description; + private String url; + private String btnText; + private List articles; + + public Long getAppId() { + return appId; + } + + public void setAppId(Long appId) { + this.appId = appId; + } + + public String getToUser() { + return toUser; + } + + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public String getToParty() { + return toParty; + } + + public void setToParty(String toParty) { + this.toParty = toParty; + } + + public boolean isToAll() { + return Boolean.TRUE.equals(toAll); + } + + public Boolean getToAll() { + return toAll; + } + + public void setToAll(Boolean toAll) { + this.toAll = toAll; + } + + public PortalMessageType getMsgType() { + return msgType != null ? msgType : PortalMessageType.TEXT; + } + + public void setMsgType(PortalMessageType msgType) { + this.msgType = msgType; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getBtnText() { + return btnText; + } + + public void setBtnText(String btnText) { + this.btnText = btnText; + } + + public List getArticles() { + return articles; + } + + public void setArticles(List articles) { + this.articles = articles; + } + + public static class PortalNewsArticle { + private String title; + private String description; + private String url; + private String picUrl; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getPicUrl() { + return picUrl; + } + + public void setPicUrl(String picUrl) { + this.picUrl = picUrl; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java new file mode 100644 index 0000000..27fdb92 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalMessageType.java @@ -0,0 +1,18 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +public enum PortalMessageType { + TEXT("text"), + TEXT_CARD("textcard"), + MARKDOWN("markdown"), + NEWS("news"); + + private final String value; + + PortalMessageType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java new file mode 100644 index 0000000..fdefc75 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPasswordUpdateRequest.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; + +public class PortalPasswordUpdateRequest { + + @NotBlank(message = "旧密码不能为空") + private String oldPassword; + + @NotBlank(message = "新密码不能为空") + private String newPassword; + + public String getOldPassword() { + return oldPassword; + } + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java new file mode 100644 index 0000000..e89f2b5 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalProxyConfigRequest.java @@ -0,0 +1,29 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class PortalProxyConfigRequest { + + @NotBlank(message = "服务器地址不能为空") + private String host; + + @NotNull(message = "端口不能为空") + @Min(value = 1, message = "端口范围无效") + @Max(value = 65535, message = "端口范围无效") + private Integer port; + + private String username; + + private String password; + + private String type = "HTTP"; + + private String exitIp; + + private Boolean active = true; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java new file mode 100644 index 0000000..0bbb7a5 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalRegisterRequest.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; + +public class PortalRegisterRequest { + + @NotBlank(message = "账号不能为空") + private String account; + + @NotBlank(message = "密码不能为空") + private String password; + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java new file mode 100644 index 0000000..9defdfb --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppApiKey.java @@ -0,0 +1,33 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_app_api_key") +public class PortalAppApiKey { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("app_id") + private Long appId; + + @TableField("api_key_hash") + private String apiKeyHash; + + @TableField("api_key_plain") + private String apiKeyPlain; + + @TableField("rate_limit_per_minute") + private Integer rateLimitPerMinute; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java new file mode 100644 index 0000000..c5ba1ec --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalCorpConfig.java @@ -0,0 +1,27 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_corp_config") +public class PortalCorpConfig { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("user_id") + private Long userId; + + @TableField("corp_id") + private String corpId; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java new file mode 100644 index 0000000..e457f65 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalMessageLog.java @@ -0,0 +1,58 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_message_log") +public class PortalMessageLog { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("user_id") + private Long userId; + + @TableField("app_id") + private Long appId; + + @TableField("agent_id") + private String agentId; + + @TableField("msg_type") + private String msgType; + + @TableField("to_user") + private String toUser; + + @TableField("to_party") + private String toParty; + + @TableField("to_all") + private Integer toAll; + + private String title; + + private String description; + + private String url; + + private String content; + + @TableField("request_json") + private String requestJson; + + @TableField("response_json") + private String responseJson; + + private Integer success; + + @TableField("error_message") + private String errorMessage; + + @TableField("created_at") + private Long createdAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java new file mode 100644 index 0000000..5bb1868 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalProxyConfig.java @@ -0,0 +1,39 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_proxy_config") +public class PortalProxyConfig { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("user_id") + private Long userId; + + private String host; + + private Integer port; + + private String username; + + private String password; + + private String type; // HTTP, SOCKS5 + + @TableField("exit_ip") + private String exitIp; + + private Boolean active; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java new file mode 100644 index 0000000..c4f4dcc --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalSystemConfig.java @@ -0,0 +1,24 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_system_config") +public class PortalSystemConfig { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("config_key") + private String configKey; + + @TableField("config_value") + private String configValue; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java new file mode 100644 index 0000000..2a84e43 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalUser.java @@ -0,0 +1,26 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_user") +public class PortalUser { + + @TableId(type = IdType.AUTO) + private Long id; + + private String account; + + @TableField("password_hash") + private String passwordHash; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java new file mode 100644 index 0000000..8552261 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalWecomApp.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_wecom_app") +public class PortalWecomApp { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("user_id") + private Long userId; + + @TableField("agent_id") + private String agentId; + + private String secret; + + private String token; + + @TableField("encoding_aes_key") + private String encodingAesKey; + + private String name; + + @TableField("avatar_url") + private String avatarUrl; + + private String description; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java new file mode 100644 index 0000000..6af7e7b --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardChartsResponse.java @@ -0,0 +1,83 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import java.util.List; + +public class DashboardChartsResponse { + + private List trend; + private List distribution; + + public List getTrend() { + return trend; + } + + public void setTrend(List trend) { + this.trend = trend; + } + + public List getDistribution() { + return distribution; + } + + public void setDistribution(List distribution) { + this.distribution = distribution; + } + + public static class TrendPoint { + private String date; + private long count; + + public TrendPoint() { + } + + public TrendPoint(String date, long count) { + this.date = date; + this.count = count; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + } + + public static class DistributionSlice { + private String name; + private long value; + + public DistributionSlice() { + } + + public DistributionSlice(String name, long value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java new file mode 100644 index 0000000..82c162c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardLogResponse.java @@ -0,0 +1,50 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class DashboardLogResponse { + + private String time; + private String appName; + private String receiver; + private int status; + private String errorMsg; + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getReceiver() { + return receiver; + } + + public void setReceiver(String receiver) { + this.receiver = receiver; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java new file mode 100644 index 0000000..c697010 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/DashboardStatsResponse.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class DashboardStatsResponse { + + private long todayTotal; + private double successRate; + private long activeApps; + private String lastErrorTime; + + public long getTodayTotal() { + return todayTotal; + } + + public void setTodayTotal(long todayTotal) { + this.todayTotal = todayTotal; + } + + public double getSuccessRate() { + return successRate; + } + + public void setSuccessRate(double successRate) { + this.successRate = successRate; + } + + public long getActiveApps() { + return activeApps; + } + + public void setActiveApps(long activeApps) { + this.activeApps = activeApps; + } + + public String getLastErrorTime() { + return lastErrorTime; + } + + public void setLastErrorTime(String lastErrorTime) { + this.lastErrorTime = lastErrorTime; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java new file mode 100644 index 0000000..81736a6 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppApiKeyResponse.java @@ -0,0 +1,59 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class PortalAppApiKeyResponse { + + private Long appId; + private boolean hasKey; + private String apiKey; + private Integer rateLimitPerMinute; + private Long createdAt; + private Long updatedAt; + + public Long getAppId() { + return appId; + } + + public void setAppId(Long appId) { + this.appId = appId; + } + + public boolean isHasKey() { + return hasKey; + } + + public void setHasKey(boolean hasKey) { + this.hasKey = hasKey; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public Integer getRateLimitPerMinute() { + return rateLimitPerMinute; + } + + public void setRateLimitPerMinute(Integer rateLimitPerMinute) { + this.rateLimitPerMinute = rateLimitPerMinute; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } + + public Long getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Long updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java new file mode 100644 index 0000000..76f3c98 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppResponse.java @@ -0,0 +1,68 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class PortalAppResponse { + + private Long id; + private String agentId; + private String name; + private String avatarUrl; + private String description; + private Long createdAt; + private Long updatedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } + + public Long getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Long updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java new file mode 100644 index 0000000..d729420 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalCorpResponse.java @@ -0,0 +1,14 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class PortalCorpResponse { + + private String corpId; + + public String getCorpId() { + return corpId; + } + + public void setCorpId(String corpId) { + this.corpId = corpId; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java new file mode 100644 index 0000000..bb81764 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogConverter.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; + +public final class PortalMessageLogConverter { + + private PortalMessageLogConverter() { + } + + public static PortalMessageLogResponse toResponse(PortalMessageLog log) { + PortalMessageLogResponse response = new PortalMessageLogResponse(); + response.setId(log.getId()); + response.setAppId(log.getAppId()); + response.setAgentId(log.getAgentId()); + response.setMsgType(log.getMsgType()); + response.setToUser(log.getToUser()); + response.setToParty(log.getToParty()); + response.setToAll(log.getToAll() != null && log.getToAll() == 1); + response.setTitle(log.getTitle()); + response.setDescription(log.getDescription()); + response.setUrl(log.getUrl()); + response.setContent(log.getContent()); + response.setSuccess(log.getSuccess() != null && log.getSuccess() == 1); + response.setErrorMessage(log.getErrorMessage()); + response.setCreatedAt(log.getCreatedAt()); + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java new file mode 100644 index 0000000..63a1484 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalMessageLogResponse.java @@ -0,0 +1,131 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class PortalMessageLogResponse { + + private Long id; + private Long appId; + private String agentId; + private String msgType; + private String toUser; + private String toParty; + private boolean toAll; + private String title; + private String description; + private String url; + private String content; + private boolean success; + private String errorMessage; + private Long createdAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getAppId() { + return appId; + } + + public void setAppId(Long appId) { + this.appId = appId; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getMsgType() { + return msgType; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public String getToUser() { + return toUser; + } + + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public String getToParty() { + return toParty; + } + + public void setToParty(String toParty) { + this.toParty = toParty; + } + + public boolean isToAll() { + return toAll; + } + + public void setToAll(boolean toAll) { + this.toAll = toAll; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java new file mode 100644 index 0000000..9ce5b5b --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPageResponse.java @@ -0,0 +1,57 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import java.util.List; + +public class PortalPageResponse { + + private List records; + private long total; + private int page; + private int pageSize; + + public PortalPageResponse() { + } + + public PortalPageResponse(List records, long total, int page, int pageSize) { + this.records = records; + this.total = total; + this.page = page; + this.pageSize = pageSize; + } + + public static PortalPageResponse of(List records, long total, int page, int pageSize) { + return new PortalPageResponse<>(records, total, page, pageSize); + } + + public List getRecords() { + return records; + } + + public void setRecords(List records) { + this.records = records; + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java new file mode 100644 index 0000000..f8be8c9 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalProxyConfigResponse.java @@ -0,0 +1,36 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import lombok.Data; + +@Data +public class PortalProxyConfigResponse { + private Long id; + private String host; + private Integer port; + private String username; + private String password; + private String type; + private String exitIp; + private Boolean active; + private Long createdAt; + private Long updatedAt; + + public static PortalProxyConfigResponse from(PortalProxyConfig config) { + if (config == null) { + return null; + } + PortalProxyConfigResponse response = new PortalProxyConfigResponse(); + response.setId(config.getId()); + response.setHost(config.getHost()); + response.setPort(config.getPort()); + response.setUsername(config.getUsername()); + response.setPassword(config.getPassword()); + response.setType(config.getType()); + response.setExitIp(config.getExitIp()); + response.setActive(config.getActive()); + response.setCreatedAt(config.getCreatedAt()); + response.setUpdatedAt(config.getUpdatedAt()); + return response; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java new file mode 100644 index 0000000..53f37af --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalUserResponse.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +public class PortalUserResponse { + + private Long id; + private String account; + private Long createdAt; + private Long updatedAt; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public Long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Long createdAt) { + this.createdAt = createdAt; + } + + public Long getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(Long updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java new file mode 100644 index 0000000..1985211 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/CaptchaService.java @@ -0,0 +1,47 @@ +package dev.qingzhou.pushserver.security; + +import dev.qingzhou.pushserver.service.SystemConfigService; +import java.util.Map; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; + +@Service +public class CaptchaService { + + private static final String VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; + private final SystemConfigService configService; + private final RestClient restClient; + + public CaptchaService(SystemConfigService configService) { + this.configService = configService; + this.restClient = RestClient.create(); + } + + public void validate(String input) { + if (!configService.isTurnstileEnabled()) { + return; + } + + if (!StringUtils.hasText(input)) { + throw new BadCredentialsException("验证码 Token 不能为空"); + } + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("secret", configService.getTurnstileSecretKey()); + formData.add("response", input); + + Map result = restClient.post() + .uri(VERIFY_URL) + .body(formData) + .retrieve() + .body(Map.class); + + if (result == null || !Boolean.TRUE.equals(result.get("success"))) { + throw new BadCredentialsException("验证码校验失败"); + } + } +} \ No newline at end of file diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java new file mode 100644 index 0000000..65cc2bf --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.java @@ -0,0 +1,56 @@ +package dev.qingzhou.pushserver.security; + +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.stereotype.Component; + +/** + * Simple in-memory fixed-window rate limiter keyed by API key record. + */ +@Component +public class PortalAppApiKeyRateLimiter { + + private static final long WINDOW_MS = 60_000L; + + private static class WindowCounter { + private long windowStart = System.currentTimeMillis(); + private final AtomicInteger count = new AtomicInteger(); + + synchronized boolean tryAcquire(int limit) { + long now = System.currentTimeMillis(); + if (now - windowStart >= WINDOW_MS) { + windowStart = now; + count.set(0); + } + int current = count.incrementAndGet(); + return current <= limit; + } + } + + private final Map buckets = new ConcurrentHashMap<>(); + + public void check(PortalAppApiKey apiKey) { + Integer limit = apiKey.getRateLimitPerMinute(); + if (limit == null || limit <= 0) { + return; + } + Long appId = apiKey.getAppId(); + if (appId == null) { + throw new PortalException(PortalStatus.BAD_REQUEST, "API Key 记录无效"); + } + WindowCounter counter = buckets.computeIfAbsent(appId, ignored -> new WindowCounter()); + if (!counter.tryAcquire(limit)) { + throw new PortalException(PortalStatus.TOO_MANY_REQUESTS, "API Key 速率限制已超出"); + } + } + + public void evict(Long appId) { + if (appId != null) { + buckets.remove(appId); + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java new file mode 100644 index 0000000..43a33d6 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalJsonLoginAuthenticationFilter.java @@ -0,0 +1,71 @@ +package dev.qingzhou.pushserver.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + +import org.jspecify.annotations.NonNull; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; +// 移除不需要的 import +// import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +public class PortalJsonLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter { + + private final ObjectMapper objectMapper; + private final CaptchaService captchaService; + + public PortalJsonLoginAuthenticationFilter( + ObjectMapper objectMapper, + CaptchaService captchaService + ) { + super("/api/login"); + this.objectMapper = objectMapper; + this.captchaService = captchaService; + } + + @Override + public Authentication attemptAuthentication( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response + ) throws AuthenticationException { + if (!isJsonRequest(request)) { + throw new AuthenticationServiceException("不支持的内容类型"); + } + PortalLoginRequest loginRequest; + try { + loginRequest = objectMapper.readValue(request.getInputStream(), PortalLoginRequest.class); + } catch (IOException ex) { + ex.printStackTrace(); + throw new AuthenticationServiceException("无效的登录参数", ex); + } + + String account = loginRequest.getAccount(); + String password = loginRequest.getPassword(); + String captcha = loginRequest.getCaptcha(); + + if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { + throw new AuthenticationServiceException("账号和密码不能为空"); + } + + captchaService.validate(captcha); + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(account, password); + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + + return this.getAuthenticationManager().authenticate(authRequest); + } + + private boolean isJsonRequest(HttpServletRequest request) { + String contentType = request.getContentType(); + return contentType != null && contentType.startsWith(MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java new file mode 100644 index 0000000..9d37f9e --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetails.java @@ -0,0 +1,61 @@ +package dev.qingzhou.pushserver.security; + +import java.util.Collection; +import java.util.List; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +public class PortalUserDetails implements UserDetails { + + private final Long userId; + private final String username; + private final String password; + private final List authorities; + + public PortalUserDetails(Long userId, String username, String password) { + this.userId = userId; + this.username = username; + this.password = password; + this.authorities = List.of(new SimpleGrantedAuthority("ROLE_USER")); + } + + public Long getUserId() { + return userId; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java new file mode 100644 index 0000000..036a3bb --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/security/PortalUserDetailsService.java @@ -0,0 +1,31 @@ +package dev.qingzhou.pushserver.security; + +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import dev.qingzhou.pushserver.service.PortalUserService; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalUserDetailsService implements UserDetailsService { + + private final PortalUserService portalUserService; + + public PortalUserDetailsService(PortalUserService portalUserService) { + this.portalUserService = portalUserService; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + if (!StringUtils.hasText(username)) { + throw new UsernameNotFoundException("Account is required"); + } + PortalUser user = portalUserService.findByAccount(username); + if (user == null) { + throw new UsernameNotFoundException("User not found"); + } + return new PortalUserDetails(user.getId(), user.getAccount(), user.getPasswordHash()); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java new file mode 100644 index 0000000..2716666 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/DashboardService.java @@ -0,0 +1,15 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; +import java.util.List; + +public interface DashboardService { + + DashboardStatsResponse fetchStats(Long userId); + + DashboardChartsResponse fetchCharts(Long userId); + + List fetchRecentLogs(Long userId, int limit); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java new file mode 100644 index 0000000..0e9cb32 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAccessTokenService.java @@ -0,0 +1,13 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.pushserver.manager.wecom.WecomToken; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; + +public interface PortalAccessTokenService { + + WecomToken fetchToken(String corpId, String secret, PortalProxyConfig proxyConfig); + + String getToken(Long appId, String corpId, String secret, PortalProxyConfig proxyConfig); + + void evict(Long appId); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java new file mode 100644 index 0000000..1c07b6f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppApiKeyService.java @@ -0,0 +1,21 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; + +public interface PortalAppApiKeyService extends IService { + + PortalAppApiKey rotateKey(Long userId, Long appId); + + PortalAppApiKey findByAppId(Long userId, Long appId); + + void removeByAppId(Long appId); + + PortalAppApiKey updateRateLimit(Long userId, Long appId, Integer rateLimitPerMinute); + + AppAuthContext requireAppByApiKey(String apiKey); + + record AppAuthContext(PortalAppApiKey apiKey, PortalWecomApp app) { + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java new file mode 100644 index 0000000..b50eed0 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalCorpConfigService.java @@ -0,0 +1,13 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; + +public interface PortalCorpConfigService extends IService { + + PortalCorpConfig getByUserId(Long userId); + + PortalCorpConfig requireByUserId(Long userId); + + PortalCorpConfig upsert(Long userId, String corpId); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java new file mode 100644 index 0000000..c483508 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageLogService.java @@ -0,0 +1,13 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; +import java.util.List; + +public interface PortalMessageLogService extends IService { + + List listRecent(Long userId, int limit, Long appId, Boolean success); + + PortalPageResponse pageLogs(Long userId, Long appId, Boolean success, int page, int pageSize); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java new file mode 100644 index 0000000..32dc980 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalMessageService.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; + +public interface PortalMessageService { + + PortalMessageLog send(Long userId, PortalMessageSendRequest request); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java new file mode 100644 index 0000000..b277077 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalProxyConfigService.java @@ -0,0 +1,14 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; + +public interface PortalProxyConfigService extends IService { + + PortalProxyConfig getByUserId(Long userId); + + PortalProxyConfig upsert(Long userId, PortalProxyConfigRequest request); + + void deleteByUserId(Long userId); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java new file mode 100644 index 0000000..e1a2616 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalUserService.java @@ -0,0 +1,15 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; + +public interface PortalUserService extends IService { + + PortalUser register(String account, String password); + + PortalUser authenticate(String account, String password); + + PortalUser findByAccount(String account); + + void updatePassword(Long userId, String oldPassword, String newPassword); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java new file mode 100644 index 0000000..0909104 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalWecomAppService.java @@ -0,0 +1,20 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import java.util.List; + +public interface PortalWecomAppService extends IService { + + PortalWecomApp addApp(Long userId, String agentId, String secret); + + List listByUser(Long userId); + + PortalWecomApp requireByUser(Long userId, Long appId); + + PortalWecomApp updateApp(Long userId, Long appId, String secret, String token, String encodingAesKey); + + PortalWecomApp syncApp(Long userId, Long appId); + + void deleteApp(Long userId, Long appId); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PushService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PushService.java new file mode 100644 index 0000000..59a7c78 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PushService.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.push.core.model.PushResult; +import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; + +public interface PushService { + + PushResult push(PushRequest request); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java new file mode 100644 index 0000000..8b4ec41 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/SystemConfigService.java @@ -0,0 +1,14 @@ +package dev.qingzhou.pushserver.service; + +public interface SystemConfigService { + + String get(String key); + String get(String key, String defaultValue); + void set(String key, String value); + + // Helper methods for Turnstile + boolean isTurnstileEnabled(); + String getTurnstileSiteKey(); + String getTurnstileSecretKey(); + void setTurnstileConfig(boolean enabled, String siteKey, String secretKey); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java new file mode 100644 index 0000000..6e8688e --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.java @@ -0,0 +1,243 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse; +import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; +import dev.qingzhou.pushserver.service.DashboardService; +import dev.qingzhou.pushserver.service.PortalMessageLogService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; + +@Service +public class DashboardServiceImpl implements DashboardService { + + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("MM-dd"); + private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final ZoneId ZONE = ZoneId.systemDefault(); + + private final PortalMessageLogService messageLogService; + private final PortalWecomAppService appService; + + public DashboardServiceImpl(PortalMessageLogService messageLogService, PortalWecomAppService appService) { + this.messageLogService = messageLogService; + this.appService = appService; + } + + @Override + public DashboardStatsResponse fetchStats(Long userId) { + long startOfDay = atStartOfDayMillis(0); + + long todayTotal = countLogs(userId, startOfDay, null); + long todaySuccess = countLogs(userId, startOfDay, 1); + double successRate = todayTotal == 0 ? 100.0 : todaySuccess * 100.0 / todayTotal; + + long activeApps = appService.count(new QueryWrapper() + .eq("user_id", userId)); + + PortalMessageLog lastError = messageLogService.getOne(new QueryWrapper() + .eq("user_id", userId) + .ge("created_at", startOfDay) + .eq("success", 0) + .orderByDesc("created_at") + .last("limit 1"), false); + String lastErrorTime = null; + if (lastError != null && lastError.getCreatedAt() != null) { + lastErrorTime = Instant.ofEpochMilli(lastError.getCreatedAt()) + .atZone(ZONE) + .toLocalTime() + .format(DateTimeFormatter.ofPattern("HH:mm:ss")); + } + + DashboardStatsResponse response = new DashboardStatsResponse(); + response.setTodayTotal(todayTotal); + response.setSuccessRate(successRate); + response.setActiveApps(activeApps); + response.setLastErrorTime(lastErrorTime); + return response; + } + + @Override + public DashboardChartsResponse fetchCharts(Long userId) { + Map appNames = loadAppNames(userId); + + DashboardChartsResponse response = new DashboardChartsResponse(); + response.setTrend(buildTrend(userId)); + response.setDistribution(buildDistribution(userId, appNames)); + return response; + } + + @Override + public List fetchRecentLogs(Long userId, int limit) { + int safeLimit = Math.max(1, Math.min(limit, 100)); + Map appNames = loadAppNames(userId); + + List logs = messageLogService.list(new QueryWrapper() + .eq("user_id", userId) + .orderByDesc("created_at") + .last("limit " + safeLimit)); + + List responses = new ArrayList<>(logs.size()); + for (PortalMessageLog log : logs) { + DashboardLogResponse item = new DashboardLogResponse(); + item.setTime(formatDateTime(log.getCreatedAt())); + item.setAppName(resolveAppName(appNames, log.getAppId(), log.getAgentId())); + item.setReceiver(resolveReceiver(log)); + item.setStatus(log.getSuccess() != null && log.getSuccess() == 1 ? 1 : 0); + item.setErrorMsg(log.getErrorMessage()); + responses.add(item); + } + return responses; + } + + private long countLogs(Long userId, long startTime, Integer success) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId) + .ge("created_at", startTime); + if (success != null) { + wrapper.eq("success", success); + } + return messageLogService.count(wrapper); + } + + private List buildTrend(Long userId) { + LocalDate today = LocalDate.now(ZONE); + LocalDate startDate = today.minusDays(6); + long startMillis = startDate.atStartOfDay(ZONE).toInstant().toEpochMilli(); + + Map counts = new LinkedHashMap<>(); + for (int i = 6; i >= 0; i--) { + LocalDate date = today.minusDays(i); + counts.put(date, 0L); + } + + // Optimize: Only select created_at + List logs = messageLogService.list(new QueryWrapper() + .select("created_at") + .eq("user_id", userId) + .ge("created_at", startMillis)); + + for (PortalMessageLog log : logs) { + if (log.getCreatedAt() == null) { + continue; + } + LocalDate date = Instant.ofEpochMilli(log.getCreatedAt()).atZone(ZONE).toLocalDate(); + // Ensure the date is within our 7-day window + if (!date.isBefore(startDate) && !date.isAfter(today)) { + counts.put(date, counts.getOrDefault(date, 0L) + 1); + } + } + + List trend = new ArrayList<>(counts.size()); + for (Map.Entry entry : counts.entrySet()) { + trend.add(new DashboardChartsResponse.TrendPoint(entry.getKey().format(DATE_FMT), entry.getValue())); + } + return trend; + } + + private List buildDistribution(Long userId, Map appNames) { + long startMillis = atStartOfDayMillis(29); + List logs = messageLogService.list(new QueryWrapper() + .select("app_id") // Optimize: Only select app_id + .eq("user_id", userId) + .ge("created_at", startMillis)); + Map counts = new HashMap<>(); + for (PortalMessageLog log : logs) { + if (log.getAppId() == null) { + continue; + } + counts.put(log.getAppId(), counts.getOrDefault(log.getAppId(), 0L) + 1); + } + + List slices = counts.entrySet().stream() + .map(e -> new DashboardChartsResponse.DistributionSlice( + resolveAppName(appNames, e.getKey(), null), + e.getValue())) + .sorted(Comparator.comparingLong(DashboardChartsResponse.DistributionSlice::getValue).reversed()) + .collect(Collectors.toList()); + + if (slices.size() > 5) { + List top = new ArrayList<>(slices.subList(0, 5)); + long other = slices.subList(5, slices.size()).stream() + .mapToLong(DashboardChartsResponse.DistributionSlice::getValue) + .sum(); + top.add(new DashboardChartsResponse.DistributionSlice("Other", other)); + slices = top; + } + + return slices; + } + + private Map loadAppNames(Long userId) { + List apps = appService.list(new QueryWrapper() + .eq("user_id", userId)); + if (apps == null || apps.isEmpty()) { + return Collections.emptyMap(); + } + Map map = new HashMap<>(); + for (PortalWecomApp app : apps) { + String name = app.getName(); + if (name == null || name.isBlank()) { + name = app.getAgentId(); + } + map.put(app.getId(), name); + } + return map; + } + + private String resolveAppName(Map appNames, Long appId, String agentId) { + if (appId != null && appNames.containsKey(appId)) { + return appNames.get(appId); + } + if (agentId != null) { + return agentId; + } + return "Unknown app"; + } + + private String resolveReceiver(PortalMessageLog log) { + if (log.getToAll() != null && log.getToAll() == 1) { + return "ALL"; + } + List parts = new ArrayList<>(2); + if (log.getToUser() != null && !log.getToUser().isBlank()) { + parts.add(log.getToUser()); + } + if (log.getToParty() != null && !log.getToParty().isBlank()) { + parts.add(log.getToParty()); + } + if (parts.isEmpty()) { + return "--"; + } + return String.join(" / ", parts); + } + + private String formatDateTime(Long millis) { + if (millis == null) { + return null; + } + return Instant.ofEpochMilli(millis).atZone(ZONE).toLocalDateTime().format(DATETIME_FMT); + } + + private long atStartOfDayMillis(int daysAgo) { + return LocalDate.now(ZONE) + .minusDays(daysAgo) + .atStartOfDay(ZONE) + .toInstant() + .toEpochMilli(); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java new file mode 100644 index 0000000..8c77625 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.java @@ -0,0 +1,64 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; +import dev.qingzhou.pushserver.manager.wecom.WecomToken; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import dev.qingzhou.pushserver.service.PortalAccessTokenService; +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Component; + +@Component +public class PortalAccessTokenServiceImpl implements PortalAccessTokenService { + + private static final int DEFAULT_EXPIRES_IN = 7200; + private static final long EXPIRE_BUFFER_MILLIS = 60_000L; + + private final WecomApiClient wecomApiClient; + private final Cache cache; + + public PortalAccessTokenServiceImpl(WecomApiClient wecomApiClient) { + this.wecomApiClient = wecomApiClient; + this.cache = Caffeine.newBuilder() + .expireAfterWrite(2, TimeUnit.HOURS) + .build(); + } + + @Override + public WecomToken fetchToken(String corpId, String secret, PortalProxyConfig proxyConfig) { + return wecomApiClient.getToken(corpId, secret, proxyConfig); + } + + @Override + public String getToken(Long appId, String corpId, String secret, PortalProxyConfig proxyConfig) { + CachedToken cached = cache.getIfPresent(appId); + long now = System.currentTimeMillis(); + if (cached != null && cached.expireAtMillis > now) { + return cached.value; + } + WecomToken token = fetchToken(corpId, secret, proxyConfig); + int expiresIn = token.getExpiresIn() != null ? token.getExpiresIn() : DEFAULT_EXPIRES_IN; + long expireAt = now + TimeUnit.SECONDS.toMillis(expiresIn) - EXPIRE_BUFFER_MILLIS; + if (expireAt < now) { + expireAt = now; + } + cache.put(appId, new CachedToken(token.getAccessToken(), expireAt)); + return token.getAccessToken(); + } + + @Override + public void evict(Long appId) { + cache.invalidate(appId); + } + + private static class CachedToken { + private final String value; + private final long expireAtMillis; + + private CachedToken(String value, long expireAtMillis) { + this.value = value; + this.expireAtMillis = expireAtMillis; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java new file mode 100644 index 0000000..9fce699 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.java @@ -0,0 +1,130 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.service.PortalAppApiKeyService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.HexFormat; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalAppApiKeyServiceImpl extends ServiceImpl + implements PortalAppApiKeyService { + + private final PortalWecomAppService appService; + private final PortalAppApiKeyRateLimiter rateLimiter; + private final SecureRandom random = new SecureRandom(); + + public PortalAppApiKeyServiceImpl(PortalWecomAppService appService, PortalAppApiKeyRateLimiter rateLimiter) { + this.appService = appService; + this.rateLimiter = rateLimiter; + } + + @Override + public PortalAppApiKey rotateKey(Long userId, Long appId) { + PortalWecomApp app = appService.requireByUser(userId, appId); + String rawKey = generateKey(); + String hash = hashKey(rawKey); + PortalAppApiKey existing = getOne(new QueryWrapper() + .eq("app_id", app.getId())); + long now = System.currentTimeMillis(); + if (existing == null) { + PortalAppApiKey record = new PortalAppApiKey(); + record.setAppId(app.getId()); + record.setApiKeyHash(hash); + record.setApiKeyPlain(rawKey); + record.setRateLimitPerMinute(0); + record.setCreatedAt(now); + record.setUpdatedAt(now); + save(record); + return record; + } + existing.setApiKeyHash(hash); + existing.setApiKeyPlain(rawKey); + existing.setUpdatedAt(now); + updateById(existing); + return existing; + } + + @Override + public PortalAppApiKey findByAppId(Long userId, Long appId) { + PortalWecomApp app = appService.requireByUser(userId, appId); + return getOne(new QueryWrapper() + .eq("app_id", app.getId())); + } + + @Override + public void removeByAppId(Long appId) { + remove(new QueryWrapper() + .eq("app_id", appId)); + rateLimiter.evict(appId); + } + + @Override + public PortalAppApiKey updateRateLimit(Long userId, Long appId, Integer rateLimitPerMinute) { + if (rateLimitPerMinute != null && rateLimitPerMinute < 0) { + throw new PortalException(PortalStatus.BAD_REQUEST, "每分钟速率限制必须大于等于 0"); + } + PortalWecomApp app = appService.requireByUser(userId, appId); + PortalAppApiKey record = getOne(new QueryWrapper() + .eq("app_id", app.getId())); + if (record == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "未找到 API Key"); + } + record.setRateLimitPerMinute(rateLimitPerMinute == null ? 0 : rateLimitPerMinute); + record.setUpdatedAt(System.currentTimeMillis()); + updateById(record); + return record; + } + + @Override + public AppAuthContext requireAppByApiKey(String apiKey) { + if (!StringUtils.hasText(apiKey)) { + throw new PortalException(PortalStatus.UNAUTHORIZED, "缺少 API Key"); + } + String hash = hashKey(apiKey.trim()); + PortalAppApiKey record = getOne(new QueryWrapper() + .eq("api_key_hash", hash)); + if (record == null) { + throw new PortalException(PortalStatus.UNAUTHORIZED, "无效的 API Key"); + } + PortalWecomApp app = appService.getById(record.getAppId()); + if (app == null) { + throw new PortalException(PortalStatus.UNAUTHORIZED, "无效的 API Key"); + } + rateLimiter.check(record); + return new AppAuthContext(record, app); + } + + private String generateKey() { + byte[] bytes = new byte[32]; + random.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + + private String hashKey(String key) { + MessageDigest digest = getDigest(); + byte[] hashed = digest.digest(key.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hashed); + } + + private MessageDigest getDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (Exception ex) { + throw new IllegalStateException("SHA-256 not available", ex); + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java new file mode 100644 index 0000000..882cfc8 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.java @@ -0,0 +1,53 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import dev.qingzhou.pushserver.service.PortalCorpConfigService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalCorpConfigServiceImpl extends ServiceImpl implements PortalCorpConfigService { + + @Override + public PortalCorpConfig getByUserId(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return getOne(wrapper); + } + + @Override + public PortalCorpConfig requireByUserId(Long userId) { + PortalCorpConfig config = getByUserId(userId); + if (config == null) { + throw new PortalException(PortalStatus.BAD_REQUEST, "企业配置未设置"); + } + return config; + } + + @Override + public PortalCorpConfig upsert(Long userId, String corpId) { + if (!StringUtils.hasText(corpId)) { + throw new PortalException(PortalStatus.BAD_REQUEST, "CorpId 不能为空"); + } + PortalCorpConfig config = getByUserId(userId); + long now = System.currentTimeMillis(); + if (config == null) { + config = new PortalCorpConfig(); + config.setUserId(userId); + config.setCorpId(corpId.trim()); + config.setCreatedAt(now); + config.setUpdatedAt(now); + save(config); + } else { + config.setCorpId(corpId.trim()); + config.setUpdatedAt(now); + updateById(config); + } + return config; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java new file mode 100644 index 0000000..e047c78 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.java @@ -0,0 +1,61 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.service.PortalMessageLogService; +import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class PortalMessageLogServiceImpl extends ServiceImpl implements PortalMessageLogService { + + @Override + public List listRecent(Long userId, int limit, Long appId, Boolean success) { + int safeLimit = Math.max(1, Math.min(limit, 200)); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId) + .orderByDesc("created_at") + .last("limit " + safeLimit); + if (appId != null) { + wrapper.eq("app_id", appId); + } + if (success != null) { + wrapper.eq("success", success ? 1 : 0); + } + return list(wrapper); + } + + @Override + public PortalPageResponse pageLogs(Long userId, Long appId, Boolean success, int page, int pageSize) { + int safePage = Math.max(1, page); + int safePageSize = Math.max(1, Math.min(pageSize, 200)); + long offset = (long) (safePage - 1) * safePageSize; + + QueryWrapper dataWrapper = new QueryWrapper<>(); + dataWrapper.eq("user_id", userId) + .orderByDesc("created_at") + .last("limit " + safePageSize + " offset " + offset); + if (appId != null) { + dataWrapper.eq("app_id", appId); + } + if (success != null) { + dataWrapper.eq("success", success ? 1 : 0); + } + List records = list(dataWrapper); + + QueryWrapper countWrapper = new QueryWrapper<>(); + countWrapper.eq("user_id", userId); + if (appId != null) { + countWrapper.eq("app_id", appId); + } + if (success != null) { + countWrapper.eq("success", success ? 1 : 0); + } + long total = count(countWrapper); + + return PortalPageResponse.of(records, total, safePage, safePageSize); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java new file mode 100644 index 0000000..b96bfe0 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.java @@ -0,0 +1,238 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; +import dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload; +import dev.qingzhou.pushserver.manager.wecom.WecomSendResponse; +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest; +import dev.qingzhou.pushserver.model.dto.portal.PortalMessageType; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.service.PortalAccessTokenService; +import dev.qingzhou.pushserver.service.PortalCorpConfigService; +import dev.qingzhou.pushserver.service.PortalMessageLogService; +import dev.qingzhou.pushserver.service.PortalMessageService; +import dev.qingzhou.pushserver.service.PortalProxyConfigService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalMessageServiceImpl implements PortalMessageService { + + private final PortalWecomAppService appService; + private final PortalCorpConfigService corpConfigService; + private final PortalAccessTokenService accessTokenService; + private final WecomApiClient wecomApiClient; + private final PortalMessageLogService messageLogService; + private final ObjectMapper objectMapper; + private final PortalProxyConfigService proxyConfigService; + + public PortalMessageServiceImpl( + PortalWecomAppService appService, + PortalCorpConfigService corpConfigService, + PortalAccessTokenService accessTokenService, + WecomApiClient wecomApiClient, + PortalMessageLogService messageLogService, + ObjectMapper objectMapper, + PortalProxyConfigService proxyConfigService + ) { + this.appService = appService; + this.corpConfigService = corpConfigService; + this.accessTokenService = accessTokenService; + this.wecomApiClient = wecomApiClient; + this.messageLogService = messageLogService; + this.objectMapper = objectMapper; + this.proxyConfigService = proxyConfigService; + } + + @Override + public PortalMessageLog send(Long userId, PortalMessageSendRequest request) { + PortalWecomApp app = appService.requireByUser(userId, request.getAppId()); + PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); + PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); + + String accessToken = accessTokenService.getToken(app.getId(), corpConfig.getCorpId(), app.getSecret(), proxyConfig); + WecomMessagePayload payload = buildPayload(app, request); + String requestJson = toJson(payload); + WecomSendResponse response = null; + String responseJson = null; + String errorMessage = null; + boolean success = false; + PortalMessageLog log = null; + try { + response = wecomApiClient.sendMessage(accessToken, payload, proxyConfig); + responseJson = toJson(response); + success = response.isSuccess(); + if (!success) { + errorMessage = response.getErrmsg(); + } + } catch (PortalException ex) { + errorMessage = ex.getMessage(); + throw ex; + } catch (Exception ex) { + errorMessage = ex.getMessage(); + throw new PortalException(PortalStatus.BAD_GATEWAY, "发送消息失败", ex); + } finally { + log = buildLog(userId, app, request, requestJson, responseJson, success, errorMessage); + messageLogService.save(log); + } + if (!success && response != null) { + throw new PortalException( + PortalStatus.BAD_REQUEST, + "企业微信发送失败: " + response.getErrmsg() + " (" + response.getErrcode() + ")" + ); + } + return log; + } + + private WecomMessagePayload buildPayload(PortalWecomApp app, PortalMessageSendRequest request) { + WecomMessagePayload payload = new WecomMessagePayload(); + payload.setMsgtype(request.getMsgType().getValue()); + payload.setAgentid(parseAgentId(app.getAgentId())); + if (request.isToAll()) { + payload.setTouser("@all"); + } else { + payload.setTouser(normalizeTarget(request.getToUser())); + payload.setToparty(normalizeTarget(request.getToParty())); + } + if (!request.isToAll() + && !StringUtils.hasText(payload.getTouser()) + && !StringUtils.hasText(payload.getToparty())) { + throw new PortalException(PortalStatus.BAD_REQUEST, "接收者不能为空"); + } + switch (request.getMsgType()) { + case TEXT -> payload.setText(buildText(request)); + case MARKDOWN -> payload.setMarkdown(buildMarkdown(request)); + case TEXT_CARD -> payload.setTextcard(buildTextCard(request)); + case NEWS -> payload.setNews(buildNews(request)); + default -> throw new PortalException(PortalStatus.BAD_REQUEST, "不支持的消息类型"); + } + return payload; + } + + private WecomMessagePayload.Text buildText(PortalMessageSendRequest request) { + String content = requireText(request.getContent(), "content"); + WecomMessagePayload.Text text = new WecomMessagePayload.Text(); + text.setContent(content); + return text; + } + + private WecomMessagePayload.Markdown buildMarkdown(PortalMessageSendRequest request) { + String content = requireText(request.getContent(), "content"); + WecomMessagePayload.Markdown markdown = new WecomMessagePayload.Markdown(); + markdown.setContent(content); + return markdown; + } + + private WecomMessagePayload.TextCard buildTextCard(PortalMessageSendRequest request) { + String title = requireText(request.getTitle(), "title"); + String description = requireText(request.getDescription(), "description"); + String url = requireText(request.getUrl(), "url"); + WecomMessagePayload.TextCard card = new WecomMessagePayload.TextCard(); + card.setTitle(title); + card.setDescription(description); + card.setUrl(url); + card.setBtnText(request.getBtnText()); + return card; + } + + private WecomMessagePayload.News buildNews(PortalMessageSendRequest request) { + List items = request.getArticles(); + if (items == null || items.isEmpty()) { + throw new PortalException(PortalStatus.BAD_REQUEST, "articles 不能为空"); + } + List articles = new ArrayList<>(items.size()); + for (PortalMessageSendRequest.PortalNewsArticle item : items) { + if (item == null) { + throw new PortalException(PortalStatus.BAD_REQUEST, "article 不能为空"); + } + WecomMessagePayload.Article article = new WecomMessagePayload.Article(); + article.setTitle(requireText(item.getTitle(), "articles.title")); + article.setUrl(requireText(item.getUrl(), "articles.url")); + if (StringUtils.hasText(item.getDescription())) { + article.setDescription(item.getDescription().trim()); + } + if (StringUtils.hasText(item.getPicUrl())) { + article.setPicUrl(item.getPicUrl().trim()); + } + articles.add(article); + } + WecomMessagePayload.News news = new WecomMessagePayload.News(); + news.setArticles(articles); + return news; + } + + private PortalMessageLog buildLog( + Long userId, + PortalWecomApp app, + PortalMessageSendRequest request, + String requestJson, + String responseJson, + boolean success, + String errorMessage + ) { + PortalMessageLog log = new PortalMessageLog(); + log.setUserId(userId); + log.setAppId(app.getId()); + log.setAgentId(app.getAgentId()); + log.setMsgType(request.getMsgType().getValue()); + log.setToUser(request.getToUser()); + log.setToParty(request.getToParty()); + log.setToAll(request.isToAll() ? 1 : 0); + log.setTitle(request.getTitle()); + log.setDescription(request.getDescription()); + log.setUrl(request.getUrl()); + if (request.getMsgType() == PortalMessageType.TEXT || request.getMsgType() == PortalMessageType.MARKDOWN) { + log.setContent(request.getContent()); + } else if (request.getMsgType() == PortalMessageType.NEWS && request.getArticles() != null + && !request.getArticles().isEmpty()) { + PortalMessageSendRequest.PortalNewsArticle first = request.getArticles().get(0); + log.setTitle(first.getTitle()); + log.setDescription(first.getDescription()); + log.setUrl(first.getUrl()); + } + log.setRequestJson(requestJson); + log.setResponseJson(responseJson); + log.setSuccess(success ? 1 : 0); + log.setErrorMessage(errorMessage); + log.setCreatedAt(System.currentTimeMillis()); + return log; + } + + private String normalizeTarget(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } + + private String requireText(String value, String field) { + if (!StringUtils.hasText(value)) { + throw new PortalException(PortalStatus.BAD_REQUEST, field + " 不能为空"); + } + return value.trim(); + } + + private long parseAgentId(String agentId) { + try { + return Long.parseLong(agentId.trim()); + } catch (Exception ex) { + throw new PortalException(PortalStatus.BAD_REQUEST, "无效的 agentId"); + } + } + + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception ex) { + return null; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java new file mode 100644 index 0000000..d165e3f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.java @@ -0,0 +1,69 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; +import dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper; +import dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import dev.qingzhou.pushserver.service.PortalProxyConfigService; +import org.springframework.stereotype.Service; + +@Service +public class PortalProxyConfigServiceImpl extends ServiceImpl implements PortalProxyConfigService { + + private final WecomApiClient wecomApiClient; + + public PortalProxyConfigServiceImpl(WecomApiClient wecomApiClient) { + this.wecomApiClient = wecomApiClient; + } + + @Override + public PortalProxyConfig getByUserId(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + return getOne(wrapper); + } + + @Override + public PortalProxyConfig upsert(Long userId, PortalProxyConfigRequest request) { + PortalProxyConfig config = getByUserId(userId); + long now = System.currentTimeMillis(); + if (config == null) { + config = new PortalProxyConfig(); + config.setUserId(userId); + config.setCreatedAt(now); + } + + config.setHost(request.getHost()); + config.setPort(request.getPort()); + config.setUsername(request.getUsername()); + config.setPassword(request.getPassword()); + config.setType(request.getType()); + config.setExitIp(request.getExitIp()); + config.setActive(request.getActive()); + config.setUpdatedAt(now); + + // 1. 先保存配置到数据库 + if (config.getId() == null) { + save(config); + } else { + updateById(config); + } + + // 2. 再进行连通性测试(如果启用) + // 即使测试失败抛出异常,数据也已经保存成功,方便用户后续修改 + if (Boolean.TRUE.equals(config.getActive())) { + wecomApiClient.testConnectivity(config); + } + + return config; + } + + @Override + public void deleteByUserId(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + remove(wrapper); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java new file mode 100644 index 0000000..4b273fb --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.java @@ -0,0 +1,85 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.mapper.portal.PortalUserMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalUser; +import dev.qingzhou.pushserver.service.PortalUserService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalUserServiceImpl extends ServiceImpl implements PortalUserService { + + private final PasswordEncoder passwordEncoder; + + public PortalUserServiceImpl(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + @Override + public PortalUser register(String account, String password) { + if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { + throw new PortalException(PortalStatus.BAD_REQUEST, "账号和密码不能为空"); + } + if (existsAccount(account)) { + throw new PortalException(PortalStatus.CONFLICT, "账号已存在"); + } + PortalUser user = new PortalUser(); + user.setAccount(account.trim()); + user.setPasswordHash(passwordEncoder.encode(password)); + long now = System.currentTimeMillis(); + user.setCreatedAt(now); + user.setUpdatedAt(now); + save(user); + return user; + } + + @Override + public PortalUser authenticate(String account, String password) { + if (!StringUtils.hasText(account) || !StringUtils.hasText(password)) { + throw new PortalException(PortalStatus.BAD_REQUEST, "账号和密码不能为空"); + } + PortalUser user = findByAccount(account); + if (user == null || !passwordEncoder.matches(password, user.getPasswordHash())) { + throw new PortalException(PortalStatus.UNAUTHORIZED, "凭证无效"); + } + return user; + } + + @Override + public PortalUser findByAccount(String account) { + if (!StringUtils.hasText(account)) { + return null; + } + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("account", account.trim()); + return getOne(wrapper); + } + + @Override + public void updatePassword(Long userId, String oldPassword, String newPassword) { + if (!StringUtils.hasText(oldPassword) || !StringUtils.hasText(newPassword)) { + throw new PortalException(PortalStatus.BAD_REQUEST, "密码字段不能为空"); + } + PortalUser user = getById(userId); + if (user == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "用户未找到"); + } + if (!passwordEncoder.matches(oldPassword, user.getPasswordHash())) { + throw new PortalException(PortalStatus.UNAUTHORIZED, "旧密码不匹配"); + } + user.setPasswordHash(passwordEncoder.encode(newPassword)); + user.setUpdatedAt(System.currentTimeMillis()); + updateById(user); + } + + private boolean existsAccount(String account) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("account", account.trim()); + return count(wrapper) > 0; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java new file mode 100644 index 0000000..ca17362 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.java @@ -0,0 +1,143 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo; +import dev.qingzhou.pushserver.manager.wecom.WecomApiClient; +import dev.qingzhou.pushserver.manager.wecom.WecomToken; +import dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; +import dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.service.PortalAccessTokenService; +import dev.qingzhou.pushserver.service.PortalCorpConfigService; +import dev.qingzhou.pushserver.service.PortalProxyConfigService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PortalWecomAppServiceImpl extends ServiceImpl implements PortalWecomAppService { + + private final WecomApiClient wecomApiClient; + private final PortalAccessTokenService accessTokenService; + private final PortalCorpConfigService corpConfigService; + private final PortalProxyConfigService proxyConfigService; + + public PortalWecomAppServiceImpl( + WecomApiClient wecomApiClient, + PortalAccessTokenService accessTokenService, + PortalCorpConfigService corpConfigService, + PortalProxyConfigService proxyConfigService + ) { + this.wecomApiClient = wecomApiClient; + this.accessTokenService = accessTokenService; + this.corpConfigService = corpConfigService; + this.proxyConfigService = proxyConfigService; + } + + @Override + public PortalWecomApp addApp(Long userId, String agentId, String secret) { + if (!StringUtils.hasText(agentId) || !StringUtils.hasText(secret)) { + throw new PortalException(PortalStatus.BAD_REQUEST, "AgentId 和 Secret 不能为空"); + } + if (existsApp(userId, agentId)) { + throw new PortalException(PortalStatus.CONFLICT, "Agent 已存在"); + } + PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); + PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); + WecomToken token = accessTokenService.fetchToken(corpConfig.getCorpId(), secret, proxyConfig); + WecomAgentInfo info = wecomApiClient.getAgentInfo(token.getAccessToken(), agentId, proxyConfig); + PortalWecomApp app = new PortalWecomApp(); + long now = System.currentTimeMillis(); + app.setUserId(userId); + app.setAgentId(agentId.trim()); + app.setSecret(secret.trim()); + app.setName(info.getName()); + app.setAvatarUrl(info.getAvatarUrl()); + app.setDescription(info.getDescription()); + app.setCreatedAt(now); + app.setUpdatedAt(now); + save(app); + return app; + } + + @Override + public List listByUser(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId) + .orderByDesc("created_at"); + return list(wrapper); + } + + @Override + public PortalWecomApp requireByUser(Long userId, Long appId) { + PortalWecomApp app = getById(appId); + if (app == null || !app.getUserId().equals(userId)) { + throw new PortalException(PortalStatus.NOT_FOUND, "应用未找到"); + } + return app; + } + + @Override + public PortalWecomApp updateApp(Long userId, Long appId, String secret, String token, String encodingAesKey) { + PortalWecomApp app = requireByUser(userId, appId); + boolean changed = false; + + if (StringUtils.hasText(secret) && !secret.equals(app.getSecret())) { + app.setSecret(secret.trim()); + // 如果 Secret 变更,需要清除 Token 缓存 + accessTokenService.evict(app.getId()); + changed = true; + } + + if (token != null && !token.equals(app.getToken())) { + app.setToken(token.trim()); + changed = true; + } + + if (encodingAesKey != null && !encodingAesKey.equals(app.getEncodingAesKey())) { + app.setEncodingAesKey(encodingAesKey.trim()); + changed = true; + } + + if (changed) { + app.setUpdatedAt(System.currentTimeMillis()); + updateById(app); + } + + return app; + } + + @Override + public PortalWecomApp syncApp(Long userId, Long appId) { + PortalWecomApp app = requireByUser(userId, appId); + PortalCorpConfig corpConfig = corpConfigService.requireByUserId(userId); + PortalProxyConfig proxyConfig = proxyConfigService.getByUserId(userId); + String accessToken = accessTokenService.getToken(app.getId(), corpConfig.getCorpId(), app.getSecret(), proxyConfig); + WecomAgentInfo info = wecomApiClient.getAgentInfo(accessToken, app.getAgentId(), proxyConfig); + app.setName(info.getName()); + app.setAvatarUrl(info.getAvatarUrl()); + app.setDescription(info.getDescription()); + app.setUpdatedAt(System.currentTimeMillis()); + updateById(app); + return app; + } + + @Override + public void deleteApp(Long userId, Long appId) { + PortalWecomApp app = requireByUser(userId, appId); + removeById(app.getId()); + accessTokenService.evict(app.getId()); + } + + private boolean existsApp(Long userId, String agentId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId) + .eq("agent_id", agentId.trim()); + return count(wrapper) > 0; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java new file mode 100644 index 0000000..cdbd4d5 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PushServiceImpl.java @@ -0,0 +1,105 @@ +package dev.qingzhou.pushserver.service.impl; + +import dev.qingzhou.push.core.api.ChannelIds; +import dev.qingzhou.push.core.api.IPushChannel; +import dev.qingzhou.push.core.api.PushChannelFactory; +import dev.qingzhou.push.core.model.PushConfig; +import dev.qingzhou.push.core.model.PushMessage; +import dev.qingzhou.push.core.model.PushResult; +import dev.qingzhou.push.core.model.enums.MessageType; +import dev.qingzhou.pushserver.config.PushProperties; +import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; +import dev.qingzhou.pushserver.service.PushService; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +@Service +public class PushServiceImpl implements PushService { + + private final PushProperties properties; + + public PushServiceImpl(PushProperties properties) { + this.properties = properties; + } + + @Override + public PushResult push(PushRequest request) { + IPushChannel channel = PushChannelFactory.getChannel(ChannelIds.WECOM); + PushMessage message = buildMessage(request); + return channel.send(message, buildConfig()); + } + + private PushMessage buildMessage(PushRequest request) { + String target = requireNonBlank(request.getTarget(), "target"); + MessageType type = parseType(request.getType()); + return switch (type) { + case TEXT -> PushMessage.text(target, requireNonBlank(request.getContent(), "content")); + case MARKDOWN -> PushMessage.markdown( + target, + requireNonBlank(request.getTitle(), "title"), + requireNonBlank(request.getContent(), "content") + ); + case TEXT_CARD -> PushMessage.textCard( + target, + requireNonBlank(request.getTitle(), "title"), + requireNonBlank(request.getContent(), "content"), + requireNonBlank(request.getUrl(), "url") + ); + case IMAGE -> PushMessage.image(target, requireNonBlank(request.getMediaId(), "mediaId")); + case NEWS -> PushMessage.news(target, mapArticles(request.getArticles())); + }; + } + + private MessageType parseType(String type) { + if (!StringUtils.hasText(type)) { + return MessageType.TEXT; + } + try { + String normalized = type.trim().toUpperCase(Locale.ROOT).replace('-', '_'); + return MessageType.valueOf(normalized); + } catch (IllegalArgumentException ex) { + throw new IllegalArgumentException("Unsupported message type: " + type); + } + } + + private List mapArticles(List items) { + if (items == null || items.isEmpty()) { + throw new IllegalArgumentException("articles cannot be empty"); + } + List articles = new ArrayList<>(items.size()); + for (PushRequest.Article item : items) { + if (item == null) { + throw new IllegalArgumentException("article cannot be null"); + } + String title = requireNonBlank(item.getTitle(), "articles.title"); + String url = requireNonBlank(item.getUrl(), "articles.url"); + PushMessage.Article article = new PushMessage.Article(); + article.setTitle(title); + article.setUrl(url); + article.setDescription(item.getDescription()); + article.setPicUrl(item.getPicUrl()); + articles.add(article); + } + return articles; + } + + private String requireNonBlank(String value, String field) { + if (!StringUtils.hasText(value)) { + throw new IllegalArgumentException(field + " cannot be blank"); + } + return value.trim(); + } + + private PushConfig buildConfig() { + PushProperties.Wecom wecom = properties.getWecom(); + return new PushConfig( + wecom.getAppKey(), + wecom.getAppSecret(), + wecom.getAgentId(), + wecom.getWebhookUrl() + ); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java new file mode 100644 index 0000000..8c6411d --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.java @@ -0,0 +1,77 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig; +import dev.qingzhou.pushserver.service.SystemConfigService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SystemConfigServiceImpl implements SystemConfigService { + + private static final String KEY_TURNSTILE_ENABLED = "turnstile.enabled"; + private static final String KEY_TURNSTILE_SITE_KEY = "turnstile.site_key"; + private static final String KEY_TURNSTILE_SECRET_KEY = "turnstile.secret_key"; + + private final PortalSystemConfigMapper configMapper; + + public SystemConfigServiceImpl(PortalSystemConfigMapper configMapper) { + this.configMapper = configMapper; + } + + @Override + public String get(String key) { + return get(key, null); + } + + @Override + public String get(String key, String defaultValue) { + PortalSystemConfig config = configMapper.selectOne( + new QueryWrapper().eq("config_key", key) + ); + return config != null ? config.getConfigValue() : defaultValue; + } + + @Override + @Transactional + public void set(String key, String value) { + PortalSystemConfig config = configMapper.selectOne( + new QueryWrapper().eq("config_key", key) + ); + if (config == null) { + config = new PortalSystemConfig(); + config.setConfigKey(key); + config.setConfigValue(value); + config.setUpdatedAt(System.currentTimeMillis()); + configMapper.insert(config); + } else { + config.setConfigValue(value); + config.setUpdatedAt(System.currentTimeMillis()); + configMapper.updateById(config); + } + } + + @Override + public boolean isTurnstileEnabled() { + return "true".equalsIgnoreCase(get(KEY_TURNSTILE_ENABLED, "false")); + } + + @Override + public String getTurnstileSiteKey() { + return get(KEY_TURNSTILE_SITE_KEY, ""); + } + + @Override + public String getTurnstileSecretKey() { + return get(KEY_TURNSTILE_SECRET_KEY, ""); + } + + @Override + @Transactional + public void setTurnstileConfig(boolean enabled, String siteKey, String secretKey) { + set(KEY_TURNSTILE_ENABLED, String.valueOf(enabled)); + set(KEY_TURNSTILE_SITE_KEY, siteKey); + set(KEY_TURNSTILE_SECRET_KEY, secretKey); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java new file mode 100644 index 0000000..8aa0384 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/CasTokenBucket.java @@ -0,0 +1,55 @@ +package dev.qingzhou.pushserver.utils; + +import java.util.concurrent.atomic.AtomicReference; + +public class CasTokenBucket { + + private final long capacity; // 最大令牌数(burst) + private final double refillPerNanos; // 每纳秒生成的令牌数 + private final AtomicReference state; + + public CasTokenBucket(long capacity, long qps) { + if (capacity <= 0 || qps <= 0) throw new IllegalArgumentException("capacity/qps must be > 0"); + this.capacity = capacity; + this.refillPerNanos = qps / 1_000_000_000.0; + long now = System.nanoTime(); + this.state = new AtomicReference<>(new State(capacity, now)); + } + + private record State(double tokens, long lastRefillNanos) {} + + public boolean tryAcquire() { + int spins = 0; + while (true) { + State cur = state.get(); + long now = System.nanoTime(); + + long delta = now - cur.lastRefillNanos; + if (delta < 0) delta = 0; // 理论上 nanoTime 单调,但防御性写一下 + + double newTokens = cur.tokens; + if (delta > 0) { + double generated = delta * refillPerNanos; + newTokens = Math.min(capacity, newTokens + generated); + } + + // 不管成功/失败,都尽量把时间推进,减少下次重复计算 + if (newTokens < 1.0) { + State next = (delta > 0) ? new State(newTokens, now) : cur; + if (next == cur || state.compareAndSet(cur, next)) { + return false; + } + } else { + State next = new State(newTokens - 1.0, now); + if (state.compareAndSet(cur, next)) { + return true; + } + } + + // 轻微退避,避免高竞争 CPU 飙 + if (++spins > 10) { + Thread.onSpinWait(); + } + } + } +} diff --git a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json new file mode 100644 index 0000000..1c46924 --- /dev/null +++ b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -0,0 +1,10892 @@ +{ + "reflection": [ + { + "type": "boolean" + }, + { + "type": "boolean[]", + "jniAccessible": true + }, + { + "type": "ch.qos.logback.classic.BasicConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "ch.qos.logback.classic.Logger" + }, + { + "type": "ch.qos.logback.classic.LoggerContext" + }, + { + "type": "ch.qos.logback.classic.spi.LogbackServiceProvider" + }, + { + "type": "ch.qos.logback.classic.util.DefaultJoranConfigurator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.annotation.TableName" + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.DdlAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisDependsOnDatabaseInitializationDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.core.io.ResourceLoader", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "mybatisPlusSpringApplicationContextAware", + "parameterTypes": [] + }, + { + "name": "sqlSessionFactory", + "parameterTypes": [ + "javax.sql.DataSource" + ] + }, + { + "name": "sqlSessionTemplate", + "parameterTypes": [ + "org.apache.ibatis.session.SqlSessionFactory" + ] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration$AutoConfiguredMapperScannerRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration" + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.AbstractWrapper", + "methods": [ + { + "name": "getEntity", + "parameterTypes": [] + }, + { + "name": "getParamNameValuePairs", + "parameterTypes": [] + }, + { + "name": "getSqlComment", + "parameterTypes": [] + }, + { + "name": "getSqlFirst", + "parameterTypes": [] + }, + { + "name": "getSqlSegment", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.ISqlSegment" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.Wrapper", + "methods": [ + { + "name": "isEmptyOfNormal", + "parameterTypes": [] + }, + { + "name": "isNonEmptyOfNormal", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Compare" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Func" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Join" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Nested" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.query.Query" + }, + { + "type": "com.baomidou.mybatisplus.core.conditions.query.QueryWrapper", + "methods": [ + { + "name": "getSqlSelect", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.mapper.BaseMapper", + "methods": [ + { + "name": "selectOne", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.conditions.Wrapper" + ] + }, + { + "name": "selectOne", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.conditions.Wrapper", + "boolean" + ] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.mapper.Mapper" + }, + { + "type": "com.baomidou.mybatisplus.core.override.MybatisMapperProxy", + "methods": [ + { + "name": "getMapperInterface", + "parameterTypes": [] + }, + { + "name": "getSqlSession", + "parameterTypes": [] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.core.toolkit.Wrappers$EmptyWrapper" + }, + { + "type": "com.baomidou.mybatisplus.extension.ddl.IDdl" + }, + { + "type": "com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor" + }, + { + "type": "com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor" + }, + { + "type": "com.baomidou.mybatisplus.extension.repository.AbstractRepository", + "methods": [ + { + "name": "getOne", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.conditions.Wrapper", + "boolean" + ] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.extension.repository.CrudRepository", + "fields": [ + { + "name": "baseMapper" + } + ] + }, + { + "type": "com.baomidou.mybatisplus.extension.repository.IRepository", + "methods": [ + { + "name": "count", + "parameterTypes": [] + }, + { + "name": "count", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.conditions.Wrapper" + ] + }, + { + "name": "getById", + "parameterTypes": [ + "java.io.Serializable" + ] + }, + { + "name": "list", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.conditions.Wrapper" + ] + }, + { + "name": "save", + "parameterTypes": [ + "java.lang.Object" + ] + } + ] + }, + { + "type": "com.baomidou.mybatisplus.extension.service.IService" + }, + { + "type": "com.baomidou.mybatisplus.extension.service.impl.ServiceImpl" + }, + { + "type": "com.baomidou.mybatisplus.extension.spi.SpringCompatibleSet" + }, + { + "type": "com.baomidou.mybatisplus.extension.spring.MybatisPlusApplicationContextAware" + }, + { + "type": "com.fasterxml.jackson.core.JsonGenerator" + }, + { + "type": "com.fasterxml.jackson.core.ObjectCodec" + }, + { + "type": "com.fasterxml.jackson.core.TreeCodec" + }, + { + "type": "com.fasterxml.jackson.core.Versioned" + }, + { + "type": "com.fasterxml.jackson.databind.ObjectMapper" + }, + { + "type": "com.fasterxml.jackson.databind.ext.Java7SupportImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.fasterxml.jackson.dataformat.cbor.CBORFactory" + }, + { + "type": "com.fasterxml.jackson.dataformat.smile.SmileFactory" + }, + { + "type": "com.fasterxml.jackson.dataformat.xml.XmlMapper" + }, + { + "type": "com.fasterxml.jackson.dataformat.yaml.YAMLFactory" + }, + { + "type": "com.github.benmanes.caffeine.cache.BLCHeader$DrainStatusRef", + "fields": [ + { + "name": "drainStatus" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueColdProducerFields", + "fields": [ + { + "name": "producerLimit" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueConsumerFields", + "fields": [ + { + "name": "consumerIndex" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.BaseMpscLinkedArrayQueueProducerFields", + "fields": [ + { + "name": "producerIndex" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.BoundedLocalCache", + "fields": [ + { + "name": "refreshes" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.PS", + "fields": [ + { + "name": "key" + }, + { + "name": "value" + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.PSW", + "fields": [ + { + "name": "writeTime" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.github.benmanes.caffeine.cache.SSW", + "fields": [ + { + "name": "FACTORY" + }, + { + "name": "expiresAfterWriteNanos" + } + ] + }, + { + "type": "com.google.gson.Gson" + }, + { + "type": "com.rometools.rome.feed.WireFeed" + }, + { + "type": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ARCFOURCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DESedeCipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.DHParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.GaloisCounterMode$AESGCM", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsKeyMaterialGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.crypto.provider.TlsPrfGenerator$V12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "com.zaxxer.hikari.HikariConfig" + }, + { + "type": "com.zaxxer.hikari.HikariConfigMXBean" + }, + { + "type": "com.zaxxer.hikari.HikariDataSource" + }, + { + "type": "com.zaxxer.hikari.pool.PoolBase" + }, + { + "type": "com.zaxxer.hikari.pool.PoolEntry" + }, + { + "type": "dev.qingzhou.pushserver.PushServerApplication", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "main", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.aspect.SecurityInterceptor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.config.PushProperties" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.common.PortalResponse", + "methods": [ + { + "name": "getData", + "parameterTypes": [] + }, + { + "name": "getMessage", + "parameterTypes": [] + }, + { + "name": "isSuccess", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.JsonDtoPackageHints", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.JsonDtoPackageHints$DtoHints" + }, + { + "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "myBatisBeanFactoryInitializationAotProcessor", + "parameterTypes": [] + }, + { + "name": "myBatisMapperFactoryBeanPostProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBaitsRuntimeHintsRegistrar" + }, + { + "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBatisBeanFactoryInitializationAotProcessor" + }, + { + "type": "dev.qingzhou.pushserver.config.MyBatisNativeConfiguration$MyBatisMapperFactoryBeanPostProcessor" + }, + { + "type": "dev.qingzhou.pushserver.config.PortalDataSourceProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig", + "methods": [ + { + "name": "dataSource", + "parameterTypes": [ + "dev.qingzhou.pushserver.config.PortalDataSourceProperties" + ] + }, + { + "name": "mybatisPlusInterceptor", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$FastClass$$0", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalDatabaseConfig$$SpringCGLIB$$FastClass$$1", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig", + "methods": [ + { + "name": "objectMapper", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$FastClass$$0", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalJacksonConfig$$SpringCGLIB$$FastClass$$1", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalMybatisConfig" + }, + { + "type": "dev.qingzhou.pushserver.config.PortalMybatisConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalSchemaInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [ + "javax.sql.DataSource" + ] + }, + { + "name": "initialize", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig", + "methods": [ + { + "name": "authenticationManager", + "parameterTypes": [ + "dev.qingzhou.pushserver.security.PortalUserDetailsService", + "org.springframework.security.crypto.password.PasswordEncoder" + ] + }, + { + "name": "passwordEncoder", + "parameterTypes": [] + }, + { + "name": "portalJsonLoginAuthenticationFilter", + "parameterTypes": [ + "com.fasterxml.jackson.databind.ObjectMapper", + "dev.qingzhou.pushserver.security.CaptchaService", + "org.springframework.security.authentication.AuthenticationManager", + "org.springframework.security.web.authentication.session.SessionAuthenticationStrategy" + ] + }, + { + "name": "securityFilterChain", + "parameterTypes": [ + "org.springframework.security.config.annotation.web.builders.HttpSecurity", + "dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter" + ] + }, + { + "name": "sessionAuthenticationStrategy", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$FastClass$$0", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalSecurityConfig$$SpringCGLIB$$FastClass$$1", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PortalWecomProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PushConfiguration" + }, + { + "type": "dev.qingzhou.pushserver.config.PushConfiguration$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.PushProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.WebConfig" + }, + { + "type": "dev.qingzhou.pushserver.config.WebConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.aspect.SecurityInterceptor" + ] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.CaptchaController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.SystemConfigService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.DashboardController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.DashboardService" + ] + }, + { + "name": "charts", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "recentLogs", + "parameterTypes": [ + "int", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "stats", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PageController", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "redirect", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalAppController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalWecomAppService", + "dev.qingzhou.pushserver.service.PortalAppApiKeyService" + ] + }, + { + "name": "create", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "createApiKey", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "getApiKey", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "list", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "sync", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "updateApiKey", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalAuthController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalUserService" + ] + }, + { + "name": "csrf", + "parameterTypes": [ + "org.springframework.security.web.csrf.CsrfToken" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalCorpController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalCorpConfigService" + ] + }, + { + "name": "getCorp", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalErrorController", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalInitController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalUserService", + "dev.qingzhou.pushserver.service.SystemConfigService" + ] + }, + { + "name": "getInitStatus", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalMeController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalUserService" + ] + }, + { + "name": "me", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalMessageController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalMessageService", + "dev.qingzhou.pushserver.service.PortalMessageLogService" + ] + }, + { + "name": "logs", + "parameterTypes": [ + "java.lang.Integer", + "java.lang.Integer", + "int", + "java.lang.Boolean", + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "send", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalProxyController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalProxyConfigService" + ] + }, + { + "name": "getProxy", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", + "jakarta.servlet.http.HttpSession" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalSystemController", + "fields": [ + { + "name": "appVersion" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.SystemConfigService" + ] + }, + { + "name": "getIgnoreVersion", + "parameterTypes": [] + }, + { + "name": "getVersion", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PushController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PushService", + "dev.qingzhou.pushserver.config.PushProperties" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.openapi.OpenApiMessageController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalAppApiKeyService", + "dev.qingzhou.pushserver.service.PortalMessageService" + ] + }, + { + "name": "send", + "parameterTypes": [ + "java.lang.String", + "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.wecom.WecomCallbackController", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalWecomAppService", + "dev.qingzhou.pushserver.service.PortalCorpConfigService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.exception.GlobalExceptionHandler", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.exception.PortalExceptionHandler", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAvatarUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setName", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.config.PortalWecomProperties" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload", + "methods": [ + { + "name": "getAgentid", + "parameterTypes": [] + }, + { + "name": "getContent", + "parameterTypes": [] + }, + { + "name": "getCreateTime", + "parameterTypes": [] + }, + { + "name": "getDuplicateCheckInterval", + "parameterTypes": [] + }, + { + "name": "getEnableDuplicateCheck", + "parameterTypes": [] + }, + { + "name": "getEnableIdTrans", + "parameterTypes": [] + }, + { + "name": "getEvent", + "parameterTypes": [] + }, + { + "name": "getEventKey", + "parameterTypes": [] + }, + { + "name": "getFromUserName", + "parameterTypes": [] + }, + { + "name": "getMarkdown", + "parameterTypes": [] + }, + { + "name": "getMediaId", + "parameterTypes": [] + }, + { + "name": "getMsgId", + "parameterTypes": [] + }, + { + "name": "getMsgtype", + "parameterTypes": [] + }, + { + "name": "getNews", + "parameterTypes": [] + }, + { + "name": "getPicUrl", + "parameterTypes": [] + }, + { + "name": "getReceiveAgentId", + "parameterTypes": [] + }, + { + "name": "getReceiveMsgType", + "parameterTypes": [] + }, + { + "name": "getSafe", + "parameterTypes": [] + }, + { + "name": "getText", + "parameterTypes": [] + }, + { + "name": "getTextcard", + "parameterTypes": [] + }, + { + "name": "getToUserName", + "parameterTypes": [] + }, + { + "name": "getToparty", + "parameterTypes": [] + }, + { + "name": "getTotag", + "parameterTypes": [] + }, + { + "name": "getTouser", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Article", + "methods": [ + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getPicUrl", + "parameterTypes": [] + }, + { + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getUrl", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Markdown", + "methods": [ + { + "name": "getContent", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$News", + "methods": [ + { + "name": "getArticles", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Text", + "methods": [ + { + "name": "getContent", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$TextCard", + "methods": [ + { + "name": "getBtnText", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getUrl", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomResponse", + "methods": [ + { + "name": "getErrcode", + "parameterTypes": [] + }, + { + "name": "getErrmsg", + "parameterTypes": [] + }, + { + "name": "isSuccess", + "parameterTypes": [] + }, + { + "name": "setErrcode", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setErrmsg", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomSendResponse", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getInvalidParty", + "parameterTypes": [] + }, + { + "name": "getInvalidTag", + "parameterTypes": [] + }, + { + "name": "getInvalidUser", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomToken", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAccessToken", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setExpiresIn", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" + }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" + }, + { + "type": "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setContent", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setMsgType", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" + ] + }, + { + "name": "setToAll", + "parameterTypes": [ + "java.lang.Boolean" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", + "fields": [ + { + "name": "rateLimitPerMinute" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setRateLimitPerMinute", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", + "fields": [ + { + "name": "agentId" + }, + { + "name": "secret" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAgentId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSecret", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", + "fields": [ + { + "name": "corpId" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setCorpId", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAccount", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCaptcha", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", + "fields": [ + { + "name": "appId" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setArticles", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "setContent", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setMsgType", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" + ] + }, + { + "name": "setTitle", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setToAll", + "parameterTypes": [ + "java.lang.Boolean" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest$PortalNewsArticle", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPicUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setTitle", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", + "fields": [ + { + "name": "host" + }, + { + "name": "port" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setActive", + "parameterTypes": [ + "java.lang.Boolean" + ] + }, + { + "name": "setExitIp", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setHost", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPort", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setType", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUsername", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getApiKeyHash", + "parameterTypes": [] + }, + { + "name": "getApiKeyPlain", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getRateLimitPerMinute", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "setApiKeyHash", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setApiKeyPlain", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setRateLimitPerMinute", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getCorpId", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setCorpId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getContent", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getErrorMessage", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getMsgType", + "parameterTypes": [] + }, + { + "name": "getRequestJson", + "parameterTypes": [] + }, + { + "name": "getResponseJson", + "parameterTypes": [] + }, + { + "name": "getSuccess", + "parameterTypes": [] + }, + { + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getToAll", + "parameterTypes": [] + }, + { + "name": "getToParty", + "parameterTypes": [] + }, + { + "name": "getToUser", + "parameterTypes": [] + }, + { + "name": "getUrl", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setAgentId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setContent", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setMsgType", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setRequestJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setResponseJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSuccess", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setTitle", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setToAll", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getActive", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getExitIp", + "parameterTypes": [] + }, + { + "name": "getHost", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getPassword", + "parameterTypes": [] + }, + { + "name": "getPort", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "getUsername", + "parameterTypes": [] + }, + { + "name": "setActive", + "parameterTypes": [ + "java.lang.Boolean" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setExitIp", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setHost", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPort", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setType", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUsername", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setConfigKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setConfigValue", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalUser", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAccount", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setPasswordHash", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAvatarUrl", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getEncodingAesKey", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getSecret", + "parameterTypes": [] + }, + { + "name": "getToken", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setAgentId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAvatarUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setName", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSecret", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse", + "methods": [ + { + "name": "getDistribution", + "parameterTypes": [] + }, + { + "name": "getTrend", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse$TrendPoint", + "methods": [ + { + "name": "getCount", + "parameterTypes": [] + }, + { + "name": "getDate", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse", + "methods": [ + { + "name": "getActiveApps", + "parameterTypes": [] + }, + { + "name": "getLastErrorTime", + "parameterTypes": [] + }, + { + "name": "getSuccessRate", + "parameterTypes": [] + }, + { + "name": "getTodayTotal", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppApiKeyResponse", + "methods": [ + { + "name": "getApiKey", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getRateLimitPerMinute", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "isHasKey", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppResponse", + "methods": [ + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAvatarUrl", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse", + "methods": [ + { + "name": "getCorpId", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse", + "methods": [ + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getContent", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getErrorMessage", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getMsgType", + "parameterTypes": [] + }, + { + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getToParty", + "parameterTypes": [] + }, + { + "name": "getToUser", + "parameterTypes": [] + }, + { + "name": "getUrl", + "parameterTypes": [] + }, + { + "name": "isSuccess", + "parameterTypes": [] + }, + { + "name": "isToAll", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse", + "methods": [ + { + "name": "getPage", + "parameterTypes": [] + }, + { + "name": "getPageSize", + "parameterTypes": [] + }, + { + "name": "getRecords", + "parameterTypes": [] + }, + { + "name": "getTotal", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse", + "methods": [ + { + "name": "getActive", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getExitIp", + "parameterTypes": [] + }, + { + "name": "getHost", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getPassword", + "parameterTypes": [] + }, + { + "name": "getPort", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUsername", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse", + "methods": [ + { + "name": "getAccount", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.security.CaptchaService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.SystemConfigService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.security.PortalJsonLoginAuthenticationFilter" + }, + { + "type": "dev.qingzhou.pushserver.security.PortalUserDetailsService", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalUserService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.DashboardService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalAccessTokenService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalAppApiKeyService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalCorpConfigService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalMessageLogService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalMessageService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalProxyConfigService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalUserService" + }, + { + "type": "dev.qingzhou.pushserver.service.PortalWecomAppService" + }, + { + "type": "dev.qingzhou.pushserver.service.PushService" + }, + { + "type": "dev.qingzhou.pushserver.service.SystemConfigService" + }, + { + "type": "dev.qingzhou.pushserver.service.impl.DashboardServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalMessageLogService", + "dev.qingzhou.pushserver.service.PortalWecomAppService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalAccessTokenServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalWecomAppService", + "dev.qingzhou.pushserver.security.PortalAppApiKeyRateLimiter" + ] + }, + { + "name": "findByAppId", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "requireAppByApiKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "rotateKey", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "updateRateLimit", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "java.lang.Integer" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalCorpConfigServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "requireByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalCorpConfigServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalMessageLogServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "pageLogs", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "java.lang.Boolean", + "int", + "int" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalMessageLogServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalMessageServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PortalWecomAppService", + "dev.qingzhou.pushserver.service.PortalCorpConfigService", + "dev.qingzhou.pushserver.service.PortalAccessTokenService", + "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", + "dev.qingzhou.pushserver.service.PortalMessageLogService", + "com.fasterxml.jackson.databind.ObjectMapper", + "dev.qingzhou.pushserver.service.PortalProxyConfigService" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" + ] + }, + { + "name": "getByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalUserServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.security.crypto.password.PasswordEncoder" + ] + }, + { + "name": "findByAccount", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalUserServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalWecomAppServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", + "dev.qingzhou.pushserver.service.PortalAccessTokenService", + "dev.qingzhou.pushserver.service.PortalCorpConfigService", + "dev.qingzhou.pushserver.service.PortalProxyConfigService" + ] + }, + { + "name": "addApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String", + "java.lang.String" + ] + }, + { + "name": "listByUser", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "requireByUser", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "syncApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalWecomAppServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PushServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.config.PushProperties" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.SystemConfigServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" + ] + }, + { + "name": "get", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + }, + { + "name": "isTurnstileEnabled", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.SystemConfigServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "groovy.lang.MetaClass" + }, + { + "type": "int[]" + }, + { + "type": "io.micrometer.context.ContextSnapshot" + }, + { + "type": "io.micrometer.core.instrument.MeterRegistry" + }, + { + "type": "io.micrometer.core.instrument.binder.tomcat.TomcatMetrics" + }, + { + "type": "io.micrometer.observation.Observation" + }, + { + "type": "io.micrometer.observation.ObservationRegistry" + }, + { + "type": "io.r2dbc.spi.ConnectionFactory" + }, + { + "type": "io.reactivex.rxjava3.core.Flowable" + }, + { + "type": "io.smallrye.mutiny.Multi" + }, + { + "type": "io.vavr.control.Try" + }, + { + "type": "jakarta.annotation.PostConstruct" + }, + { + "type": "jakarta.annotation.PreDestroy" + }, + { + "type": "jakarta.annotation.Resource" + }, + { + "type": "jakarta.ejb.EJB" + }, + { + "type": "jakarta.ejb.TransactionAttribute" + }, + { + "type": "jakarta.faces.context.FacesContext" + }, + { + "type": "jakarta.inject.Inject" + }, + { + "type": "jakarta.inject.Named" + }, + { + "type": "jakarta.inject.Provider" + }, + { + "type": "jakarta.inject.Qualifier" + }, + { + "type": "jakarta.json.bind.Jsonb" + }, + { + "type": "jakarta.persistence.EntityManagerFactory" + }, + { + "type": "jakarta.persistence.Persistence" + }, + { + "type": "jakarta.persistence.PersistenceContext" + }, + { + "type": "jakarta.servlet.Filter" + }, + { + "type": "jakarta.servlet.GenericFilter" + }, + { + "type": "jakarta.servlet.GenericServlet" + }, + { + "type": "jakarta.servlet.MultipartConfigElement" + }, + { + "type": "jakarta.servlet.Servlet" + }, + { + "type": "jakarta.servlet.ServletConfig" + }, + { + "type": "jakarta.servlet.ServletContext" + }, + { + "type": "jakarta.servlet.ServletRegistration" + }, + { + "type": "jakarta.servlet.ServletRequest" + }, + { + "type": "jakarta.servlet.http.HttpServlet" + }, + { + "type": "jakarta.servlet.jsp.jstl.core.Config" + }, + { + "type": "jakarta.transaction.Transaction" + }, + { + "type": "jakarta.transaction.TransactionManager" + }, + { + "type": "jakarta.transaction.Transactional" + }, + { + "type": "jakarta.validation.ConstraintValidator" + }, + { + "type": "jakarta.validation.Valid" + }, + { + "type": "jakarta.validation.Validator" + }, + { + "type": "jakarta.validation.ValidatorFactory" + }, + { + "type": "jakarta.validation.bootstrap.GenericBootstrap" + }, + { + "type": "jakarta.validation.constraints.Max", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + }, + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.Min", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + }, + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.NotBlank", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.NotNull", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.executable.ExecutableValidator" + }, + { + "type": "jakarta.xml.bind.Binder" + }, + { + "type": "jakarta.xml.ws.WebServiceRef" + }, + { + "type": "java.beans.Introspector" + }, + { + "type": "java.beans.PropertyVetoException" + }, + { + "type": "java.io.Closeable" + }, + { + "type": "java.io.Serializable" + }, + { + "type": "java.io.Serializable[]" + }, + { + "type": "java.lang.AutoCloseable" + }, + { + "type": "java.lang.Boolean", + "jniAccessible": true, + "methods": [ + { + "name": "getBoolean", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "java.lang.CharSequence[]" + }, + { + "type": "java.lang.Class", + "methods": [ + { + "name": "getModule", + "parameterTypes": [] + }, + { + "name": "isRecord", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.ClassLoader", + "fields": [ + { + "name": "classLoaderValueMap" + } + ] + }, + { + "type": "java.lang.Class[]" + }, + { + "type": "java.lang.CloneNotSupportedException" + }, + { + "type": "java.lang.Comparable" + }, + { + "type": "java.lang.Comparable[]" + }, + { + "type": "java.lang.Error" + }, + { + "type": "java.lang.Long" + }, + { + "type": "java.lang.Module", + "methods": [ + { + "name": "isNamed", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Number" + }, + { + "type": "java.lang.Object", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Object[]" + }, + { + "type": "java.lang.RuntimeException" + }, + { + "type": "java.lang.String" + }, + { + "type": "java.lang.String[]" + }, + { + "type": "java.lang.System" + }, + { + "type": "java.lang.Thread" + }, + { + "type": "java.lang.Thread$Builder" + }, + { + "type": "java.lang.Throwable", + "jniAccessible": true, + "methods": [ + { + "name": "toString", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.WrongThreadException" + }, + { + "type": "java.lang.annotation.Annotation[]" + }, + { + "type": "java.lang.annotation.Documented" + }, + { + "type": "java.lang.annotation.Inherited" + }, + { + "type": "java.lang.annotation.Repeatable" + }, + { + "type": "java.lang.annotation.Retention" + }, + { + "type": "java.lang.annotation.Target" + }, + { + "type": "java.lang.constant.Constable" + }, + { + "type": "java.lang.constant.Constable[]" + }, + { + "type": "java.lang.constant.ConstantDesc" + }, + { + "type": "java.lang.constant.ConstantDesc[]" + }, + { + "type": "java.lang.invoke.MethodHandles", + "methods": [ + { + "name": "privateLookupIn", + "parameterTypes": [ + "java.lang.Class", + "java.lang.invoke.MethodHandles$Lookup" + ] + } + ] + }, + { + "type": "java.lang.invoke.TypeDescriptor$OfField" + }, + { + "type": "java.lang.reflect.AccessibleObject" + }, + { + "type": "java.lang.reflect.AnnotatedElement" + }, + { + "type": "java.lang.reflect.GenericDeclaration" + }, + { + "type": "java.lang.reflect.InvocationHandler" + }, + { + "type": "java.lang.reflect.ParameterizedType", + "methods": [ + { + "name": "getActualTypeArguments", + "parameterTypes": [] + }, + { + "name": "getRawType", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.reflect.Type" + }, + { + "type": "java.lang.reflect.TypeVariable", + "methods": [ + { + "name": "getBounds", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.reflect.UndeclaredThrowableException" + }, + { + "type": "java.lang.reflect.WildcardType", + "methods": [ + { + "name": "getLowerBounds", + "parameterTypes": [] + }, + { + "name": "getUpperBounds", + "parameterTypes": [] + } + ] + }, + { + "type": "java.net.http.HttpClient" + }, + { + "type": "java.security.AlgorithmParametersSpi" + }, + { + "type": "java.security.KeyStoreSpi" + }, + { + "type": "java.security.interfaces.RSAPrivateKey" + }, + { + "type": "java.security.interfaces.RSAPublicKey" + }, + { + "type": "java.sql.Date" + }, + { + "type": "java.sql.Driver" + }, + { + "type": "java.sql.DriverManager" + }, + { + "type": "java.sql.SQLException" + }, + { + "type": "java.sql.Statement[]" + }, + { + "type": "java.sql.Timestamp" + }, + { + "type": "java.sql.Wrapper" + }, + { + "type": "java.text.ListFormat" + }, + { + "type": "java.util.AbstractMap" + }, + { + "type": "java.util.ArrayList", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "java.util.Enumeration" + }, + { + "type": "java.util.EventListener" + }, + { + "type": "java.util.HashSet" + }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableMap" + }, + { + "type": "java.util.ImmutableCollections$Map1" + }, + { + "type": "java.util.ImmutableCollections$MapN" + }, + { + "type": "java.util.List" + }, + { + "type": "java.util.Map" + }, + { + "type": "java.util.Map$Entry[]" + }, + { + "type": "java.util.MapBeanInfo" + }, + { + "type": "java.util.MapCustomizer" + }, + { + "type": "java.util.SequencedCollection" + }, + { + "type": "java.util.Set" + }, + { + "type": "java.util.concurrent.Callable" + }, + { + "type": "java.util.concurrent.Executor" + }, + { + "type": "java.util.concurrent.ThreadFactory" + }, + { + "type": "java.util.logging.LogManager" + }, + { + "type": "java.util.logging.SimpleFormatter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "javafx.beans.value.ObservableValue" + }, + { + "type": "javax.money.MonetaryAmount" + }, + { + "type": "javax.naming.InitialContext" + }, + { + "type": "javax.net.ssl.SSLParameters" + }, + { + "type": "javax.security.auth.Subject" + }, + { + "type": "javax.sql.CommonDataSource" + }, + { + "type": "javax.sql.DataSource" + }, + { + "type": "javax.sql.XADataSource" + }, + { + "type": "jdk.crac.management.CRaCMXBean" + }, + { + "type": "jdk.internal.loader.ClassLoaders$AppClassLoader" + }, + { + "type": "jdk.internal.loader.ClassLoaders$PlatformClassLoader" + }, + { + "type": "jdk.internal.misc.Unsafe" + }, + { + "type": "kotlin.Metadata" + }, + { + "type": "kotlin.reflect.full.KClasses" + }, + { + "type": "kotlinx.coroutines.reactor.MonoKt" + }, + { + "type": "kotlinx.serialization.Serializable" + }, + { + "type": "kotlinx.serialization.cbor.Cbor" + }, + { + "type": "kotlinx.serialization.json.Json" + }, + { + "type": "kotlinx.serialization.protobuf.ProtoBuf" + }, + { + "type": "oracle.jdbc.OracleConnection" + }, + { + "type": "oracle.ucp.jdbc.PoolDataSource" + }, + { + "type": "oracle.ucp.jdbc.PoolDataSourceImpl" + }, + { + "type": "org.aopalliance.intercept.MethodInterceptor" + }, + { + "type": "org.apache.catalina.core.ApplicationContextFacade" + }, + { + "type": "org.apache.catalina.loader.JdbcLeakPrevention", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "clearJdbcDriverRegistrations", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.catalina.startup.Tomcat" + }, + { + "type": "org.apache.catalina.util.CharsetMapper", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.commons.dbcp2.BasicDataSource" + }, + { + "type": "org.apache.commons.logging.LogFactory" + }, + { + "type": "org.apache.commons.logging.impl.Slf4jLogFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.commons.logging.impl.WeakHashtable", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.coyote.AbstractProtocol", + "methods": [ + { + "name": "getAddress", + "parameterTypes": [] + }, + { + "name": "getLocalPort", + "parameterTypes": [] + }, + { + "name": "getProperty", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPort", + "parameterTypes": [ + "int" + ] + }, + { + "name": "setProperty", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + } + ] + }, + { + "type": "org.apache.coyote.UpgradeProtocol" + }, + { + "type": "org.apache.coyote.http11.AbstractHttp11Protocol", + "methods": [ + { + "name": "isSSLEnabled", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.coyote.http11.Http11NioProtocol" + }, + { + "type": "org.apache.derby.jdbc.EmbeddedDriver" + }, + { + "type": "org.apache.el.ExpressionFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.hc.client5.http.classic.HttpClient" + }, + { + "type": "org.apache.ibatis.annotations.Mapper" + }, + { + "type": "org.apache.ibatis.executor.Executor", + "methods": [ + { + "name": "close", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "commit", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "update", + "parameterTypes": [ + "org.apache.ibatis.mapping.MappedStatement", + "java.lang.Object" + ] + } + ] + }, + { + "type": "org.apache.ibatis.executor.statement.StatementHandler", + "methods": [ + { + "name": "parameterize", + "parameterTypes": [ + "java.sql.Statement" + ] + }, + { + "name": "prepare", + "parameterTypes": [ + "java.sql.Connection", + "java.lang.Integer" + ] + }, + { + "name": "query", + "parameterTypes": [ + "java.sql.Statement", + "org.apache.ibatis.session.ResultHandler" + ] + }, + { + "name": "update", + "parameterTypes": [ + "java.sql.Statement" + ] + } + ] + }, + { + "type": "org.apache.ibatis.javassist.ClassPool" + }, + { + "type": "org.apache.ibatis.javassist.util.proxy.ProxyFactory" + }, + { + "type": "org.apache.ibatis.logging.slf4j.Slf4jImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.apache.ibatis.ognl.OgnlRuntime$ClassPropertyMethodCache" + }, + { + "type": "org.apache.ibatis.plugin.Interceptor" + }, + { + "type": "org.apache.ibatis.plugin.Interceptor[]" + }, + { + "type": "org.apache.ibatis.plugin.Intercepts" + }, + { + "type": "org.apache.ibatis.plugin.Signature" + }, + { + "type": "org.apache.ibatis.reflection.SystemMetaObject$NullObject" + }, + { + "type": "org.apache.ibatis.scripting.LanguageDriver" + }, + { + "type": "org.apache.ibatis.scripting.defaults.RawLanguageDriver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.ibatis.scripting.xmltags.XMLLanguageDriver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.apache.ibatis.session.SqlSession", + "methods": [ + { + "name": "insert", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] + }, + { + "name": "selectList", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] + }, + { + "name": "selectOne", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] + }, + { + "name": "update", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] + } + ] + }, + { + "type": "org.apache.ibatis.session.SqlSessionFactory" + }, + { + "type": "org.apache.ibatis.session.defaults.DefaultSqlSessionFactory" + }, + { + "type": "org.apache.jasper.compiler.JspConfig" + }, + { + "type": "org.apache.jasper.servlet.JspServlet" + }, + { + "type": "org.apache.logging.log4j.core.impl.Log4jContextFactory" + }, + { + "type": "org.apache.logging.log4j.util.EnvironmentPropertySource" + }, + { + "type": "org.apache.logging.log4j.util.SystemPropertiesPropertySource" + }, + { + "type": "org.apache.logging.slf4j.SLF4JProvider" + }, + { + "type": "org.apache.tomcat.jdbc.pool.DataSource" + }, + { + "type": "org.apache.tomcat.util.net.AbstractEndpoint", + "methods": [ + { + "name": "setBindOnInit", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.apache.tomcat.util.net.NioEndpoint" + }, + { + "type": "org.apache.tomcat.websocket.server.WsFilter" + }, + { + "type": "org.apache.tomcat.websocket.server.WsSci" + }, + { + "type": "org.aspectj.weaver.Advice" + }, + { + "type": "org.crac.Core" + }, + { + "type": "org.crac.Resource" + }, + { + "type": "org.eclipse.core.runtime.FileLocator" + }, + { + "type": "org.eclipse.jetty.client.HttpClient" + }, + { + "type": "org.graalvm.nativeimage.ImageInfo", + "methods": [ + { + "name": "inImageCode", + "parameterTypes": [] + } + ] + }, + { + "type": "org.h2.Driver" + }, + { + "type": "org.hibernate.validator.HibernateValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMaxValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMinValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MaxValidatorForInteger", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MinValidatorForInteger", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.engine.AbstractConfigurationImpl", + "methods": [ + { + "name": "externalClassLoader", + "parameterTypes": [ + "java.lang.ClassLoader" + ] + } + ] + }, + { + "type": "org.hibernate.validator.internal.engine.ConfigurationImpl" + }, + { + "type": "org.hibernate.validator.internal.util.logging.Log_$logger", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.jboss.logging.Logger" + ] + } + ] + }, + { + "type": "org.hibernate.validator.internal.util.logging.Log_$logger_zh" + }, + { + "type": "org.hibernate.validator.internal.util.logging.Log_$logger_zh_CN" + }, + { + "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle", + "fields": [ + { + "name": "INSTANCE" + } + ] + }, + { + "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle_zh" + }, + { + "type": "org.hibernate.validator.internal.util.logging.Messages_$bundle_zh_CN" + }, + { + "type": "org.hsqldb.jdbc.JDBCDriver" + }, + { + "type": "org.jboss.logging.Logger" + }, + { + "type": "org.joda.time.ReadableInstant" + }, + { + "type": "org.jspecify.annotations.NullMarked" + }, + { + "type": "org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver" + }, + { + "type": "org.mybatis.scripting.freemarker.FreeMarkerLanguageDriverConfig" + }, + { + "type": "org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver" + }, + { + "type": "org.mybatis.scripting.velocity.Driver" + }, + { + "type": "org.mybatis.scripting.velocity.VelocityLanguageDriver" + }, + { + "type": "org.mybatis.scripting.velocity.VelocityLanguageDriverConfig" + }, + { + "type": "org.mybatis.spring.SqlSessionFactoryBean" + }, + { + "type": "org.mybatis.spring.SqlSessionTemplate" + }, + { + "type": "org.mybatis.spring.annotation.MapperScan" + }, + { + "type": "org.mybatis.spring.annotation.MapperScannerRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.mybatis.spring.mapper.MapperFactoryBean", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Class" + ] + }, + { + "name": "setAddToConfig", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setMapperInterface", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "org.mybatis.spring.mapper.MapperScannerConfigurer", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setBasePackage", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setProcessPropertyPlaceHolders", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setSqlSessionFactoryBeanName", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.mybatis.spring.support.SqlSessionDaoSupport", + "methods": [ + { + "name": "setSqlSessionFactory", + "parameterTypes": [ + "org.apache.ibatis.session.SqlSessionFactory" + ] + } + ] + }, + { + "type": "org.osgi.framework.FrameworkUtil" + }, + { + "type": "org.reactivestreams.Publisher" + }, + { + "type": "org.slf4j.bridge.SLF4JBridgeHandler" + }, + { + "type": "org.slf4j.helpers.Log4jLoggerFactory" + }, + { + "type": "org.slf4j.spi.LocationAwareLogger" + }, + { + "type": "org.springframework.aop.PointcutAdvisor" + }, + { + "type": "org.springframework.aop.SpringProxy" + }, + { + "type": "org.springframework.aop.TargetClassAware" + }, + { + "type": "org.springframework.aop.framework.AbstractAdvisingBeanPostProcessor" + }, + { + "type": "org.springframework.aop.framework.Advised" + }, + { + "type": "org.springframework.aop.framework.AopConfigException" + }, + { + "type": "org.springframework.aop.framework.AopInfrastructureBean" + }, + { + "type": "org.springframework.aop.framework.AopProxyUtils" + }, + { + "type": "org.springframework.aop.framework.ProxyConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setProxyTargetClass", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.springframework.aop.framework.ProxyProcessorSupport", + "methods": [ + { + "name": "setOrder", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator" + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator" + }, + { + "type": "org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor" + }, + { + "type": "org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.aop.scope.ScopedObject" + }, + { + "type": "org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor" + }, + { + "type": "org.springframework.aop.support.AbstractPointcutAdvisor" + }, + { + "type": "org.springframework.aot.hint.annotation.Reflective" + }, + { + "type": "org.springframework.beans.factory.Aware" + }, + { + "type": "org.springframework.beans.factory.BeanClassLoaderAware" + }, + { + "type": "org.springframework.beans.factory.BeanFactoryAware" + }, + { + "type": "org.springframework.beans.factory.BeanNameAware" + }, + { + "type": "org.springframework.beans.factory.DisposableBean" + }, + { + "type": "org.springframework.beans.factory.FactoryBean" + }, + { + "type": "org.springframework.beans.factory.InitializingBean" + }, + { + "type": "org.springframework.beans.factory.SmartInitializingSingleton" + }, + { + "type": "org.springframework.beans.factory.annotation.Autowired" + }, + { + "type": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.beans.factory.annotation.Qualifier" + }, + { + "type": "org.springframework.beans.factory.annotation.Value" + }, + { + "type": "org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.aot.BeanRegistrationAotProcessor" + }, + { + "type": "org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter" + }, + { + "type": "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.BeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor" + }, + { + "type": "org.springframework.beans.factory.support.NullBean" + }, + { + "type": "org.springframework.boot.ApplicationProperties", + "methods": [ + { + "name": "setAllowBeanDefinitionOverriding", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "org.springframework.boot.ClearCachesApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.SpringBootConfiguration" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties" + }, + { + "type": "org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextFactory" + }, + { + "type": "org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository" + }, + { + "type": "org.springframework.boot.ansi.AnsiOutput$Enabled" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackage" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureAfter" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureBefore" + }, + { + "type": "org.springframework.boot.autoconfigure.AutoConfigureOrder" + }, + { + "type": "org.springframework.boot.autoconfigure.EnableAutoConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.SpringBootApplication" + }, + { + "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "forceAutoProxyCreatorToUseClassProxying", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationAvailability", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnCheckpointRestore" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnClass" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingFilterBean" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnNotWarDeployment" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnProperty" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnThreading" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type" + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnBeanCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnClassCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnPropertyCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnResourceCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnThreadingCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnWarDeploymentCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.condition.SearchStrategy" + }, + { + "type": "org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "defaultLifecycleProcessor", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.context.LifecycleProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.LifecycleProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration$ResourceBundleCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "propertySourcesPlaceholderConfigurer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.info.ProjectInfoProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration$GitResourceAvailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.info.ProjectInfoProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.CharsetsBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.preinitialize.ConversionServiceBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.FileWatcher" + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader", + "org.springframework.boot.autoconfigure.ssl.SslProperties" + ] + }, + { + "name": "fileWatcher", + "parameterTypes": [] + }, + { + "name": "sslBundleRegistry", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "sslPropertiesSslBundleRegistrar", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.ssl.FileWatcher" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar" + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.ssl.SslPropertiesBundleRegistrar" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ApplicationTaskExecutorAsyncConfigurer" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationTaskExecutorAsyncConfigurer", + "parameterTypes": [ + "org.springframework.beans.factory.BeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "bootstrapExecutorAliasPostProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$OnExecutorCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "simpleAsyncTaskExecutorBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "applicationTaskExecutor", + "parameterTypes": [ + "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "threadPoolTaskExecutorBuilder", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskExecutionProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "simpleAsyncTaskSchedulerBuilder", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration" + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "threadPoolTaskSchedulerBuilder", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain" + }, + { + "type": "org.springframework.boot.autoconfigure.web.OnEnabledResourceChainCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.WebProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getResources", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.WebProperties$Resources", + "methods": [ + { + "name": "setStaticLocations", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.autoconfigure.web.WebResourcesRuntimeHints" + }, + { + "type": "org.springframework.boot.autoconfigure.web.format.WebConversionService" + }, + { + "type": "org.springframework.boot.availability.ApplicationAvailability" + }, + { + "type": "org.springframework.boot.availability.ApplicationAvailabilityBean" + }, + { + "type": "org.springframework.boot.builder.ParentContextCloserApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.ContextIdApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.FileEncodingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.TypeExcludeFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.bootstrap.ConfigurableBootstrapContext" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigDataLocation[]" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataNotFoundAction" + }, + { + "type": "org.springframework.boot.context.config.ConfigDataProperties" + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.StandardConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory", + "org.springframework.boot.context.properties.bind.Binder", + "org.springframework.core.io.ResourceLoader" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.event.EventPublishingRunListener", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.SpringApplication", + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.context.logging.LoggingApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.BoundConfigurationProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationProperties" + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder$ConfigurationPropertiesBinderFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.ConfigurationPropertiesSource" + }, + { + "type": "org.springframework.boot.context.properties.EnableConfigurationProperties" + }, + { + "type": "org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.context.properties.NestedConfigurationProperty" + }, + { + "type": "org.springframework.boot.context.properties.bind.Name" + }, + { + "type": "org.springframework.boot.context.properties.bind.Nested" + }, + { + "type": "org.springframework.boot.convert.DurationUnit" + }, + { + "type": "org.springframework.boot.env.PropertiesPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.env.YamlPropertySourceLoader", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.health.actuate.endpoint.HealthEndpoint" + }, + { + "type": "org.springframework.boot.health.autoconfigure.contributor.ConditionalOnEnabledHealthIndicator" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.DefaultClientHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.DefaultServerHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "clientConvertersCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "serverConvertersCustomizer", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "stringHttpMessageConvertersCustomizer", + "parameterTypes": [ + "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonJsonHttpMessageConvertersCustomizer", + "parameterTypes": [ + "tools.jackson.databind.json.JsonMapper" + ] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.JsonbHttpMessageConvertersConfiguration" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.KotlinSerializationHttpMessageConvertersConfiguration" + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.MessageConverterBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer" + }, + { + "type": "org.springframework.boot.io.Base64ProtocolResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.ClassPathResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.io.ProtocolResolverApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.JacksonComponentModule" + }, + { + "type": "org.springframework.boot.jackson.JacksonMixinModule" + }, + { + "type": "org.springframework.boot.jackson.JacksonMixinModuleEntries" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonJsonMapper", + "parameterTypes": [ + "tools.jackson.databind.json.JsonMapper$Builder" + ] + }, + { + "name": "jsonComponentModule", + "parameterTypes": [] + }, + { + "name": "jsonMapperBuilder", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$AbstractMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "standardJsonMapperBuilderCustomizer", + "parameterTypes": [ + "org.springframework.boot.jackson.autoconfigure.JacksonProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration$StandardJsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JacksonMixinConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jacksonMixinModule", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.boot.jackson.JacksonMixinModuleEntries" + ] + }, + { + "name": "jacksonMixinModuleEntries", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "problemDetailJsonMapperBuilderCustomizer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JacksonProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer" + }, + { + "type": "org.springframework.boot.jdbc.SpringJdbcDependsOnDatabaseInitializationDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.ApplicationDataSourceScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$EmbeddedDatabaseCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceAvailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration$PooledDataSourceCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceCheckpointRestoreConfiguration" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceInitializationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dataSourceScriptDatabaseInitializer", + "parameterTypes": [ + "javax.sql.DataSource", + "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints" + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "hikariPoolDataSourceMetadataProvider", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionManager", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "javax.sql.DataSource", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcClientAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jdbcClient", + "parameterTypes": [ + "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.JdbcTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "jdbcTemplate", + "parameterTypes": [ + "javax.sql.DataSource", + "org.springframework.boot.jdbc.autoconfigure.JdbcProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.autoconfigure.NamedParameterJdbcTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "namedParameterJdbcTemplate", + "parameterTypes": [ + "org.springframework.jdbc.core.JdbcTemplate" + ] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" + }, + { + "type": "org.springframework.boot.loader.launch.JarLauncher", + "jniAccessible": true, + "methods": [ + { + "name": "main", + "parameterTypes": [ + "java.lang.String[]" + ] + } + ] + }, + { + "type": "org.springframework.boot.loader.launch.LaunchedClassLoader" + }, + { + "type": "org.springframework.boot.logging.LogLevelEditor" + }, + { + "type": "org.springframework.boot.logging.java.JavaLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.log4j2.SpringBootPropertySource" + }, + { + "type": "org.springframework.boot.logging.logback.LogbackLoggingSystem$Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.logging.logback.RootLogLevelConfigurator" + }, + { + "type": "org.springframework.boot.persistence.autoconfigure.PersistenceExceptionTranslationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "persistenceExceptionTranslationPostProcessor", + "parameterTypes": [ + "org.springframework.core.env.Environment" + ] + } + ] + }, + { + "type": "org.springframework.boot.rsocket.server.RSocketServerCustomizer" + }, + { + "type": "org.springframework.boot.security.autoconfigure.MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.SecurityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationEventPublisher", + "parameterTypes": [ + "org.springframework.context.ApplicationEventPublisher" + ] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.SecurityProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration" + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ConditionalOnDefaultWebSecurity" + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.DefaultWebSecurityCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "securityFilterChainRegistration", + "parameterTypes": [ + "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.SecurityFilterProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration$EnableWebSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "pathPatternRequestMatcherBuilder", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + ] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.HttpEncodingAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "characterEncodingFilter", + "parameterTypes": [ + "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.MultipartAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.servlet.autoconfigure.MultipartProperties" + ] + }, + { + "name": "multipartConfigElement", + "parameterTypes": [] + }, + { + "name": "multipartResolver", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.MultipartProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.autoconfigure.ServletEncodingProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedCharacterEncodingFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedFormContentFilter" + }, + { + "type": "org.springframework.boot.servlet.filter.OrderedRequestContextFilter" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.ApplicationScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.ConditionalOnSqlInitialization" + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.OnSqlInitializationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer" + }, + { + "type": "org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.ssl.DefaultSslBundleRegistry" + }, + { + "type": "org.springframework.boot.ssl.SslBundleRegistry" + }, + { + "type": "org.springframework.boot.ssl.SslBundles" + }, + { + "type": "org.springframework.boot.support.AnsiOutputApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.EnvironmentPostProcessorApplicationListener", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.RandomValuePropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.logging.DeferredLogFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.support.SpringApplicationJsonEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.support.SystemEnvironmentPropertySourceEnvironmentPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.task.SimpleAsyncTaskExecutorBuilder" + }, + { + "type": "org.springframework.boot.task.SimpleAsyncTaskSchedulerBuilder" + }, + { + "type": "org.springframework.boot.task.ThreadPoolTaskExecutorBuilder" + }, + { + "type": "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder" + }, + { + "type": "org.springframework.boot.thread.Threading" + }, + { + "type": "org.springframework.boot.tomcat.ConfigurableTomcatWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.TomcatWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "tomcatWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties", + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerConfiguration$TomcatWebSocketConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "webSocketWebServerCustomizer", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.TomcatWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.WebSocketTomcatWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" + ] + }, + { + "name": "tomcatServletWebServerFactory", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "tomcatServletWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.boot.tomcat.autoconfigure.TomcatServerProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.tomcat.autoconfigure.servlet.TomcatServletWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.tomcat.reactive.TomcatReactiveWebServerFactory" + }, + { + "type": "org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.ExecutionListenersTransactionManagerCustomizer" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionAutoConfiguration$TransactionTemplateConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionTemplate", + "parameterTypes": [ + "org.springframework.transaction.PlatformTransactionManager" + ] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "platformTransactionManagerCustomizers", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "transactionExecutionListeners", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizer" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionManagerCustomizers" + }, + { + "type": "org.springframework.boot.transaction.autoconfigure.TransactionProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.JakartaValidationBackgroundPreinitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.PrimaryDefaultValidatorPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "defaultValidator", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "methodValidationPostProcessor", + "parameterTypes": [ + "org.springframework.core.env.Environment", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.validation.autoconfigure.ValidatorAdapter" + }, + { + "type": "org.springframework.boot.validation.beanvalidation.FilteredMethodValidationPostProcessor" + }, + { + "type": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", + "methods": [ + { + "name": "byAnnotation", + "parameterTypes": [ + "java.lang.Class" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.context.reactive.FilteredReactiveWebContextResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.context.servlet.ServletContextResourceFilePathResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistrar" + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistrarBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.error.ErrorPageRegistry" + }, + { + "type": "org.springframework.boot.web.server.AbstractConfigurableWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.ConfigurableWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setPort", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "servletWebServerFactoryCustomizer", + "parameterTypes": [ + "org.springframework.boot.web.server.autoconfigure.ServerProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration$BeanPostProcessorsRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerFactoryCustomizer" + }, + { + "type": "org.springframework.boot.web.server.context.ServerPortInfoApplicationContextInitializer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.reactive.context.ReactiveWebServerApplicationContextFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.servlet.ServletWebServerFactory" + }, + { + "type": "org.springframework.boot.web.server.servlet.WebListenerRegistry" + }, + { + "type": "org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContextFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.web.servlet.AbstractFilterRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1" + }, + { + "type": "org.springframework.boot.web.servlet.DynamicRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.FilterRegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.RegistrationBean" + }, + { + "type": "org.springframework.boot.web.servlet.ServletContextInitializer" + }, + { + "type": "org.springframework.boot.web.servlet.ServletRegistrationBean" + }, + { + "type": "org.springframework.boot.webmvc.WebMvcWebApplicationTypeDeducer", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dispatcherServlet", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "dispatcherServletRegistration", + "parameterTypes": [ + "org.springframework.web.servlet.DispatcherServlet", + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.DispatcherServletRegistrationBean" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.JspTemplateAvailabilityProvider", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "formContentFilter", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$EnableWebMvcConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.boot.autoconfigure.web.WebProperties", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ListableBeanFactory" + ] + }, + { + "name": "flashMapManager", + "parameterTypes": [] + }, + { + "name": "localeResolver", + "parameterTypes": [] + }, + { + "name": "mvcApiVersionStrategy", + "parameterTypes": [] + }, + { + "name": "mvcConversionService", + "parameterTypes": [] + }, + { + "name": "mvcValidator", + "parameterTypes": [] + }, + { + "name": "viewNameTranslator", + "parameterTypes": [] + }, + { + "name": "welcomePageHandlerMapping", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "welcomePageNotAcceptableHandlerMapping", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.web.WebProperties", + "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "org.springframework.beans.factory.ListableBeanFactory", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "defaultViewResolver", + "parameterTypes": [] + }, + { + "name": "requestContextFilter", + "parameterTypes": [] + }, + { + "name": "viewResolver", + "parameterTypes": [ + "org.springframework.beans.factory.BeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WebMvcProperties", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageHandlerMapping" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.WelcomePageNotAcceptableHandlerMapping" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.DefaultErrorViewResolver" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + }, + { + "name": "errorAttributes", + "parameterTypes": [] + }, + { + "name": "errorPageCustomizer", + "parameterTypes": [ + "org.springframework.boot.webmvc.autoconfigure.DispatcherServletPath" + ] + }, + { + "name": "preserveErrorControllerTargetClassPostProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [ + "org.springframework.context.ApplicationContext", + "org.springframework.boot.autoconfigure.web.WebProperties" + ] + }, + { + "name": "conventionErrorViewResolver", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorPageCustomizer" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$StaticView" + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "beanNameViewResolver", + "parameterTypes": [] + }, + { + "name": "defaultErrorView", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver" + }, + { + "type": "org.springframework.boot.webmvc.error.DefaultErrorAttributes" + }, + { + "type": "org.springframework.boot.webmvc.error.ErrorAttributes" + }, + { + "type": "org.springframework.boot.webmvc.error.ErrorController" + }, + { + "type": "org.springframework.cglib.proxy.Dispatcher" + }, + { + "type": "org.springframework.cglib.proxy.MethodInterceptor" + }, + { + "type": "org.springframework.cglib.proxy.NoOp" + }, + { + "type": "org.springframework.cloud.commons.util.InetUtils" + }, + { + "type": "org.springframework.context.ApplicationContextAware" + }, + { + "type": "org.springframework.context.ApplicationEventPublisherAware" + }, + { + "type": "org.springframework.context.ApplicationListener" + }, + { + "type": "org.springframework.context.ApplicationStartupAware" + }, + { + "type": "org.springframework.context.EmbeddedValueResolverAware" + }, + { + "type": "org.springframework.context.EnvironmentAware" + }, + { + "type": "org.springframework.context.Lifecycle" + }, + { + "type": "org.springframework.context.LifecycleProcessor" + }, + { + "type": "org.springframework.context.MessageSourceAware" + }, + { + "type": "org.springframework.context.Phased" + }, + { + "type": "org.springframework.context.ResourceLoaderAware" + }, + { + "type": "org.springframework.context.SmartLifecycle" + }, + { + "type": "org.springframework.context.annotation.AnnotationScopeMetadataResolver", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.AutoProxyRegistrar", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.Bean" + }, + { + "type": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.annotation.ComponentScan" + }, + { + "type": "org.springframework.context.annotation.ComponentScan$Filter" + }, + { + "type": "org.springframework.context.annotation.Conditional" + }, + { + "type": "org.springframework.context.annotation.Configuration" + }, + { + "type": "org.springframework.context.annotation.ConfigurationClassEnhancer$EnhancedConfiguration" + }, + { + "type": "org.springframework.context.annotation.ConfigurationClassPostProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setMetadataReaderFactory", + "parameterTypes": [ + "org.springframework.core.type.classreading.MetadataReaderFactory" + ] + } + ] + }, + { + "type": "org.springframework.context.annotation.DependsOn" + }, + { + "type": "org.springframework.context.annotation.Import" + }, + { + "type": "org.springframework.context.annotation.ImportAware" + }, + { + "type": "org.springframework.context.annotation.ImportRuntimeHints" + }, + { + "type": "org.springframework.context.annotation.Lazy" + }, + { + "type": "org.springframework.context.annotation.Primary" + }, + { + "type": "org.springframework.context.annotation.Role" + }, + { + "type": "org.springframework.context.annotation.Scope" + }, + { + "type": "org.springframework.context.event.DefaultEventListenerFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.event.EventListenerMethodProcessor", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.context.support.ApplicationObjectSupport" + }, + { + "type": "org.springframework.context.support.DefaultLifecycleProcessor" + }, + { + "type": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer" + }, + { + "type": "org.springframework.core.GenericTypeResolver" + }, + { + "type": "org.springframework.core.Ordered" + }, + { + "type": "org.springframework.core.PriorityOrdered" + }, + { + "type": "org.springframework.core.annotation.AliasFor" + }, + { + "type": "org.springframework.core.annotation.AnnotationAttributes[]" + }, + { + "type": "org.springframework.core.annotation.Order" + }, + { + "type": "org.springframework.core.convert.ConversionService" + }, + { + "type": "org.springframework.core.convert.converter.ConverterRegistry" + }, + { + "type": "org.springframework.core.convert.support.ConfigurableConversionService" + }, + { + "type": "org.springframework.core.convert.support.GenericConversionService" + }, + { + "type": "org.springframework.core.env.EnvironmentCapable" + }, + { + "type": "org.springframework.core.task.AsyncTaskExecutor" + }, + { + "type": "org.springframework.core.task.TaskExecutor" + }, + { + "type": "org.springframework.core.type.classreading.CachingMetadataReaderFactory" + }, + { + "type": "org.springframework.core.type.classreading.MetadataReaderFactory" + }, + { + "type": "org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" + }, + { + "type": "org.springframework.dao.support.DaoSupport" + }, + { + "type": "org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.format.FormatterRegistry" + }, + { + "type": "org.springframework.format.support.DefaultFormattingConversionService" + }, + { + "type": "org.springframework.format.support.FormattingConversionService" + }, + { + "type": "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.http.ProblemDetail" + }, + { + "type": "org.springframework.http.ReactiveHttpInputMessage" + }, + { + "type": "org.springframework.http.converter.HttpMessageConverter" + }, + { + "type": "org.springframework.http.converter.StringHttpMessageConverter" + }, + { + "type": "org.springframework.http.converter.json.JacksonJsonHttpMessageConverter" + }, + { + "type": "org.springframework.jdbc.core.JdbcOperations" + }, + { + "type": "org.springframework.jdbc.core.JdbcTemplate" + }, + { + "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations" + }, + { + "type": "org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate" + }, + { + "type": "org.springframework.jdbc.core.simple.DefaultJdbcClient" + }, + { + "type": "org.springframework.jdbc.core.simple.JdbcClient" + }, + { + "type": "org.springframework.jdbc.datasource.DataSourceTransactionManager" + }, + { + "type": "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType" + }, + { + "type": "org.springframework.jdbc.datasource.init.DatabasePopulator" + }, + { + "type": "org.springframework.jdbc.support.JdbcAccessor" + }, + { + "type": "org.springframework.jdbc.support.JdbcTransactionManager" + }, + { + "type": "org.springframework.jmx.export.MBeanExporter" + }, + { + "type": "org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler" + }, + { + "type": "org.springframework.scheduling.SchedulingTaskExecutor" + }, + { + "type": "org.springframework.scheduling.annotation.AsyncConfigurer" + }, + { + "type": "org.springframework.scheduling.concurrent.CustomizableThreadFactory" + }, + { + "type": "org.springframework.scheduling.concurrent.ExecutorConfigurationSupport" + }, + { + "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" + }, + { + "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" + }, + { + "type": "org.springframework.security.access.SecurityConfig" + }, + { + "type": "org.springframework.security.access.expression.AbstractSecurityExpressionHandler" + }, + { + "type": "org.springframework.security.access.expression.SecurityExpressionHandler" + }, + { + "type": "org.springframework.security.authentication.AnonymousAuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.AuthenticationEventPublisher" + }, + { + "type": "org.springframework.security.authentication.AuthenticationManager" + }, + { + "type": "org.springframework.security.authentication.AuthenticationManagerResolver" + }, + { + "type": "org.springframework.security.authentication.AuthenticationProvider" + }, + { + "type": "org.springframework.security.authentication.DefaultAuthenticationEventPublisher" + }, + { + "type": "org.springframework.security.authentication.ProviderManager" + }, + { + "type": "org.springframework.security.authentication.ReactiveAuthenticationManager" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureLockedEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent" + }, + { + "type": "org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent" + }, + { + "type": "org.springframework.security.authorization.AuthorizationManager" + }, + { + "type": "org.springframework.security.config.ObjectPostProcessor" + }, + { + "type": "org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.AbstractSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.SecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.SecurityConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.ProviderManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationManagerBuilder", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor", + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "enableGlobalAuthenticationAutowiredConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "initializeAuthenticationProviderBeanManagerConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "initializeUserDetailsBeanManagerConfigurer", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setApplicationContext", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setGlobalAuthenticationConfigurers", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "setObjectPostProcessor", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeAuthenticationProviderBeanManagerConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer" + }, + { + "type": "org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor" + }, + { + "type": "org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "objectPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.config.AutowireCapableBeanFactory" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.HttpSecurityBuilder" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.HttpSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.WebSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.builders.WebSecurity$SecurityExpressionHandlerAdapter" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.EnableWebSecurity" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "httpSecurity", + "parameterTypes": [] + }, + { + "name": "setApplicationContext", + "parameterTypes": [ + "org.springframework.context.ApplicationContext" + ] + }, + { + "name": "setAuthenticationConfiguration", + "parameterTypes": [ + "org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration" + ] + }, + { + "name": "setContentNegotiationStrategy", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationStrategy" + ] + }, + { + "name": "setObjectPostProcessor", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.OAuth2ImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "authenticationManagerPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "filterChainDecoratorPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "webAuthorizationManagerPostProcessor", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider", + "org.springframework.beans.factory.ObjectProvider" + ] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$1" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$2" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationConfiguration$3" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.ObservationImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.SpringWebMvcImportSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "requestDataValueProcessor", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "conversionServicePostProcessor", + "parameterTypes": [] + }, + { + "name": "delegatingApplicationListener", + "parameterTypes": [] + }, + { + "name": "privilegeEvaluator", + "parameterTypes": [] + }, + { + "name": "setFilterChainProxySecurityConfigurer", + "parameterTypes": [ + "org.springframework.security.config.ObjectPostProcessor", + "org.springframework.beans.factory.config.ConfigurableListableBeanFactory" + ] + }, + { + "name": "setFilterChains", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "springSecurityFilterChain", + "parameterTypes": [ + "org.springframework.beans.factory.ObjectProvider" + ] + }, + { + "name": "springSecurityPathPatternParserBeanDefinitionRegistryPostProcessor", + "parameterTypes": [] + }, + { + "name": "webSecurityExpressionHandler", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$1" + }, + { + "type": "org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.security.config.crypto.RsaKeyConversionServicePostProcessor" + }, + { + "type": "org.springframework.security.config.http.SessionCreationPolicy" + }, + { + "type": "org.springframework.security.context.DelegatingApplicationListener" + }, + { + "type": "org.springframework.security.core.userdetails.UserDetailsService" + }, + { + "type": "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" + }, + { + "type": "org.springframework.security.crypto.password.AbstractValidatingPasswordEncoder" + }, + { + "type": "org.springframework.security.crypto.password.PasswordEncoder" + }, + { + "type": "org.springframework.security.data.repository.query.SecurityEvaluationContextExtension" + }, + { + "type": "org.springframework.security.oauth2.client.registration.ClientRegistration" + }, + { + "type": "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository" + }, + { + "type": "org.springframework.security.oauth2.jwt.JwtDecoder" + }, + { + "type": "org.springframework.security.oauth2.server.resource.BearerTokenError" + }, + { + "type": "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" + }, + { + "type": "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" + }, + { + "type": "org.springframework.security.web.DefaultSecurityFilterChain" + }, + { + "type": "org.springframework.security.web.FilterChainProxy" + }, + { + "type": "org.springframework.security.web.SecurityFilterChain" + }, + { + "type": "org.springframework.security.web.access.ExceptionTranslationFilter" + }, + { + "type": "org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator" + }, + { + "type": "org.springframework.security.web.access.WebInvocationPrivilegeEvaluator" + }, + { + "type": "org.springframework.security.web.access.intercept.AuthorizationFilter" + }, + { + "type": "org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager" + }, + { + "type": "org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutFilter" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutHandler" + }, + { + "type": "org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler" + }, + { + "type": "org.springframework.security.web.authentication.session.AbstractSessionFixationProtectionStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.authentication.session.SessionAuthenticationStrategy" + }, + { + "type": "org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer" + }, + { + "type": "org.springframework.security.web.context.SecurityContextHolderFilter" + }, + { + "type": "org.springframework.security.web.csrf.CsrfFilter" + }, + { + "type": "org.springframework.security.web.header.HeaderWriterFilter" + }, + { + "type": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter" + }, + { + "type": "org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" + }, + { + "type": "org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher$Builder" + }, + { + "type": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" + }, + { + "type": "org.springframework.security.web.session.SessionManagementFilter" + }, + { + "type": "org.springframework.stereotype.Component" + }, + { + "type": "org.springframework.stereotype.Controller" + }, + { + "type": "org.springframework.stereotype.Indexed" + }, + { + "type": "org.springframework.stereotype.Service" + }, + { + "type": "org.springframework.transaction.ConfigurableTransactionManager" + }, + { + "type": "org.springframework.transaction.PlatformTransactionManager" + }, + { + "type": "org.springframework.transaction.ReactiveTransactionManager" + }, + { + "type": "org.springframework.transaction.TransactionDefinition" + }, + { + "type": "org.springframework.transaction.TransactionManager" + }, + { + "type": "org.springframework.transaction.annotation.AbstractTransactionManagementConfiguration", + "methods": [ + { + "name": "transactionAttributeSource", + "parameterTypes": [] + }, + { + "name": "transactionalEventListenerFactory", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.annotation.EnableTransactionManagement" + }, + { + "type": "org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "transactionAdvisor", + "parameterTypes": [ + "org.springframework.transaction.interceptor.TransactionAttributeSource", + "org.springframework.transaction.interceptor.TransactionInterceptor" + ] + }, + { + "name": "transactionInterceptor", + "parameterTypes": [ + "org.springframework.transaction.interceptor.TransactionAttributeSource" + ] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.RestrictedTransactionalEventListenerFactory" + }, + { + "type": "org.springframework.transaction.annotation.TransactionManagementConfigurationSelector", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.transaction.annotation.TransactionRuntimeHints" + }, + { + "type": "org.springframework.transaction.annotation.Transactional" + }, + { + "type": "org.springframework.transaction.aspectj.AbstractTransactionAspect" + }, + { + "type": "org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionAspectSupport" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionAttributeSource" + }, + { + "type": "org.springframework.transaction.interceptor.TransactionInterceptor" + }, + { + "type": "org.springframework.transaction.support.AbstractPlatformTransactionManager" + }, + { + "type": "org.springframework.transaction.support.DefaultTransactionDefinition" + }, + { + "type": "org.springframework.transaction.support.ResourceTransactionManager" + }, + { + "type": "org.springframework.transaction.support.TransactionOperations" + }, + { + "type": "org.springframework.transaction.support.TransactionTemplate" + }, + { + "type": "org.springframework.util.AntPathMatcher" + }, + { + "type": "org.springframework.util.ConcurrentReferenceHashMap$Segment[]" + }, + { + "type": "org.springframework.util.CustomizableThreadCreator" + }, + { + "type": "org.springframework.util.PathMatcher" + }, + { + "type": "org.springframework.validation.SmartValidator" + }, + { + "type": "org.springframework.validation.Validator" + }, + { + "type": "org.springframework.validation.annotation.Validated" + }, + { + "type": "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" + }, + { + "type": "org.springframework.validation.beanvalidation.MethodValidationPostProcessor" + }, + { + "type": "org.springframework.validation.beanvalidation.SpringValidatorAdapter" + }, + { + "type": "org.springframework.web.accept.ApiVersionStrategy" + }, + { + "type": "org.springframework.web.accept.ApiVersionStrategyEditor" + }, + { + "type": "org.springframework.web.accept.ContentNegotiationManager" + }, + { + "type": "org.springframework.web.accept.ContentNegotiationStrategy" + }, + { + "type": "org.springframework.web.accept.MediaTypeFileExtensionResolver" + }, + { + "type": "org.springframework.web.bind.annotation.ControllerAdvice" + }, + { + "type": "org.springframework.web.bind.annotation.DeleteMapping" + }, + { + "type": "org.springframework.web.bind.annotation.ExceptionHandler" + }, + { + "type": "org.springframework.web.bind.annotation.GetMapping" + }, + { + "type": "org.springframework.web.bind.annotation.Mapping" + }, + { + "type": "org.springframework.web.bind.annotation.PathVariable" + }, + { + "type": "org.springframework.web.bind.annotation.PostMapping" + }, + { + "type": "org.springframework.web.bind.annotation.PutMapping" + }, + { + "type": "org.springframework.web.bind.annotation.RequestBody" + }, + { + "type": "org.springframework.web.bind.annotation.RequestHeader" + }, + { + "type": "org.springframework.web.bind.annotation.RequestMapping" + }, + { + "type": "org.springframework.web.bind.annotation.RequestMethod[]" + }, + { + "type": "org.springframework.web.bind.annotation.RequestParam" + }, + { + "type": "org.springframework.web.bind.annotation.ResponseBody" + }, + { + "type": "org.springframework.web.bind.annotation.RestController" + }, + { + "type": "org.springframework.web.bind.annotation.RestControllerAdvice" + }, + { + "type": "org.springframework.web.client.RestClientException" + }, + { + "type": "org.springframework.web.context.ConfigurableWebApplicationContext" + }, + { + "type": "org.springframework.web.context.ServletContextAware" + }, + { + "type": "org.springframework.web.context.request.RequestContextListener" + }, + { + "type": "org.springframework.web.context.support.GenericWebApplicationContext" + }, + { + "type": "org.springframework.web.context.support.ServletContextResource" + }, + { + "type": "org.springframework.web.context.support.WebApplicationObjectSupport" + }, + { + "type": "org.springframework.web.filter.CharacterEncodingFilter" + }, + { + "type": "org.springframework.web.filter.DelegatingFilterProxy" + }, + { + "type": "org.springframework.web.filter.FormContentFilter" + }, + { + "type": "org.springframework.web.filter.GenericFilterBean" + }, + { + "type": "org.springframework.web.filter.OncePerRequestFilter" + }, + { + "type": "org.springframework.web.filter.RequestContextFilter" + }, + { + "type": "org.springframework.web.filter.ServletRequestPathFilter", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.web.method.annotation.ExceptionHandlerMethodResolver" + }, + { + "type": "org.springframework.web.method.support.CompositeUriComponentsContributor" + }, + { + "type": "org.springframework.web.method.support.UriComponentsContributor" + }, + { + "type": "org.springframework.web.multipart.MultipartResolver" + }, + { + "type": "org.springframework.web.multipart.support.StandardServletMultipartResolver" + }, + { + "type": "org.springframework.web.reactive.HandlerResult" + }, + { + "type": "org.springframework.web.reactive.function.client.ExchangeFilterFunction" + }, + { + "type": "org.springframework.web.reactive.function.client.WebClientException" + }, + { + "type": "org.springframework.web.servlet.DispatcherServlet" + }, + { + "type": "org.springframework.web.servlet.FlashMapManager" + }, + { + "type": "org.springframework.web.servlet.FrameworkServlet" + }, + { + "type": "org.springframework.web.servlet.HandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.HandlerExceptionResolver" + }, + { + "type": "org.springframework.web.servlet.HandlerInterceptor" + }, + { + "type": "org.springframework.web.servlet.HandlerMapping" + }, + { + "type": "org.springframework.web.servlet.HandlerMappingEditor" + }, + { + "type": "org.springframework.web.servlet.HttpServletBean" + }, + { + "type": "org.springframework.web.servlet.LocaleResolver" + }, + { + "type": "org.springframework.web.servlet.RequestToViewNameTranslator" + }, + { + "type": "org.springframework.web.servlet.View" + }, + { + "type": "org.springframework.web.servlet.ViewResolver" + }, + { + "type": "org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration", + "methods": [ + { + "name": "setConfigurers", + "parameterTypes": [ + "java.util.List" + ] + } + ] + }, + { + "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport", + "methods": [ + { + "name": "beanNameHandlerMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "defaultServletHandlerMapping", + "parameterTypes": [] + }, + { + "name": "handlerExceptionResolver", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager" + ] + }, + { + "name": "handlerFunctionAdapter", + "parameterTypes": [] + }, + { + "name": "httpRequestHandlerAdapter", + "parameterTypes": [] + }, + { + "name": "mvcContentNegotiationManager", + "parameterTypes": [] + }, + { + "name": "mvcPathMatcher", + "parameterTypes": [] + }, + { + "name": "mvcPatternParser", + "parameterTypes": [] + }, + { + "name": "mvcResourceUrlProvider", + "parameterTypes": [] + }, + { + "name": "mvcUriComponentsContributor", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" + ] + }, + { + "name": "mvcUrlPathHelper", + "parameterTypes": [] + }, + { + "name": "mvcViewResolver", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager" + ] + }, + { + "name": "requestMappingHandlerAdapter", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.validation.Validator" + ] + }, + { + "name": "requestMappingHandlerMapping", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.web.accept.ApiVersionStrategy", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "resourceHandlerMapping", + "parameterTypes": [ + "org.springframework.web.accept.ContentNegotiationManager", + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + }, + { + "name": "routerFunctionMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider", + "org.springframework.web.accept.ApiVersionStrategy" + ] + }, + { + "name": "simpleControllerHandlerAdapter", + "parameterTypes": [] + }, + { + "name": "viewControllerHandlerMapping", + "parameterTypes": [ + "org.springframework.format.support.FormattingConversionService", + "org.springframework.web.servlet.resource.ResourceUrlProvider" + ] + } + ] + }, + { + "type": "org.springframework.web.servlet.config.annotation.WebMvcConfigurer" + }, + { + "type": "org.springframework.web.servlet.function.support.HandlerFunctionAdapter" + }, + { + "type": "org.springframework.web.servlet.function.support.RouterFunctionMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler" + }, + { + "type": "org.springframework.web.servlet.handler.AbstractUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.HandlerExceptionResolverComposite" + }, + { + "type": "org.springframework.web.servlet.handler.MatchableHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.i18n.AbstractLocaleResolver" + }, + { + "type": "org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" + }, + { + "type": "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping$HttpOptionsHandler" + }, + { + "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" + }, + { + "type": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" + }, + { + "type": "org.springframework.web.servlet.resource.ResourceUrlProvider" + }, + { + "type": "org.springframework.web.servlet.support.AbstractFlashMapManager" + }, + { + "type": "org.springframework.web.servlet.support.RequestDataValueProcessor" + }, + { + "type": "org.springframework.web.servlet.support.SessionFlashMapManager" + }, + { + "type": "org.springframework.web.servlet.support.WebContentGenerator" + }, + { + "type": "org.springframework.web.servlet.view.AbstractCachingViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.AbstractUrlBasedView" + }, + { + "type": "org.springframework.web.servlet.view.AbstractView" + }, + { + "type": "org.springframework.web.servlet.view.BeanNameViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.ContentNegotiatingViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator" + }, + { + "type": "org.springframework.web.servlet.view.InternalResourceView" + }, + { + "type": "org.springframework.web.servlet.view.InternalResourceViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.UrlBasedViewResolver" + }, + { + "type": "org.springframework.web.servlet.view.ViewResolverComposite" + }, + { + "type": "org.springframework.web.util.UrlPathHelper" + }, + { + "type": "org.springframework.web.util.pattern.PathPatternParser" + }, + { + "type": "org.sqlite.BusyHandler", + "jniAccessible": true, + "methods": [ + { + "name": "callback", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "type": "org.sqlite.Collation", + "jniAccessible": true, + "methods": [ + { + "name": "xCompare", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + } + ] + }, + { + "type": "org.sqlite.Function", + "jniAccessible": true, + "fields": [ + { + "name": "args" + }, + { + "name": "context" + }, + { + "name": "value" + } + ], + "methods": [ + { + "name": "xFunc", + "parameterTypes": [] + } + ] + }, + { + "type": "org.sqlite.Function$Aggregate", + "jniAccessible": true, + "methods": [ + { + "name": "clone", + "parameterTypes": [] + }, + { + "name": "xFinal", + "parameterTypes": [] + }, + { + "name": "xStep", + "parameterTypes": [] + } + ] + }, + { + "type": "org.sqlite.Function$Window", + "jniAccessible": true, + "methods": [ + { + "name": "xInverse", + "parameterTypes": [] + }, + { + "name": "xValue", + "parameterTypes": [] + } + ] + }, + { + "type": "org.sqlite.JDBC", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.sqlite.ProgressHandler", + "jniAccessible": true, + "methods": [ + { + "name": "progress", + "parameterTypes": [] + } + ] + }, + { + "type": "org.sqlite.core.DB", + "jniAccessible": true, + "methods": [ + { + "name": "onCommit", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "onUpdate", + "parameterTypes": [ + "int", + "java.lang.String", + "java.lang.String", + "long" + ] + }, + { + "name": "throwex", + "parameterTypes": [] + }, + { + "name": "throwex", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "type": "org.sqlite.core.DB$ProgressObserver", + "jniAccessible": true, + "methods": [ + { + "name": "progress", + "parameterTypes": [ + "int", + "int" + ] + } + ] + }, + { + "type": "org.sqlite.core.NativeDB", + "jniAccessible": true, + "fields": [ + { + "name": "busyHandler" + }, + { + "name": "commitListener" + }, + { + "name": "pointer" + }, + { + "name": "progressHandler" + }, + { + "name": "updateListener" + } + ], + "methods": [ + { + "name": "stringToUtf8ByteArray", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "throwex", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "org.webjars.WebJarAssetLocator" + }, + { + "type": "org.webjars.WebJarVersionLocator" + }, + { + "type": "org.yaml.snakeyaml.Yaml" + }, + { + "type": "reactor.core.publisher.Flux" + }, + { + "type": "reactor.core.publisher.Mono" + }, + { + "type": "reactor.netty.http.client.HttpClient" + }, + { + "type": "sun.launcher.LauncherHelper", + "jniAccessible": true, + "fields": [ + { + "name": "isStaticMain" + }, + { + "name": "noArgMain" + } + ], + "methods": [ + { + "name": "getApplicationClass", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.management.VMManagementImpl", + "jniAccessible": true, + "fields": [ + { + "name": "compTimeMonitoringSupport" + }, + { + "name": "currentThreadCpuTimeSupport" + }, + { + "name": "objectMonitorUsageSupport" + }, + { + "name": "otherThreadCpuTimeSupport" + }, + { + "name": "remoteDiagnosticCommandsSupport" + }, + { + "name": "synchronizerUsageSupport" + }, + { + "name": "threadAllocatedMemorySupport" + }, + { + "name": "threadContentionMonitoringSupport" + } + ] + }, + { + "type": "sun.misc.Unsafe", + "fields": [ + { + "name": "theUnsafe" + } + ] + }, + { + "type": "sun.reflect.ReflectionFactory", + "methods": [ + { + "name": "getReflectionFactory", + "parameterTypes": [] + }, + { + "name": "newConstructorForSerialization", + "parameterTypes": [ + "java.lang.Class", + "java.lang.reflect.Constructor" + ] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DRBG", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.JavaKeyStore$JKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SHA5$SHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.SecureRandom", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.provider.certpath.PKIXCertPathValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.PSSParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSAPSSSignature", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "type": "sun.text.resources.cldr.FormatData" + }, + { + "type": "sun.text.resources.cldr.FormatData_en" + }, + { + "type": "sun.text.resources.cldr.FormatData_en_US" + }, + { + "type": "sun.util.resources.cldr.CalendarData" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en" + }, + { + "type": "sun.util.resources.cldr.TimeZoneNames_en_US" + }, + { + "type": "tools.jackson.core.TreeCodec" + }, + { + "type": "tools.jackson.core.Versioned" + }, + { + "type": "tools.jackson.databind.JacksonModule" + }, + { + "type": "tools.jackson.databind.ObjectMapper" + }, + { + "type": "tools.jackson.databind.cfg.MapperBuilder" + }, + { + "type": "tools.jackson.databind.deser.Deserializers[]" + }, + { + "type": "tools.jackson.databind.deser.KeyDeserializers[]" + }, + { + "type": "tools.jackson.databind.deser.ValueInstantiators[]" + }, + { + "type": "tools.jackson.databind.json.JsonMapper" + }, + { + "type": "tools.jackson.databind.json.JsonMapper$Builder" + }, + { + "type": "tools.jackson.databind.module.SimpleModule" + }, + { + "type": "tools.jackson.databind.ser.Serializers[]" + }, + { + "type": "tools.jackson.dataformat.cbor.CBORMapper" + }, + { + "type": "tools.jackson.dataformat.smile.SmileMapper" + }, + { + "type": "tools.jackson.dataformat.xml.XmlMapper" + }, + { + "type": "tools.jackson.dataformat.yaml.YAMLMapper" + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.ParameterizedType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.TypeVariable", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.lang.reflect.WildcardType", + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + "java.io.Serializable" + ] + } + }, + { + "type": { + "proxy": [ + "java.sql.Connection" + ] + } + }, + { + "type": { + "proxy": [ + "org.apache.ibatis.executor.Executor" + ] + } + }, + { + "type": { + "proxy": [ + "org.apache.ibatis.executor.statement.StatementHandler" + ] + } + }, + { + "type": { + "proxy": [ + "org.apache.ibatis.session.SqlSession" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.boot.context.properties.ConfigurationProperties" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.ControllerAdvice" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.ExceptionHandler" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.PathVariable" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.RequestHeader" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.RequestMapping" + ] + } + }, + { + "type": { + "proxy": [ + "org.springframework.web.bind.annotation.RequestParam" + ] + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration", + "interfaces": [ + "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$BootstrapExecutorConfiguration", + "interfaces": [ + "org.springframework.beans.factory.config.BeanFactoryPostProcessor" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.jdbc.autoconfigure.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration", + "interfaces": [ + "org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider" + ] + } + } + }, + { + "type": { + "lambda": { + "declaringClass": "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter", + "interfaces": [ + "org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter" + ] + } + } + } + ], + "resources": [ + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.xml" + }, + { + "glob": "META-INF/build-info.properties" + }, + { + "glob": "META-INF/maven/org.xerial/sqlite-jdbc/pom.properties" + }, + { + "glob": "META-INF/services/ch.qos.logback.classic.spi.Configurator" + }, + { + "glob": "META-INF/services/com.baomidou.mybatisplus.core.spi.CompatibleSet" + }, + { + "glob": "META-INF/services/jakarta.el.ExpressionFactory" + }, + { + "glob": "META-INF/services/jakarta.validation.ConstraintValidator" + }, + { + "glob": "META-INF/services/jakarta.validation.spi.ValidationProvider" + }, + { + "glob": "META-INF/services/jakarta.validation.valueextraction.ValueExtractor" + }, + { + "glob": "META-INF/services/java.net.spi.InetAddressResolverProvider" + }, + { + "glob": "META-INF/services/java.net.spi.URLStreamHandlerProvider" + }, + { + "glob": "META-INF/services/java.nio.channels.spi.SelectorProvider" + }, + { + "glob": "META-INF/services/java.sql.Driver" + }, + { + "glob": "META-INF/services/java.time.zone.ZoneRulesProvider" + }, + { + "glob": "META-INF/services/java.util.spi.ResourceBundleControlProvider" + }, + { + "glob": "META-INF/services/javax.xml.parsers.DocumentBuilderFactory" + }, + { + "glob": "META-INF/services/javax.xml.xpath.XPathFactory" + }, + { + "glob": "META-INF/services/org.apache.commons.logging.LogFactory" + }, + { + "glob": "META-INF/services/org.apache.juli.logging.Log" + }, + { + "glob": "META-INF/services/org.apache.logging.log4j.util.PropertySource" + }, + { + "glob": "META-INF/services/org.slf4j.spi.SLF4JServiceProvider" + }, + { + "glob": "META-INF/services/tools.jackson.databind.JacksonModule" + }, + { + "glob": "META-INF/spring-autoconfigure-metadata.properties" + }, + { + "glob": "META-INF/spring.components" + }, + { + "glob": "META-INF/spring.factories" + }, + { + "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports" + }, + { + "glob": "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements" + }, + { + "glob": "META-INF/validation.xml" + }, + { + "glob": "application-prod.properties" + }, + { + "glob": "application-prod.xml" + }, + { + "glob": "application-prod.yaml" + }, + { + "glob": "application-prod.yml" + }, + { + "glob": "application.properties" + }, + { + "glob": "application.xml" + }, + { + "glob": "application.yaml" + }, + { + "glob": "application.yml" + }, + { + "glob": "banner.txt" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/DdlAutoConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration$AutoConfiguredMapperScannerRegistrar.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration$MapperScannerRegistrarNotFoundConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusInnerInterceptorAutoConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$FreeMarkerConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$LegacyFreeMarkerConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$LegacyVelocityConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$ThymeleafConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration$VelocityConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/autoconfigure/MybatisPlusLanguageDriverAutoConfiguration.class" + }, + { + "glob": "com/baomidou/mybatisplus/extension/repository/AbstractRepository.class" + }, + { + "glob": "com/baomidou/mybatisplus/extension/repository/CrudRepository.class" + }, + { + "glob": "com/baomidou/mybatisplus/extension/repository/IRepository.class" + }, + { + "glob": "com/baomidou/mybatisplus/extension/service/IService.class" + }, + { + "glob": "com/baomidou/mybatisplus/extension/service/impl/ServiceImpl.class" + }, + { + "glob": "commons-logging.properties" + }, + { + "glob": "config/application-prod.properties" + }, + { + "glob": "config/application-prod.xml" + }, + { + "glob": "config/application-prod.yaml" + }, + { + "glob": "config/application-prod.yml" + }, + { + "glob": "config/application.properties" + }, + { + "glob": "config/application.xml" + }, + { + "glob": "config/application.yaml" + }, + { + "glob": "config/application.yml" + }, + { + "glob": "data-all.sql" + }, + { + "glob": "data.sql" + }, + { + "glob": "dev/qingzhou/pushserver" + }, + { + "glob": "dev/qingzhou/pushserver/aspect/SecurityInterceptor.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/JsonDtoPackageHints$DtoHints.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/JsonDtoPackageHints.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBaitsRuntimeHintsRegistrar.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisBeanFactoryInitializationAotProcessor.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisMapperFactoryBeanPostProcessor.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration$MyBatisMapperTypeUtils.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/MyBatisNativeConfiguration.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PortalDatabaseConfig.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PortalJacksonConfig.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PortalMybatisConfig.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PortalSchemaInitializer.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PortalSecurityConfig.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/PushConfiguration.class" + }, + { + "glob": "dev/qingzhou/pushserver/config/WebConfig.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/CaptchaController$CaptchaResponse.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/CaptchaController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/DashboardController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PageController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalAppController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalAuthController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalCorpController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalErrorController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalInitController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalMeController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalMessageController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalProxyController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalSystemController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PushController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/openapi/OpenApiMessageController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.class" + }, + { + "glob": "dev/qingzhou/pushserver/exception/GlobalExceptionHandler.class" + }, + { + "glob": "dev/qingzhou/pushserver/exception/PortalExceptionHandler.class" + }, + { + "glob": "dev/qingzhou/pushserver/manager/wecom/WecomApiClient.class" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalSystemConfigMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalUserMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/security/CaptchaService.class" + }, + { + "glob": "dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter$WindowCounter.class" + }, + { + "glob": "dev/qingzhou/pushserver/security/PortalAppApiKeyRateLimiter.class" + }, + { + "glob": "dev/qingzhou/pushserver/security/PortalUserDetailsService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/DashboardService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalAccessTokenService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalAppApiKeyService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalCorpConfigService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalMessageLogService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalMessageService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalProxyConfigService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalUserService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PortalWecomAppService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/PushService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/SystemConfigService.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl$CachedToken.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalMessageLogServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalUserServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalWecomAppServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PushServiceImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.class" + }, + { + "glob": "git.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings_zh.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings_zh_CN.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings_zh_Hans.properties" + }, + { + "glob": "jakarta/servlet/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings_zh.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings_zh_CN.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings_zh_Hans.properties" + }, + { + "glob": "jakarta/servlet/http/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "jndi.properties" + }, + { + "glob": "log4j2.StatusLogger.properties" + }, + { + "glob": "log4j2.component.properties" + }, + { + "glob": "log4j2.system.properties" + }, + { + "glob": "logback-spring.groovy" + }, + { + "glob": "logback-spring.xml" + }, + { + "glob": "logback-test-spring.groovy" + }, + { + "glob": "logback-test-spring.xml" + }, + { + "glob": "logback-test.groovy" + }, + { + "glob": "logback-test.xml" + }, + { + "glob": "logback.groovy" + }, + { + "glob": "logback.xml" + }, + { + "glob": "mapper" + }, + { + "glob": "messages.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/authenticator/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/authenticator/jaspic/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/connector/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/core/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedFilters.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedListeners.properties" + }, + { + "glob": "org/apache/catalina/core/RestrictedServlets.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/deploy/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/loader/JdbcLeakPrevention.class" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/loader/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/mapper/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/mbeans/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/realm/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/session/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/startup/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/util/CharsetMapperDefault.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/util/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/util/ServerInfo.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/valves/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/catalina/webresources/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/coyote/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/coyote/http11/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/coyote/http11/filters/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/naming/LocalStrings.properties" + }, + { + "glob": "org/apache/naming/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/naming/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/naming/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/naming/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/jni/AprStatus.class" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/buf/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/compat/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/descriptor/web/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/http/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/http/parser/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/modeler/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/net/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/scan/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/util/threads/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/websocket/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans.properties" + }, + { + "glob": "org/apache/tomcat/websocket/server/LocalStrings_zh_Hans_CN.properties" + }, + { + "glob": "org/mybatis/spring/annotation/MapperScan.class" + }, + { + "glob": "org/mybatis/spring/annotation/MapperScannerRegistrar.class" + }, + { + "glob": "org/springframework/aot/hint/annotation/Reflective.class" + }, + { + "glob": "org/springframework/beans/factory/Aware.class" + }, + { + "glob": "org/springframework/beans/factory/BeanFactoryAware.class" + }, + { + "glob": "org/springframework/beans/factory/InitializingBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureAfter.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureBefore.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/AutoConfigureOrder.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$AspectJAutoProxyingConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration$ClassProxyingConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/aop/AopAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/availability/ApplicationAvailabilityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnBooleanProperty.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnClass.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnMissingClass.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnNotWarDeployment.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnProperty.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnResource.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnSingleCandidate.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/condition/ConditionalOnWebApplication.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/ConfigurationPropertiesAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/LifecycleAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/MessageSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/context/PropertyPlaceholderAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration$GitResourceAvailableCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/info/ProjectInfoAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/jmx/JmxAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/ssl/SslAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$AsyncConfigurerWrapperConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$BootstrapExecutorConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ExecutorBeanCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition$ModelCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$OnExecutorCondition.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$TaskExecutorConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$TaskSchedulerConfiguration.class" + }, + { + "glob": "org/springframework/boot/autoconfigure/task/TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/context/properties/EnableConfigurationProperties.class" + }, + { + "glob": "org/springframework/boot/context/properties/EnableConfigurationPropertiesRegistrar.class" + }, + { + "glob": "org/springframework/boot/gson/autoconfigure$GsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/gson/autoconfigure/GsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition$ReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$NotReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration$StringHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$Jackson2XmlMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$MappingJackson2XmlHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition$Jackson2Preferred.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition$JacksonUnavailable.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonJsonHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration$JacksonXmlHttpMessageConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$AbstractMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$CborConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonAutoConfigurationRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration$StandardJsonMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonJsonMapperBuilderCustomizerConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JacksonMixinConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration$ProblemDetailJsonMapperBuilderCustomizer.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$JsonProblemDetailsConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration$XmlConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson2/autoconfigure$Jackson2AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jackson2/autoconfigure/Jackson2AutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$EmbeddedDatabaseConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceAvailableCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$ExplicitType.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition$PooledDataSourceAvailable.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceCondition.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration$PooledDataSourceConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceCheckpointRestoreConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Dbcp2.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Generic.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Hikari.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$OracleUcp.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceConfiguration$Tomcat.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceInitializationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceJmxConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$CommonsDbcp2PoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariDataSourcePoolMetadataRuntimeHints.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$OracleUcpPoolDataSourceMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration$TomcatDataSourcePoolMetadataProviderConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourcePoolMetadataProvidersConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration$JdbcTransactionManagerConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/DataSourceTransactionManagerAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcClientAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JdbcTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/JndiDataSourceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jdbc/autoconfigure/NamedParameterJdbcTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/jsonb/autoconfigure$JsonbAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/jsonb/autoconfigure/JsonbAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure$KotlinxSerializationJsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/kotlinx/serialization/json/autoconfigure/KotlinxSerializationJsonAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$CompositeMeterRegistryAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure$MetricsAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/CompositeMeterRegistryAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple$SimpleMetricsExportAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/metrics/autoconfigure/export/simple/SimpleMetricsExportAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/observation/autoconfigure$ObservationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/micrometer/observation/autoconfigure/ObservationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/persistence/autoconfigure/PersistenceExceptionTranslationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/rsocket/autoconfigure$RSocketMessagingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/rsocket/autoconfigure/RSocketMessagingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$MissingAlternative.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$NameConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured$PasswordConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/MissingAlternativeUserDetailsManagerOrUserPropertiesConfigured.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$RSocketSecurityEnabledCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication$ReactiveWebApplicationCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration$RSocketEnabledOrReactiveWebApplication.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/ReactiveUserDetailsServiceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration$SecurityDataConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/SecurityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/UserDetailsServiceAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ConditionalOnDefaultWebSecurity.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Beans.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition$Classes.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/DefaultWebSecurityCondition.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/SecurityFilterAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$EnableWebSecurityConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$PathPatternRequestMatcherBuilderConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration$SecurityFilterChainConfiguration.class" + }, + { + "glob": "org/springframework/boot/security/autoconfigure/web/servlet/ServletWebSecurityAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/servlet/autoconfigure/HttpEncodingAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/servlet/autoconfigure/MultipartAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/sql/autoconfigure/init/ConditionalOnSqlInitialization.class" + }, + { + "glob": "org/springframework/boot/sql/init/dependency/DatabaseInitializationDependencyConfigurer.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration$TomcatWebSocketConfiguration.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/TomcatWebServerConfiguration.class" + }, + { + "glob": "org/springframework/boot/tomcat/autoconfigure/servlet/TomcatServletWebServerAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$AspectJTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$CglibAutoProxyConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration$JdkDynamicAutoProxyConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$EnableTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration$TransactionTemplateConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/transaction/autoconfigure/TransactionManagerCustomizationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/validation/autoconfigure/PrimaryDefaultValidatorPostProcessor.class" + }, + { + "glob": "org/springframework/boot/validation/autoconfigure/ValidationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration$BeanPostProcessorsRegistrar.class" + }, + { + "glob": "org/springframework/boot/web/server/autoconfigure/servlet/ServletWebServerConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DefaultDispatcherServletCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/DispatcherServletAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainCustomizerConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceChainResourceHandlerRegistrationCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$ResourceHandlerRegistrationCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration$WelcomePageHandlerMappingFactory.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration$MeterFilterConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/WebMvcObservationAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorPageCustomizer.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$ErrorTemplateMissingCondition.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$PreserveErrorControllerTargetClassPostProcessor.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$StaticView.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/autoconfigure/error/ErrorMvcAutoConfiguration.class" + }, + { + "glob": "org/springframework/boot/webmvc/error/ErrorController.class" + }, + { + "glob": "org/springframework/context/ApplicationContextAware.class" + }, + { + "glob": "org/springframework/context/EnvironmentAware.class" + }, + { + "glob": "org/springframework/context/ResourceLoaderAware.class" + }, + { + "glob": "org/springframework/context/annotation/AdviceModeImportSelector.class" + }, + { + "glob": "org/springframework/context/annotation/AutoProxyRegistrar.class" + }, + { + "glob": "org/springframework/context/annotation/Conditional.class" + }, + { + "glob": "org/springframework/context/annotation/Configuration.class" + }, + { + "glob": "org/springframework/context/annotation/Import.class" + }, + { + "glob": "org/springframework/context/annotation/ImportAware.class" + }, + { + "glob": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class" + }, + { + "glob": "org/springframework/context/annotation/ImportRuntimeHints.class" + }, + { + "glob": "org/springframework/context/annotation/Lazy.class" + }, + { + "glob": "org/springframework/context/annotation/Role.class" + }, + { + "glob": "org/springframework/core/annotation/Order.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$AuthenticationManagerDelegator.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$LazyPasswordEncoder.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/authentication/configuration/EnableGlobalAuthentication.class" + }, + { + "glob": "org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/EnableWebSecurity.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration$LazyPasswordEncoder.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/ObservationConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/ObservationImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$AnnotationAwareOrderComparator.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration$CompositeFilterChainProxy.class" + }, + { + "glob": "org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class" + }, + { + "glob": "org/springframework/security/core/userdetails/UserDetailsService.class" + }, + { + "glob": "org/springframework/transaction/annotation/AbstractTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/transaction/annotation/EnableTransactionManagement.class" + }, + { + "glob": "org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class" + }, + { + "glob": "org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.class" + }, + { + "glob": "org/springframework/web/bind/annotation/ControllerAdvice.class" + }, + { + "glob": "org/springframework/web/bind/annotation/Mapping.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RequestMapping.class" + }, + { + "glob": "org/springframework/web/bind/annotation/ResponseBody.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RestController.class" + }, + { + "glob": "org/springframework/web/bind/annotation/RestControllerAdvice.class" + }, + { + "glob": "org/springframework/web/context/ServletContextAware.class" + }, + { + "glob": "org/springframework/web/servlet/HandlerInterceptor.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport$NoOpValidator.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.class" + }, + { + "glob": "org/springframework/web/servlet/config/annotation/WebMvcConfigurer.class" + }, + { + "glob": "org/sqlite/native/Windows/x86_64/sqlitejdbc.dll" + }, + { + "glob": "schema-all.sql" + }, + { + "glob": "schema.sql" + }, + { + "glob": "spring.properties" + }, + { + "glob": "sqlite-jdbc.properties" + }, + { + "glob": "static/.well-known/appspecific/com.chrome.devtools.json" + }, + { + "glob": "static/assets/DashboardView-BKGHLnEP.js" + }, + { + "glob": "static/assets/DashboardView-DHdOv7sv.css" + }, + { + "glob": "static/assets/ProxyView-BNsKsUAn.css" + }, + { + "glob": "static/assets/ProxyView-CdQhhSdP.js" + }, + { + "glob": "static/assets/index-C9wU1arO.js" + }, + { + "glob": "static/assets/index-sMwk8kPl.css" + }, + { + "glob": "static/favicon.png" + }, + { + "glob": "static/index.html" + }, + { + "glob": "static/logo.png" + }, + { + "glob": "static/system/content.css.map" + }, + { + "glob": "static/system/sidebar.css.map" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" + }, + { + "module": "java.base", + "glob": "jdk/internal/icu/impl/data/icudt76b/uprops.icu" + }, + { + "module": "java.base", + "glob": "sun/net/idn/uidna.spp" + }, + { + "bundle": "jakarta.servlet.LocalStrings" + }, + { + "bundle": "jakarta.servlet.http.LocalStrings" + }, + { + "bundle": "org.apache.catalina.authenticator.LocalStrings" + }, + { + "bundle": "org.apache.catalina.authenticator.jaspic.LocalStrings" + }, + { + "bundle": "org.apache.catalina.connector.LocalStrings" + }, + { + "bundle": "org.apache.catalina.core.LocalStrings" + }, + { + "bundle": "org.apache.catalina.deploy.LocalStrings" + }, + { + "bundle": "org.apache.catalina.loader.LocalStrings" + }, + { + "bundle": "org.apache.catalina.mapper.LocalStrings" + }, + { + "bundle": "org.apache.catalina.mbeans.LocalStrings" + }, + { + "bundle": "org.apache.catalina.realm.LocalStrings" + }, + { + "bundle": "org.apache.catalina.session.LocalStrings" + }, + { + "bundle": "org.apache.catalina.startup.LocalStrings" + }, + { + "bundle": "org.apache.catalina.util.LocalStrings" + }, + { + "bundle": "org.apache.catalina.valves.LocalStrings" + }, + { + "bundle": "org.apache.catalina.webresources.LocalStrings" + }, + { + "bundle": "org.apache.coyote.LocalStrings" + }, + { + "bundle": "org.apache.coyote.http11.LocalStrings" + }, + { + "bundle": "org.apache.coyote.http11.filters.LocalStrings" + }, + { + "bundle": "org.apache.naming.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.buf.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.compat.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.descriptor.web.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.http.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.http.parser.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.modeler.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.net.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.scan.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.util.threads.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.websocket.LocalStrings" + }, + { + "bundle": "org.apache.tomcat.websocket.server.LocalStrings" + } + ] +} \ No newline at end of file diff --git a/push-server-core/src/main/resources/META-INF/native-image/reflect-config.json b/push-server-core/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..94583e7 --- /dev/null +++ b/push-server-core/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,38 @@ +[ + { + "name": "com.baomidou.mybatisplus.core.override.MybatisMapperProxy", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "org.apache.ibatis.binding.MapperProxy", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.SSW", + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.PSW", + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.PS", + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.SS", + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.SSL", + "allDeclaredConstructors": true + }, + { + "name": "com.github.benmanes.caffeine.cache.PSL", + "allDeclaredConstructors": true + } +] \ No newline at end of file diff --git a/push-server-core/src/main/resources/application-dev.yml b/push-server-core/src/main/resources/application-dev.yml new file mode 100644 index 0000000..8e97390 --- /dev/null +++ b/push-server-core/src/main/resources/application-dev.yml @@ -0,0 +1,16 @@ +push: + auth: + key: 替换为自己的key + security: + block-minutes: 30 + fail-window-minutes: 5 + max-fails: 5 + rate-limit-capacity: 10 + rate-limit-qps: 1 + wecom: + app-key: + app-secret: + agent-id: + webhook-url: +server: + port: 8000 diff --git a/push-server-core/src/main/resources/application-prod.yml b/push-server-core/src/main/resources/application-prod.yml new file mode 100644 index 0000000..8e97390 --- /dev/null +++ b/push-server-core/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +push: + auth: + key: 替换为自己的key + security: + block-minutes: 30 + fail-window-minutes: 5 + max-fails: 5 + rate-limit-capacity: 10 + rate-limit-qps: 1 + wecom: + app-key: + app-secret: + agent-id: + webhook-url: +server: + port: 8000 diff --git a/push-server-core/src/main/resources/application.yml b/push-server-core/src/main/resources/application.yml new file mode 100644 index 0000000..5f77ccd --- /dev/null +++ b/push-server-core/src/main/resources/application.yml @@ -0,0 +1,24 @@ +spring: + application: + name: push-server + profiles: + active: ${SPRING_PROFILES_ACTIVE:@spring.profiles.active@} + config: + additional-location: "optional:file:./config/" + main: + allow-bean-definition-overriding: true + web: + resources: + static-locations: "file:./dist/, classpath:/static/" # 优先找本地 dist,找不到找包内 static + +info: + app: + version: @project.version@ + +server: + port: 8000 +# yDEHjQUcqGgD +logging: + level: + org.springframework.security: DEBUG + org.springframework.web: DEBUG diff --git a/push-server-core/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java b/push-server-core/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java new file mode 100644 index 0000000..b761230 --- /dev/null +++ b/push-server-core/src/test/java/dev/qingzhou/pushserver/PushServerApplicationTests.java @@ -0,0 +1,13 @@ +package dev.qingzhou.pushserver; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PushServerApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/push-server-core/src/test/java/dev/qingzhou/pushserver/service/DashboardServiceTest.java b/push-server-core/src/test/java/dev/qingzhou/pushserver/service/DashboardServiceTest.java new file mode 100644 index 0000000..8166cfe --- /dev/null +++ b/push-server-core/src/test/java/dev/qingzhou/pushserver/service/DashboardServiceTest.java @@ -0,0 +1,43 @@ +package dev.qingzhou.pushserver.service; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse; +import dev.qingzhou.pushserver.service.impl.DashboardServiceImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class DashboardServiceTest { + + private DashboardService dashboardService; + + @Mock + private PortalMessageLogService messageLogService; + + @Mock + private PortalWecomAppService appService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + dashboardService = new DashboardServiceImpl(messageLogService, appService); + } + + @Test + void testFetchStatsWithNoLogs() { + Long userId = 1L; + when(messageLogService.count(any(Wrapper.class))).thenReturn(0L); + when(appService.count(any(Wrapper.class))).thenReturn(0L); + when(messageLogService.getOne(any(Wrapper.class), any(Boolean.class))).thenReturn(null); + + DashboardStatsResponse stats = dashboardService.fetchStats(userId); + + assertEquals(0L, stats.getTodayTotal()); + assertEquals(100.0, stats.getSuccessRate(), "Success rate should be 100% when no logs exist"); + } +} From 32604ce2720394cd17db3b2cd251b2170103979d Mon Sep 17 00:00:00 2001 From: ma Date: Mon, 2 Feb 2026 20:44:04 +0800 Subject: [PATCH 03/15] =?UTF-8?q?feat(plugin):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=B3=A8=E5=86=8C=E3=80=81=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=92=8C=E4=BB=A4=E7=89=8C=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 16 ++ push-server-api/pom.xml | 59 +++++++- .../push/api/model/ActionContext.java | 25 ++++ .../qingzhou/push/api/model/ConfigField.java | 17 +++ .../qingzhou/push/api/model/ConfigType.java | 10 ++ .../qingzhou/push/api/model/PluginMeta.java | 16 ++ .../qingzhou/push/api/model/PushMessage.java | 37 +++++ .../qingzhou/push/api/model/SelectOption.java | 14 ++ .../dev/qingzhou/push/api/spi/PushPlugin.java | 22 +++ .../dev/qingzhou/push/api/spi/PushSender.java | 11 ++ .../src/main/proto/plugin_gateway.proto | 140 ++++++++++++++++++ push-server-core/pom.xml | 5 + .../config/PortalSchemaInitializer.java | 12 ++ .../controller/PortalPluginController.java | 65 ++++++++ .../mapper/portal/PortalPluginMapper.java | 9 ++ .../dto/portal/PortalPluginCreateRequest.java | 18 +++ .../model/entity/portal/PortalPlugin.java | 26 ++++ .../model/vo/portal/PortalPluginVo.java | 17 +++ .../service/PortalPluginService.java | 23 +++ .../service/impl/PortalPluginServiceImpl.java | 118 +++++++++++++++ .../qingzhou/pushserver/utils/TokenUtils.java | 18 +++ 21 files changed, 677 insertions(+), 1 deletion(-) create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/ActionContext.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigField.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigType.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/PluginMeta.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/PushMessage.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/model/SelectOption.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushPlugin.java create mode 100644 push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushSender.java create mode 100644 push-server-api/src/main/proto/plugin_gateway.proto create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPluginCreateRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPlugin.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalPluginService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/utils/TokenUtils.java diff --git a/pom.xml b/pom.xml index 48d779c..331a723 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,22 @@ push-core 1.0.0 + + io.grpc + grpc-protobuf + 1.78.0 + + + + io.grpc + grpc-stub + 1.78.0 + + + io.grpc + grpc-netty-shaded + 1.78.0 + diff --git a/push-server-api/pom.xml b/push-server-api/pom.xml index 29731cf..453f96f 100644 --- a/push-server-api/pom.xml +++ b/push-server-api/pom.xml @@ -14,6 +14,63 @@ 25 25 + 1.78.0 + 3.24.0 - + + + + io.grpc + grpc-protobuf + compile + + + + io.grpc + grpc-stub + compile + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.projectlombok + lombok + provided + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + + \ No newline at end of file diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/ActionContext.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ActionContext.java new file mode 100644 index 0000000..1d635ab --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ActionContext.java @@ -0,0 +1,25 @@ +package dev.qingzhou.push.api.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Data +@Builder +public class ActionContext { + private String eventId; + private String appId; + private String userId; + private String userName; + + private String type; // TEXT, CLICK + private String content; // 具体内容 + + // 运行时配置 (从平台透传而来,不要持久化) + private Map pluginConfig; + + public String getConfig(String key) { + return pluginConfig != null ? pluginConfig.get(key) : null; + } +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigField.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigField.java new file mode 100644 index 0000000..e3a2aa4 --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigField.java @@ -0,0 +1,17 @@ +package dev.qingzhou.push.api.model; + +import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data +@Builder +public class ConfigField { + private String name; + private String label; + private ConfigType type; + private String defaultValue; + private boolean required; + private String description; + private List options; +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigType.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigType.java new file mode 100644 index 0000000..35a3794 --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/ConfigType.java @@ -0,0 +1,10 @@ +package dev.qingzhou.push.api.model; + +public enum ConfigType { + TEXT, + PASSWORD, + BOOLEAN, + SELECT, + NUMBER, + TEXTAREA +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/PluginMeta.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/PluginMeta.java new file mode 100644 index 0000000..a03fd76 --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/PluginMeta.java @@ -0,0 +1,16 @@ +package dev.qingzhou.push.api.model; + +import lombok.Builder; +import lombok.Data; +import java.util.List; + +@Data +@Builder +public class PluginMeta { + private String id; + private String version; + private String name; + private String description; + private int maxConcurrency; + private List configFields; +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/PushMessage.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/PushMessage.java new file mode 100644 index 0000000..4dae606 --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/PushMessage.java @@ -0,0 +1,37 @@ +package dev.qingzhou.push.api.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PushMessage { + private String appId; + private String targetUserId; + private String requestId; + + private String type; // 消息类型: text, markdown, image, news 等 + private String content; // 文本内容 / Markdown 内容 + + private String title; // 标题 + private String url; // 跳转链接 + private String mediaId; // 媒体ID (如图片/视频) + + private List
articles; // 图文列表 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Article { + private String title; + private String description; + private String url; + private String picUrl; + } +} \ No newline at end of file diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/model/SelectOption.java b/push-server-api/src/main/java/dev/qingzhou/push/api/model/SelectOption.java new file mode 100644 index 0000000..f1fc08f --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/model/SelectOption.java @@ -0,0 +1,14 @@ +package dev.qingzhou.push.api.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SelectOption { + private String label; + private String value; + private String description; +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushPlugin.java b/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushPlugin.java new file mode 100644 index 0000000..a945897 --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushPlugin.java @@ -0,0 +1,22 @@ +package dev.qingzhou.push.api.spi; + +import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.PluginMeta; + +public interface PushPlugin { + + // 获取插件元数据 (ID, Version, ConfigDefinition) + PluginMeta getMeta(); + + // 路由判断:决定是否处理该消息 + boolean supports(ActionContext context); + + // 初始化:注入发送器能力 + void init(PushSender sender); + + // 核心业务逻辑 + // 约定:无返回值,处理结果必须通过 sender 异步发送 + void handle(ActionContext context); + + default void shutdown() {} +} diff --git a/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushSender.java b/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushSender.java new file mode 100644 index 0000000..2da29ef --- /dev/null +++ b/push-server-api/src/main/java/dev/qingzhou/push/api/spi/PushSender.java @@ -0,0 +1,11 @@ +package dev.qingzhou.push.api.spi; + +import dev.qingzhou.push.api.model.PushMessage; + +public interface PushSender { + /** + * 发送消息 + * @param message 消息对象 + */ + void send(PushMessage message); +} \ No newline at end of file diff --git a/push-server-api/src/main/proto/plugin_gateway.proto b/push-server-api/src/main/proto/plugin_gateway.proto new file mode 100644 index 0000000..003cc6c --- /dev/null +++ b/push-server-api/src/main/proto/plugin_gateway.proto @@ -0,0 +1,140 @@ +syntax = "proto3"; + +package dev.qingzhou.push.api.grpc; + +option java_multiple_files = true; +option java_package = "dev.qingzhou.push.api.grpc"; +option java_outer_classname = "PluginGatewayProto"; + +// Service: 插件网关 +service PluginGateway { + // 建立双向流 (Metadata: Authorization: Bearer ) + rpc Connect (stream PluginUpstreamPacket) returns (stream PlatformDownstreamPacket); +} + +// --- 基础信封 (Observability) --- +message PacketHeader { + string trace_id = 1; // 分布式追踪 ID + int64 timestamp = 2; // 时间戳 + string plugin_key = 3; // 插件标识 + int32 protocol_ver = 4; // 协议版本 +} + +// --- 顶层包 --- +message PluginUpstreamPacket { + PacketHeader header = 1; + oneof payload { + RegisterRequest register = 2; + Heartbeat heartbeat = 3; + ActionAck action_ack = 4; // 任务回执 + PushRequest push_request = 5; // 主动推送 + } +} + +message PlatformDownstreamPacket { + PacketHeader header = 1; + oneof payload { + RegisterResponse register_ack = 2; + UserActionEvent action_event = 3; // 下发任务 + PushResponse push_response = 4; // 推送确认 + } +} + +// --- 1. 注册与配置元数据 --- +enum ConfigType { + CONFIG_TYPE_UNSPECIFIED = 0; + CONFIG_TYPE_TEXT = 1; + CONFIG_TYPE_PASSWORD = 2; + CONFIG_TYPE_BOOLEAN = 3; + CONFIG_TYPE_SELECT = 4; + CONFIG_TYPE_NUMBER = 5; + CONFIG_TYPE_TEXTAREA = 6; +} + +message SelectOption { + string value = 1; + string label = 2; + string description = 3; +} + +message ConfigDefinition { + string name = 1; + string label = 2; + ConfigType type = 3; + string default_value = 4; + bool required = 5; + string description = 6; + repeated SelectOption options = 7; +} + +message RegisterRequest { + string plugin_key = 1; + string plugin_version = 2; + repeated string commands = 3; // 路由提示 + int32 max_concurrency = 4; // 流控声明 + repeated ConfigDefinition config_definitions = 5; +} + +message RegisterResponse { + bool success = 1; + string reason = 2; + int32 server_protocol_ver = 3; +} + +// --- 2. 心跳 --- +message Heartbeat { + int32 current_inflight = 1; // 当前负载 + int64 uptime_seconds = 2; +} + +// --- 3. 任务下发 --- +enum UserActionType { + USER_ACTION_TYPE_UNSPECIFIED = 0; + USER_ACTION_TYPE_TEXT = 1; + USER_ACTION_TYPE_CLICK = 2; +} + +message UserActionEvent { + string event_id = 1; + string app_id = 2; + string user_id = 3; + string user_name = 4; + UserActionType type = 5; + string content = 6; + string channel_source = 7; + map plugin_config = 8; // 运行时配置注入 +} + +// --- 4. 可靠性回执 --- +enum AckStatus { + ACK_STATUS_RECEIVED = 0; + ACK_STATUS_PROCESSING = 1; + ACK_STATUS_SUCCESS = 2; + ACK_STATUS_FAILED = 3; +} + +message ActionAck { + string event_id = 1; + AckStatus status = 2; + string message = 3; +} + +// --- 5. 消息推送 --- +enum PushContentType { + PUSH_CONTENT_TYPE_TEXT = 0; + PUSH_CONTENT_TYPE_MARKDOWN = 1; +} + +message PushRequest { + string request_id = 1; + string app_id = 2; + string target_user_id = 3; + PushContentType type = 4; + string content = 5; +} + +message PushResponse { + bool success = 1; + string error_code = 2; + string error_msg = 3; +} diff --git a/push-server-core/pom.xml b/push-server-core/pom.xml index 5a9d8c8..079047c 100644 --- a/push-server-core/pom.xml +++ b/push-server-core/pom.xml @@ -72,6 +72,11 @@ lombok provided + + io.grpc + grpc-netty-shaded + 1.78.0 + diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java index 4f9f586..0b35103 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java @@ -118,6 +118,18 @@ CREATE TABLE IF NOT EXISTS v2_message_log ( created_at INTEGER NOT NULL ) """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_plugin ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plugin_key TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + description TEXT, + token TEXT NOT NULL, + status INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL + ) + """); try (Connection connection = dataSource.getConnection()) { try (Statement statement = connection.createStatement()) { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginController.java new file mode 100644 index 0000000..ee13408 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginController.java @@ -0,0 +1,65 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.model.dto.portal.PortalPluginCreateRequest; +import dev.qingzhou.pushserver.model.vo.portal.PortalPluginVo; +import dev.qingzhou.pushserver.service.PortalPluginService; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2/plugin") +@RequiredArgsConstructor +public class PortalPluginController { + + private final PortalPluginService pluginService; + + @PostMapping("/create") + public PortalResponse create(@Valid @RequestBody PortalPluginCreateRequest request) { + String token = pluginService.createPlugin(request); + return PortalResponse.ok(token); + } + + @PostMapping("/reset-token") + public PortalResponse resetToken(@RequestBody Map payload) { + Integer id = payload.get("id"); + if (id == null) { + return PortalResponse.fail("ID is required"); + } + String newToken = pluginService.resetToken(id); + return PortalResponse.ok(newToken); + } + + @PostMapping("/status") + public PortalResponse switchStatus(@RequestBody Map payload) { + Integer id = payload.get("id"); + Integer status = payload.get("status"); + if (id == null || status == null) { + return PortalResponse.fail("ID and status are required"); + } + pluginService.switchStatus(id, status); + return PortalResponse.ok(null); + } + + @GetMapping("/list") + public PortalResponse> list() { + return PortalResponse.ok(pluginService.listPlugins()); + } + + @PostMapping("/delete") + public PortalResponse delete(@RequestBody Map payload) { + Integer id = payload.get("id"); + if (id == null) { + return PortalResponse.fail("ID is required"); + } + pluginService.deletePlugin(id); + return PortalResponse.ok(null); + } +} \ No newline at end of file diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.java new file mode 100644 index 0000000..faaeff2 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPlugin; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalPluginMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPluginCreateRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPluginCreateRequest.java new file mode 100644 index 0000000..0c9bcd0 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/PortalPluginCreateRequest.java @@ -0,0 +1,18 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +@Data +public class PortalPluginCreateRequest { + + @NotBlank(message = "Plugin Key cannot be empty") + @Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "Plugin Key can only contain letters, numbers, underscores and hyphens") + private String pluginKey; + + @NotBlank(message = "Name cannot be empty") + private String name; + + private String description; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPlugin.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPlugin.java new file mode 100644 index 0000000..9af0124 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPlugin.java @@ -0,0 +1,26 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_plugin") +public class PortalPlugin { + @TableId(type = IdType.AUTO) + private Integer id; + + private String pluginKey; + private String name; + private String description; + private String token; + + /** + * 1: Enabled, 0: Disabled + */ + private Integer status; + + private Long createdAt; + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java new file mode 100644 index 0000000..6284464 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PortalPluginVo { + private Integer id; + private String pluginKey; + private String name; + private String description; + private Integer status; // 1: Enabled, 0: Disabled + private Long createdAt; + // 注意:Token 通常不在此处返回,或者只返回脱敏后的版本 + private Boolean isConnected; // 预留字段:当前是否在线 +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalPluginService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalPluginService.java new file mode 100644 index 0000000..b837790 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalPluginService.java @@ -0,0 +1,23 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.pushserver.model.dto.portal.PortalPluginCreateRequest; +import dev.qingzhou.pushserver.model.vo.portal.PortalPluginVo; +import java.util.List; + +public interface PortalPluginService { + + // 1. 注册插件,返回包含 Token 的明文 + String createPlugin(PortalPluginCreateRequest request); + + // 2. 重置 Token,返回新 Token + String resetToken(Integer id); + + // 3. 切换状态 (启用/禁用) + void switchStatus(Integer id, Integer status); + + // 4. 插件列表 + List listPlugins(); + + // 5. 删除插件 + void deletePlugin(Integer id); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java new file mode 100644 index 0000000..67b3d46 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java @@ -0,0 +1,118 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import dev.qingzhou.pushserver.exception.PortalException; +import dev.qingzhou.pushserver.exception.PortalStatus; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper; +import dev.qingzhou.pushserver.model.dto.portal.PortalPluginCreateRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalPlugin; +import dev.qingzhou.pushserver.model.vo.portal.PortalPluginVo; +import dev.qingzhou.pushserver.service.PortalPluginService; +import dev.qingzhou.pushserver.utils.TokenUtils; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PortalPluginServiceImpl implements PortalPluginService { + + private final PortalPluginMapper pluginMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public String createPlugin(PortalPluginCreateRequest request) { + // 1. Check duplicate key + if (pluginMapper.exists(new LambdaQueryWrapper() + .eq(PortalPlugin::getPluginKey, request.getPluginKey()))) { + throw new PortalException(PortalStatus.CONFLICT, "Plugin key already exists"); + } + + // 2. Generate Token + String token = TokenUtils.generateToken("sk_live_"); + + // 3. Save + PortalPlugin plugin = new PortalPlugin(); + plugin.setPluginKey(request.getPluginKey()); + plugin.setName(request.getName()); + plugin.setDescription(request.getDescription()); + plugin.setToken(token); + plugin.setStatus(1); // Default Enabled + plugin.setCreatedAt(System.currentTimeMillis()); + plugin.setUpdatedAt(System.currentTimeMillis()); + + pluginMapper.insert(plugin); + + return token; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String resetToken(Integer id) { + PortalPlugin plugin = pluginMapper.selectById(id); + if (plugin == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "Plugin not found"); + } + + String newToken = TokenUtils.generateToken("sk_live_"); + plugin.setToken(newToken); + plugin.setUpdatedAt(System.currentTimeMillis()); + pluginMapper.updateById(plugin); + + // TODO: If we have active gRPC connections for this plugin, we might want to terminate them. + + return newToken; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void switchStatus(Integer id, Integer status) { + PortalPlugin plugin = pluginMapper.selectById(id); + if (plugin == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "Plugin not found"); + } + + if (status != 0 && status != 1) { + throw new PortalException(PortalStatus.BAD_REQUEST, "Invalid status"); + } + + plugin.setStatus(status); + plugin.setUpdatedAt(System.currentTimeMillis()); + pluginMapper.updateById(plugin); + + // TODO: If disabled (status=0), disconnect active sessions. + } + + @Override + public List listPlugins() { + List list = pluginMapper.selectList(new LambdaQueryWrapper() + .orderByDesc(PortalPlugin::getCreatedAt)); + + return list.stream().map(p -> PortalPluginVo.builder() + .id(p.getId()) + .pluginKey(p.getPluginKey()) + .name(p.getName()) + .description(p.getDescription()) + .status(p.getStatus()) + .createdAt(p.getCreatedAt()) + .isConnected(false) // TODO: Check connection manager + .build()).collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePlugin(Integer id) { + PortalPlugin plugin = pluginMapper.selectById(id); + if (plugin == null) { + throw new PortalException(PortalStatus.NOT_FOUND, "Plugin not found"); + } + + // TODO: Check if any Apps are using this plugin before deleting. + + pluginMapper.deleteById(id); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/TokenUtils.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/TokenUtils.java new file mode 100644 index 0000000..4a58fd0 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/utils/TokenUtils.java @@ -0,0 +1,18 @@ +package dev.qingzhou.pushserver.utils; + +import java.security.SecureRandom; +import java.util.Base64; + +public class TokenUtils { + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + /** + * 生成带前缀的随机 Token (例如: sk_live_abc123...) + */ + public static String generateToken(String prefix) { + byte[] randomBytes = new byte[24]; + SECURE_RANDOM.nextBytes(randomBytes); + String randomStr = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); + return prefix + randomStr; + } +} From 3fd5dca01cbe0f2a448f0992674b84c64115a713 Mon Sep 17 00:00:00 2001 From: ma Date: Mon, 2 Feb 2026 21:41:06 +0800 Subject: [PATCH 04/15] =?UTF-8?q?feat(gRPC):=20=E6=96=B0=E5=A2=9E=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=20gRPC=20=E7=9A=84=E6=8F=92=E4=BB=B6=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E5=92=8C=E4=BA=8B=E4=BB=B6=E5=88=86=E5=8F=91=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/proto/plugin_gateway.proto | 15 ++ .../pushserver/config/GrpcServerConfig.java | 55 +++++++ .../qingzhou/pushserver/grpc/GrpcAdapter.java | 54 +++++++ .../grpc/PluginAuthInterceptor.java | 45 ++++++ .../grpc/PluginConnectionManager.java | 36 +++++ .../pushserver/grpc/PluginGatewayImpl.java | 135 ++++++++++++++++++ .../grpc/PluginPacketDispatcher.java | 45 ++++++ .../model/vo/portal/PortalPluginVo.java | 1 + .../pushserver/plugin/GrpcPluginProxy.java | 58 ++++++++ .../plugin/builtin/WebhookPlugin.java | 99 +++++++++++++ .../service/PluginManagerService.java | 19 +++ .../impl/PluginManagerServiceImpl.java | 89 ++++++++++++ .../service/impl/PortalPluginServiceImpl.java | 64 ++++++--- .../service/impl/ServerPushSender.java | 28 ++++ 14 files changed, 725 insertions(+), 18 deletions(-) create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/GrpcServerConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/GrpcAdapter.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginAuthInterceptor.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginPacketDispatcher.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PluginManagerService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/ServerPushSender.java diff --git a/push-server-api/src/main/proto/plugin_gateway.proto b/push-server-api/src/main/proto/plugin_gateway.proto index 003cc6c..a97e544 100644 --- a/push-server-api/src/main/proto/plugin_gateway.proto +++ b/push-server-api/src/main/proto/plugin_gateway.proto @@ -123,6 +123,16 @@ message ActionAck { enum PushContentType { PUSH_CONTENT_TYPE_TEXT = 0; PUSH_CONTENT_TYPE_MARKDOWN = 1; + PUSH_CONTENT_TYPE_IMAGE = 2; + PUSH_CONTENT_TYPE_NEWS = 3; + PUSH_CONTENT_TYPE_TEXT_CARD = 4; +} + +message PushArticle { + string title = 1; + string description = 2; + string url = 3; + string pic_url = 4; } message PushRequest { @@ -131,6 +141,11 @@ message PushRequest { string target_user_id = 3; PushContentType type = 4; string content = 5; + + string title = 6; + string url = 7; + string media_id = 8; + repeated PushArticle articles = 9; } message PushResponse { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/GrpcServerConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/GrpcServerConfig.java new file mode 100644 index 0000000..649c70c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/GrpcServerConfig.java @@ -0,0 +1,55 @@ +package dev.qingzhou.pushserver.config; + +import dev.qingzhou.pushserver.grpc.PluginAuthInterceptor; +import dev.qingzhou.pushserver.grpc.PluginGatewayImpl; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class GrpcServerConfig { + + private final PluginGatewayImpl pluginGateway; + private final PluginAuthInterceptor authInterceptor; + + @Value("${grpc.server.port:9090}") + private int port; + + private Server server; + + @PostConstruct + public void start() throws IOException { + server = ServerBuilder.forPort(port) + .addService(io.grpc.ServerInterceptors.intercept(pluginGateway, authInterceptor)) + .build() + .start(); + log.info("gRPC Server started, listening on {}", port); + } + + @PreDestroy + public void stop() { + if (server != null) { + log.info("Shutting down gRPC Server..."); + server.shutdown(); + try { + if (!server.awaitTermination(30, TimeUnit.SECONDS)) { + server.shutdownNow(); + server.awaitTermination(5, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + server.shutdownNow(); + } + log.info("gRPC Server stopped."); + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/GrpcAdapter.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/GrpcAdapter.java new file mode 100644 index 0000000..5a4c680 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/GrpcAdapter.java @@ -0,0 +1,54 @@ +package dev.qingzhou.pushserver.grpc; + +import dev.qingzhou.push.api.grpc.*; +import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; +import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + +@Component +public class GrpcAdapter { + + public PushRequest toCorePushRequest(dev.qingzhou.push.api.grpc.PushRequest protoReq) { + PushRequest coreReq = new PushRequest(); + coreReq.setTarget(protoReq.getTargetUserId()); + coreReq.setContent(protoReq.getContent()); + coreReq.setTitle(protoReq.getTitle()); + coreReq.setUrl(protoReq.getUrl()); + coreReq.setMediaId(protoReq.getMediaId()); + + switch (protoReq.getType()) { + case PUSH_CONTENT_TYPE_MARKDOWN: + coreReq.setType("markdown"); + break; + case PUSH_CONTENT_TYPE_IMAGE: + coreReq.setType("image"); + break; + case PUSH_CONTENT_TYPE_NEWS: + coreReq.setType("news"); + break; + case PUSH_CONTENT_TYPE_TEXT_CARD: + coreReq.setType("textcard"); + break; + case PUSH_CONTENT_TYPE_TEXT: + default: + coreReq.setType("text"); + break; + } + + if (protoReq.getArticlesCount() > 0) { + List articles = new ArrayList<>(); + for (PushArticle protoArticle : protoReq.getArticlesList()) { + PushRequest.Article article = new PushRequest.Article(); + article.setTitle(protoArticle.getTitle()); + article.setDescription(protoArticle.getDescription()); + article.setUrl(protoArticle.getUrl()); + article.setPicUrl(protoArticle.getPicUrl()); + articles.add(article); + } + coreReq.setArticles(articles); + } + + return coreReq; + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginAuthInterceptor.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginAuthInterceptor.java new file mode 100644 index 0000000..38e4fa6 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginAuthInterceptor.java @@ -0,0 +1,45 @@ +package dev.qingzhou.pushserver.grpc; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPlugin; +import io.grpc.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PluginAuthInterceptor implements ServerInterceptor { + + public static final Context.Key PLUGIN_CONTEXT_KEY = Context.key("plugin"); + + private final PortalPluginMapper pluginMapper; + + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata headers, ServerCallHandler next) { + + String authHeader = headers.get(Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER)); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + call.close(Status.UNAUTHENTICATED.withDescription("Missing or invalid Authorization header"), headers); + return new ServerCall.Listener<>() {}; + } + + String token = authHeader.substring(7); + + // TODO: Add Caching here to avoid DB hit on every connect + PortalPlugin plugin = pluginMapper.selectOne(new LambdaQueryWrapper() + .eq(PortalPlugin::getToken, token) + .eq(PortalPlugin::getStatus, 1)); // Must be enabled + + if (plugin == null) { + call.close(Status.UNAUTHENTICATED.withDescription("Invalid Token"), headers); + return new ServerCall.Listener<>() {}; + } + + Context ctx = Context.current().withValue(PLUGIN_CONTEXT_KEY, plugin); + return Contexts.interceptCall(ctx, call, headers, next); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java new file mode 100644 index 0000000..b65b029 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java @@ -0,0 +1,36 @@ +package dev.qingzhou.pushserver.grpc; + +import dev.qingzhou.push.api.grpc.PlatformDownstreamPacket; +import io.grpc.stub.StreamObserver; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class PluginConnectionManager { + + // Key: Plugin Key, Value: Response Observer (to send data to plugin) + private final Map> connections = new ConcurrentHashMap<>(); + + public void register(String pluginKey, StreamObserver observer) { + log.info("Plugin connected: {}", pluginKey); + connections.put(pluginKey, observer); + } + + public void unregister(String pluginKey) { + if (pluginKey != null) { + log.info("Plugin disconnected: {}", pluginKey); + connections.remove(pluginKey); + } + } + + public StreamObserver get(String pluginKey) { + return connections.get(pluginKey); + } + + public boolean isConnected(String pluginKey) { + return connections.containsKey(pluginKey); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java new file mode 100644 index 0000000..d525c5f --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java @@ -0,0 +1,135 @@ +package dev.qingzhou.pushserver.grpc; + +import dev.qingzhou.push.api.grpc.*; +import dev.qingzhou.push.api.grpc.PluginGatewayGrpc.PluginGatewayImplBase; +import dev.qingzhou.pushserver.model.entity.portal.PortalPlugin; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import dev.qingzhou.pushserver.service.PushService; +import dev.qingzhou.pushserver.service.PluginManagerService; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.model.ConfigField; +import dev.qingzhou.push.api.model.ConfigType; +import dev.qingzhou.push.api.model.SelectOption; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PluginGatewayImpl extends PluginGatewayImplBase { + + private final PluginConnectionManager connectionManager; + private final GrpcAdapter grpcAdapter; + private final PushService pushService; + private final PluginManagerService pluginManagerService; + + @Override + public StreamObserver connect(StreamObserver responseObserver) { + + PortalPlugin plugin = PluginAuthInterceptor.PLUGIN_CONTEXT_KEY.get(); + if (plugin == null) { + // Should be caught by interceptor, but safety check + responseObserver.onError(io.grpc.Status.UNAUTHENTICATED.asRuntimeException()); + return new StreamObserver<>() { + @Override public void onNext(PluginUpstreamPacket value) {} + @Override public void onError(Throwable t) {} + @Override public void onCompleted() {} + }; + } + + final String pluginKey = plugin.getPluginKey(); + log.info("New connection stream from plugin: {}", pluginKey); + + return new StreamObserver() { + @Override + public void onNext(PluginUpstreamPacket packet) { + try { + handlePacket(pluginKey, packet, responseObserver); + } catch (Exception e) { + log.error("Error handling packet from {}", pluginKey, e); + } + } + + @Override + public void onError(Throwable t) { + log.warn("Stream error for plugin {}: {}", pluginKey, t.getMessage()); + cleanup(); + } + + @Override + public void onCompleted() { + log.info("Stream completed for plugin {}", pluginKey); + cleanup(); + responseObserver.onCompleted(); + } + + private void cleanup() { + connectionManager.unregister(pluginKey); + pluginManagerService.unregisterRemotePlugin(pluginKey); + } + }; + } + + private void handlePacket(String pluginKey, PluginUpstreamPacket packet, StreamObserver responseObserver) { + if (packet.hasRegister()) { + // Register logic + RegisterRequest req = packet.getRegister(); + if (!req.getPluginKey().equals(pluginKey)) { + log.warn("Plugin Key mismatch in register packet. Token says {}, packet says {}", pluginKey, req.getPluginKey()); + } + + // Convert RegisterRequest to PluginMeta + PluginMeta meta = PluginMeta.builder() + .id(pluginKey) + .version(req.getPluginVersion()) + .maxConcurrency(req.getMaxConcurrency()) + .build(); + + // Convert ConfigDefinitions if needed... (simplified here) + + connectionManager.register(pluginKey, responseObserver); + pluginManagerService.registerRemotePlugin(pluginKey, meta); + + responseObserver.onNext(PlatformDownstreamPacket.newBuilder() + .setHeader(PacketHeader.newBuilder() + .setTraceId(packet.getHeader().getTraceId()) + .setTimestamp(System.currentTimeMillis()) + .build()) + .setRegisterAck(RegisterResponse.newBuilder().setSuccess(true).build()) + .build()); + + } else if (packet.hasHeartbeat()) { + // Heartbeat logic - just log for now + } else if (packet.hasPushRequest()) { + dev.qingzhou.pushserver.model.dto.openapi.PushRequest coreReq = grpcAdapter.toCorePushRequest(packet.getPushRequest()); + + try { + pushService.push(coreReq); + + responseObserver.onNext(PlatformDownstreamPacket.newBuilder() + .setHeader(PacketHeader.newBuilder() + .setTraceId(packet.getHeader().getTraceId()) + .setTimestamp(System.currentTimeMillis()) + .build()) + .setPushResponse(PushResponse.newBuilder().setSuccess(true).build()) + .build()); + } catch (Exception e) { + log.error("Push failed for plugin {}", pluginKey, e); + responseObserver.onNext(PlatformDownstreamPacket.newBuilder() + .setHeader(PacketHeader.newBuilder() + .setTraceId(packet.getHeader().getTraceId()) + .setTimestamp(System.currentTimeMillis()) + .build()) + .setPushResponse(PushResponse.newBuilder() + .setSuccess(false) + .setErrorMsg(e.getMessage()) + .build()) + .build()); + } + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginPacketDispatcher.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginPacketDispatcher.java new file mode 100644 index 0000000..9f2044a --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginPacketDispatcher.java @@ -0,0 +1,45 @@ +package dev.qingzhou.pushserver.grpc; + +import dev.qingzhou.push.api.grpc.PacketHeader; +import dev.qingzhou.push.api.grpc.PlatformDownstreamPacket; +import dev.qingzhou.push.api.grpc.UserActionEvent; +import io.grpc.stub.StreamObserver; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Slf4j +@Component +@RequiredArgsConstructor +public class PluginPacketDispatcher { + + private final PluginConnectionManager connectionManager; + + public boolean sendUserAction(String pluginKey, UserActionEvent actionEvent) { + StreamObserver observer = connectionManager.get(pluginKey); + if (observer == null) { + log.warn("Cannot send action to plugin {}: No active connection", pluginKey); + return false; + } + + PlatformDownstreamPacket packet = PlatformDownstreamPacket.newBuilder() + .setHeader(PacketHeader.newBuilder() + .setTimestamp(System.currentTimeMillis()) + .setTraceId(UUID.randomUUID().toString()) + .setPluginKey(pluginKey) + .build()) + .setActionEvent(actionEvent) + .build(); + + try { + observer.onNext(packet); + return true; + } catch (Exception e) { + log.error("Failed to send packet to plugin {}", pluginKey, e); + connectionManager.unregister(pluginKey); + return false; + } + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java index 6284464..1e86d35 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalPluginVo.java @@ -14,4 +14,5 @@ public class PortalPluginVo { private Long createdAt; // 注意:Token 通常不在此处返回,或者只返回脱敏后的版本 private Boolean isConnected; // 预留字段:当前是否在线 + private Boolean isBuiltin; } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java new file mode 100644 index 0000000..b79b8d9 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java @@ -0,0 +1,58 @@ +package dev.qingzhou.pushserver.plugin; + +import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.spi.PushPlugin; +import dev.qingzhou.push.api.spi.PushSender; +import dev.qingzhou.pushserver.grpc.PluginPacketDispatcher; +import dev.qingzhou.push.api.grpc.UserActionEvent; +import dev.qingzhou.push.api.grpc.UserActionType; +import lombok.RequiredArgsConstructor; + +import java.util.UUID; + +@RequiredArgsConstructor +public class GrpcPluginProxy implements PushPlugin { + + private final String pluginKey; + private final PluginMeta meta; + private final PluginPacketDispatcher dispatcher; + + @Override + public PluginMeta getMeta() { + return meta; + } + + @Override + public boolean supports(ActionContext context) { + // 简单路由:真正的逻辑应在 Manager 层根据 commands 前缀匹配 + return true; + } + + @Override + public void init(PushSender sender) { + // Remote plugins don't need local sender injection + } + + @Override + public void handle(ActionContext context) { + UserActionEvent.Builder eventBuilder = UserActionEvent.newBuilder() + .setEventId(context.getEventId() == null ? UUID.randomUUID().toString() : context.getEventId()) + .setAppId(context.getAppId() == null ? "" : context.getAppId()) + .setUserId(context.getUserId() == null ? "" : context.getUserId()) + .setUserName(context.getUserName() == null ? "" : context.getUserName()) + .setContent(context.getContent() == null ? "" : context.getContent()); + + if ("CLICK".equalsIgnoreCase(context.getType())) { + eventBuilder.setType(UserActionType.USER_ACTION_TYPE_CLICK); + } else { + eventBuilder.setType(UserActionType.USER_ACTION_TYPE_TEXT); + } + + if (context.getPluginConfig() != null) { + eventBuilder.putAllPluginConfig(context.getPluginConfig()); + } + + dispatcher.sendUserAction(pluginKey, eventBuilder.build()); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java new file mode 100644 index 0000000..bb62a10 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java @@ -0,0 +1,99 @@ +package dev.qingzhou.pushserver.plugin.builtin; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.ConfigField; +import dev.qingzhou.push.api.model.ConfigType; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.spi.PushPlugin; +import dev.qingzhou.push.api.spi.PushSender; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WebhookPlugin implements PushPlugin { + + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate = new RestTemplate(); + + @Override + public PluginMeta getMeta() { + return PluginMeta.builder() + .id("builtin-webhook") + .name("Webhook") + .description("将消息事件以 JSON 格式推送到指定的 HTTP URL") + .version("1.0.0") + .maxConcurrency(100) + .configFields(List.of( + ConfigField.builder() + .name("url") + .label("Webhook URL") + .type(ConfigType.TEXT) + .required(true) + .description("接收事件的接口地址") + .build(), + ConfigField.builder() + .name("token") + .label("Verify Token") + .type(ConfigType.PASSWORD) + .required(false) + .description("可选:用于安全校验的 Token") + .build() + )) + .build(); + } + + @Override + public boolean supports(ActionContext context) { + // 只有当运行时配置中包含了 'url' 时,才由本插件处理 + return context.getConfig("url") != null && !context.getConfig("url").isBlank(); + } + + @Override + public void init(PushSender sender) { + // Webhook 通常是单向通知,不需要回复用户,所以这里不需要保存 sender + } + + @Override + public void handle(ActionContext context) { + String url = context.getConfig("url"); + String token = context.getConfig("token"); + + // 异步执行,避免阻塞主事件循环 + CompletableFuture.runAsync(() -> { + try { + Map payload = new HashMap<>(); + payload.put("eventId", context.getEventId()); + payload.put("type", context.getType()); + payload.put("userId", context.getUserId()); + payload.put("content", context.getContent()); + payload.put("timestamp", System.currentTimeMillis()); + + if (token != null) { + payload.put("token", token); + } + + log.debug("Sending webhook to {}", url); + String response = restTemplate.postForObject(url, payload, String.class); + log.debug("Webhook response: {}", response); + + } catch (Exception e) { + log.error("Failed to send webhook to {}: {}", url, e.getMessage()); + } + }); + } + + @Override + public void shutdown() { + // No resources to release + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PluginManagerService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PluginManagerService.java new file mode 100644 index 0000000..c7b4115 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PluginManagerService.java @@ -0,0 +1,19 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.spi.PushPlugin; +import java.util.List; + +public interface PluginManagerService { + + void registerRemotePlugin(String pluginKey, PluginMeta meta); + + void unregisterRemotePlugin(String pluginKey); + + List getAllPlugins(); + + List getLocalPlugins(); + + void dispatch(ActionContext context); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java new file mode 100644 index 0000000..f1328ba --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java @@ -0,0 +1,89 @@ +package dev.qingzhou.pushserver.service.impl; + +import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.spi.PushPlugin; +import dev.qingzhou.push.api.spi.PushSender; +import dev.qingzhou.pushserver.grpc.PluginPacketDispatcher; +import dev.qingzhou.pushserver.plugin.GrpcPluginProxy; +import dev.qingzhou.pushserver.service.PluginManagerService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import jakarta.annotation.PostConstruct; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PluginManagerServiceImpl implements PluginManagerService { + + private final List localPlugins; + private final PluginPacketDispatcher packetDispatcher; + private final PushSender pushSender; + + private final Map remotePlugins = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + log.info("Found {} local plugins", localPlugins.size()); + for (PushPlugin plugin : localPlugins) { + try { + plugin.init(pushSender); + log.info("Initialized local plugin: {}", plugin.getClass().getName()); + } catch (Exception e) { + log.error("Failed to init local plugin {}", plugin.getClass().getName(), e); + } + } + } + + @Override + public void registerRemotePlugin(String pluginKey, PluginMeta meta) { + log.info("Registering remote plugin: {}", pluginKey); + GrpcPluginProxy proxy = new GrpcPluginProxy(pluginKey, meta, packetDispatcher); + remotePlugins.put(pluginKey, proxy); + } + + @Override + public void unregisterRemotePlugin(String pluginKey) { + log.info("Unregistering remote plugin: {}", pluginKey); + remotePlugins.remove(pluginKey); + } + + @Override + public List getAllPlugins() { + List all = new ArrayList<>(localPlugins); + all.addAll(remotePlugins.values()); + return all; + } + + @Override + public List getLocalPlugins() { + return localPlugins; + } + + @Override + public void dispatch(ActionContext context) { + for (PushPlugin plugin : getAllPlugins()) { + try { + if (plugin.supports(context)) { + // Try to log name if meta is available, else class name + String name = (plugin.getMeta() != null && plugin.getMeta().getName() != null) + ? plugin.getMeta().getName() + : plugin.getClass().getSimpleName(); + + log.info("Dispatching event {} to plugin {}", context.getEventId(), name); + plugin.handle(context); + return; // Stop after first match + } + } catch (Exception e) { + log.error("Plugin failed to handle event", e); + } + } + log.warn("No plugin handled event {}", context.getEventId()); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java index 67b3d46..1a9210d 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.java @@ -16,16 +16,25 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import dev.qingzhou.pushserver.service.PluginManagerService; +import dev.qingzhou.pushserver.grpc.PluginConnectionManager; +import dev.qingzhou.push.api.spi.PushPlugin; +import dev.qingzhou.push.api.model.PluginMeta; +import java.util.ArrayList; + @Slf4j @Service @RequiredArgsConstructor public class PortalPluginServiceImpl implements PortalPluginService { private final PortalPluginMapper pluginMapper; + private final PluginManagerService pluginManagerService; + private final PluginConnectionManager connectionManager; @Override @Transactional(rollbackFor = Exception.class) public String createPlugin(PortalPluginCreateRequest request) { + // ... (unchanged) // 1. Check duplicate key if (pluginMapper.exists(new LambdaQueryWrapper() .eq(PortalPlugin::getPluginKey, request.getPluginKey()))) { @@ -49,7 +58,9 @@ public String createPlugin(PortalPluginCreateRequest request) { return token; } - + + // ... resetToken and switchStatus (unchanged - assume they are fine or I just overwrite whole class to be safe) + @Override @Transactional(rollbackFor = Exception.class) public String resetToken(Integer id) { @@ -63,8 +74,6 @@ public String resetToken(Integer id) { plugin.setUpdatedAt(System.currentTimeMillis()); pluginMapper.updateById(plugin); - // TODO: If we have active gRPC connections for this plugin, we might want to terminate them. - return newToken; } @@ -83,24 +92,46 @@ public void switchStatus(Integer id, Integer status) { plugin.setStatus(status); plugin.setUpdatedAt(System.currentTimeMillis()); pluginMapper.updateById(plugin); - - // TODO: If disabled (status=0), disconnect active sessions. } @Override public List listPlugins() { - List list = pluginMapper.selectList(new LambdaQueryWrapper() + // 1. Remote Plugins from DB + List dbList = pluginMapper.selectList(new LambdaQueryWrapper() .orderByDesc(PortalPlugin::getCreatedAt)); + + List result = new ArrayList<>(); + + for (PortalPlugin p : dbList) { + result.add(PortalPluginVo.builder() + .id(p.getId()) + .pluginKey(p.getPluginKey()) + .name(p.getName()) + .description(p.getDescription()) + .status(p.getStatus()) + .createdAt(p.getCreatedAt()) + .isConnected(connectionManager.isConnected(p.getPluginKey())) + .isBuiltin(false) + .build()); + } + + // 2. Local Plugins from Manager + List localPlugins = pluginManagerService.getLocalPlugins(); + for (PushPlugin p : localPlugins) { + PluginMeta meta = p.getMeta(); + result.add(PortalPluginVo.builder() + .id(-1) // Negative ID for builtin + .pluginKey(meta.getId()) + .name(meta.getName()) + .description(meta.getDescription()) + .status(1) // Always enabled for now, or manage state elsewhere + .createdAt(0L) + .isConnected(true) // Builtin is always connected + .isBuiltin(true) + .build()); + } - return list.stream().map(p -> PortalPluginVo.builder() - .id(p.getId()) - .pluginKey(p.getPluginKey()) - .name(p.getName()) - .description(p.getDescription()) - .status(p.getStatus()) - .createdAt(p.getCreatedAt()) - .isConnected(false) // TODO: Check connection manager - .build()).collect(Collectors.toList()); + return result; } @Override @@ -110,9 +141,6 @@ public void deletePlugin(Integer id) { if (plugin == null) { throw new PortalException(PortalStatus.NOT_FOUND, "Plugin not found"); } - - // TODO: Check if any Apps are using this plugin before deleting. - pluginMapper.deleteById(id); } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/ServerPushSender.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/ServerPushSender.java new file mode 100644 index 0000000..fddf850 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/ServerPushSender.java @@ -0,0 +1,28 @@ +package dev.qingzhou.pushserver.service.impl; + +import dev.qingzhou.push.api.model.PushMessage; +import dev.qingzhou.push.api.spi.PushSender; +import dev.qingzhou.pushserver.model.dto.openapi.PushRequest; +import dev.qingzhou.pushserver.service.PushService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ServerPushSender implements PushSender { + + private final PushService pushService; + + @Override + public void send(PushMessage message) { + PushRequest req = new PushRequest(); + req.setTarget(message.getTargetUserId()); + req.setType(message.getType() != null ? message.getType() : "text"); + req.setContent(message.getContent()); + req.setTitle(message.getTitle()); + req.setUrl(message.getUrl()); + // Map other fields... + + pushService.push(req); + } +} From 239ff0da0840ac6669524615c9fb547fa98c0b22 Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 21:44:39 +0800 Subject: [PATCH 05/15] =?UTF-8?q?feat(plugin):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E4=B8=8E?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 + push-server-core/pom.xml | 4 + .../config/PortalDatabaseConfig.java | 6 +- .../config/PortalSchemaInitializer.java | 58 ++++- .../controller/PortalAppPluginController.java | 52 ++++ .../controller/PortalPluginLogController.java | 70 ++++++ .../wecom/WecomCallbackController.java | 77 ++++-- .../grpc/PluginConnectionManager.java | 16 +- .../pushserver/grpc/PluginGatewayImpl.java | 47 +++- .../portal/PortalAppPluginConfigMapper.java | 9 + .../portal/PortalPluginActionLogMapper.java | 9 + .../PortalPluginHeartbeatLogMapper.java | 9 + .../portal/AppPluginConfigSaveRequest.java | 15 ++ .../entity/portal/PortalAppPluginConfig.java | 38 +++ .../entity/portal/PortalPluginActionLog.java | 24 ++ .../portal/PortalPluginHeartbeatLog.java | 17 ++ .../vo/portal/PortalAppPluginConfigVo.java | 25 ++ .../plugin/builtin/WebhookPlugin.java | 31 ++- .../service/PortalAppPluginService.java | 14 ++ .../impl/PluginManagerServiceImpl.java | 226 ++++++++++++++++-- .../impl/PortalAppPluginServiceImpl.java | 128 ++++++++++ 21 files changed, 831 insertions(+), 51 deletions(-) create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppPluginController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginLogController.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/AppPluginConfigSaveRequest.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppPluginConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginActionLog.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginHeartbeatLog.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppPluginConfigVo.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppPluginService.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppPluginServiceImpl.java diff --git a/pom.xml b/pom.xml index 331a723..f059f4c 100644 --- a/pom.xml +++ b/pom.xml @@ -46,6 +46,13 @@ + + com.baomidou + mybatis-plus-bom + 3.5.15 + pom + import + com.baomidou mybatis-plus-spring-boot4-starter diff --git a/push-server-core/pom.xml b/push-server-core/pom.xml index 079047c..24fcfdf 100644 --- a/push-server-core/pom.xml +++ b/push-server-core/pom.xml @@ -57,6 +57,10 @@ spring-boot-starter-webmvc-test test + + com.baomidou + mybatis-plus-jsqlparser + com.github.ben-manes.caffeine diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java index 74560f4..04178dc 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalDatabaseConfig.java @@ -1,6 +1,8 @@ package dev.qingzhou.pushserver.config; +import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.zaxxer.hikari.HikariDataSource; import java.nio.file.Files; import java.nio.file.Path; @@ -30,7 +32,9 @@ public DataSource dataSource(PortalDataSourceProperties properties) { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { - return new MybatisPlusInterceptor(); + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQLITE)); + return interceptor; } private String buildSqliteUrl(String filePath) { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java index 0b35103..db0da98 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/PortalSchemaInitializer.java @@ -92,11 +92,6 @@ CREATE TABLE IF NOT EXISTS v2_proxy_config ( CREATE UNIQUE INDEX IF NOT EXISTS idx_v2_app_api_key_hash ON v2_app_api_key(api_key_hash) """); - List alterStatements = new ArrayList<>(); - alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN api_key_plain TEXT NOT NULL DEFAULT ''"); - alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN rate_limit_per_minute INTEGER NOT NULL DEFAULT 0"); - alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN token TEXT"); - alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN encoding_aes_key TEXT"); statements.add(""" CREATE TABLE IF NOT EXISTS v2_message_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -130,6 +125,57 @@ CREATE TABLE IF NOT EXISTS v2_plugin ( updated_at INTEGER NOT NULL ) """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_app_plugin_config ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + app_id INTEGER NOT NULL, + plugin_key TEXT NOT NULL, + config_json TEXT, + status INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + UNIQUE(app_id, plugin_key) + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_plugin_action_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plugin_key TEXT NOT NULL, + event_id TEXT, + status INTEGER, + message TEXT, + app_id TEXT, + app_name TEXT, + user_id TEXT, + type TEXT, + content TEXT, + plugin_config TEXT, + created_at INTEGER NOT NULL, + UNIQUE(plugin_key, event_id) + ) + """); + statements.add(""" + CREATE TABLE IF NOT EXISTS v2_plugin_heartbeat_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plugin_key TEXT NOT NULL, + current_inflight INTEGER, + uptime_seconds INTEGER, + created_at INTEGER NOT NULL + ) + """); + + List alterStatements = new ArrayList<>(); + alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN api_key_plain TEXT NOT NULL DEFAULT ''"); + alterStatements.add("ALTER TABLE v2_app_api_key ADD COLUMN rate_limit_per_minute INTEGER NOT NULL DEFAULT 0"); + alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN token TEXT"); + alterStatements.add("ALTER TABLE v2_wecom_app ADD COLUMN encoding_aes_key TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN app_id TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN user_id TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN type TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN content TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN plugin_config TEXT"); + alterStatements.add("ALTER TABLE v2_plugin_action_log ADD COLUMN app_name TEXT"); + alterStatements.add("CREATE UNIQUE INDEX IF NOT EXISTS idx_v2_plugin_action_log_key_event ON v2_plugin_action_log(plugin_key, event_id)"); try (Connection connection = dataSource.getConnection()) { try (Statement statement = connection.createStatement()) { @@ -140,7 +186,7 @@ CREATE TABLE IF NOT EXISTS v2_plugin ( try { statement.execute(sql); } catch (Exception ignored) { - // Column may already exist; ignore migration errors to stay backward compatible. + // Column/index may already exist; ignore to stay backward compatible. } } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppPluginController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppPluginController.java new file mode 100644 index 0000000..d747d9e --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalAppPluginController.java @@ -0,0 +1,52 @@ +package dev.qingzhou.pushserver.controller; + +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.common.PortalSessionSupport; +import dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest; +import dev.qingzhou.pushserver.model.vo.portal.PortalAppPluginConfigVo; +import dev.qingzhou.pushserver.service.PortalAppPluginService; +import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/v2/apps/{appId}/plugins") +@RequiredArgsConstructor +public class PortalAppPluginController { + + private final PortalAppPluginService appPluginService; + + @GetMapping + public PortalResponse> list( + @PathVariable Long appId, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + return PortalResponse.ok(appPluginService.listByApp(userId, appId)); + } + + @PostMapping + public PortalResponse saveConfig( + @PathVariable Long appId, + @Valid @RequestBody AppPluginConfigSaveRequest request, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + appPluginService.saveConfig(userId, appId, request); + return PortalResponse.ok(null); + } + + @DeleteMapping("/{pluginKey}") + public PortalResponse deleteConfig( + @PathVariable Long appId, + @PathVariable String pluginKey, + HttpSession session + ) { + Long userId = PortalSessionSupport.requireUserId(session); + appPluginService.deleteConfig(userId, appId, pluginKey); + return PortalResponse.ok(null); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginLogController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginLogController.java new file mode 100644 index 0000000..dc1ea36 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalPluginLogController.java @@ -0,0 +1,70 @@ +package dev.qingzhou.pushserver.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog; +import dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 插件观测日志(ActionAck / Heartbeat)查询 + */ +@RestController +@RequestMapping("/v2/plugins") +@RequiredArgsConstructor +public class PortalPluginLogController { + + private final PortalPluginActionLogMapper actionLogMapper; + private final PortalPluginHeartbeatLogMapper heartbeatLogMapper; + + @GetMapping("/{pluginKey}/actions") + public PortalResponse> listActions( + @PathVariable String pluginKey, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + + int safePage = Math.max(page, 1); + int safePageSize = Math.max(Math.min(pageSize, 200), 1); + + Page p = actionLogMapper.selectPage( + Page.of(safePage, safePageSize), + new LambdaQueryWrapper() + .eq(PortalPluginActionLog::getPluginKey, pluginKey) + .orderByDesc(PortalPluginActionLog::getId) + ); + + PortalPageResponse resp = PortalPageResponse.of( + p.getRecords(), p.getTotal(), (int) p.getCurrent(), (int) p.getSize()); + return PortalResponse.ok(resp); + } + + @GetMapping("/{pluginKey}/heartbeats") + public PortalResponse> listHeartbeats( + @PathVariable String pluginKey, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int pageSize) { + + int safePage = Math.max(page, 1); + int safePageSize = Math.max(Math.min(pageSize, 200), 1); + + Page p = heartbeatLogMapper.selectPage( + Page.of(safePage, safePageSize), + new LambdaQueryWrapper() + .eq(PortalPluginHeartbeatLog::getPluginKey, pluginKey) + .orderByDesc(PortalPluginHeartbeatLog::getId) + ); + + PortalPageResponse resp = PortalPageResponse.of( + p.getRecords(), p.getTotal(), (int) p.getCurrent(), (int) p.getSize()); + return PortalResponse.ok(resp); + } +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java index e20ec99..20806f2 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java @@ -1,16 +1,22 @@ package dev.qingzhou.pushserver.controller.wecom; +import dev.qingzhou.push.api.model.ActionContext; import dev.qingzhou.pushserver.manager.wecom.AesException; import dev.qingzhou.pushserver.manager.wecom.WXBizMsgCrypt; import dev.qingzhou.pushserver.manager.wecom.WecomMessageParser; import dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload; import dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig; import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.service.PluginManagerService; import dev.qingzhou.pushserver.service.PortalCorpConfigService; import dev.qingzhou.pushserver.service.PortalWecomAppService; import lombok.extern.slf4j.Slf4j; +import org.springframework.core.task.TaskExecutor; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; +import java.util.UUID; + @Slf4j @RestController @RequestMapping("/v2/wecom/callback/{appId}") @@ -18,14 +24,21 @@ public class WecomCallbackController { private final PortalWecomAppService wecomAppService; private final PortalCorpConfigService corpConfigService; + private final PluginManagerService pluginManagerService; + private final TaskExecutor taskExecutor; - public WecomCallbackController(PortalWecomAppService wecomAppService, PortalCorpConfigService corpConfigService) { + public WecomCallbackController(PortalWecomAppService wecomAppService, + PortalCorpConfigService corpConfigService, + PluginManagerService pluginManagerService, + TaskExecutor taskExecutor) { this.wecomAppService = wecomAppService; this.corpConfigService = corpConfigService; + this.pluginManagerService = pluginManagerService; + this.taskExecutor = taskExecutor; } /** - * 企业微信回调 URL 验证 (GET) + * 企业微信回调 URL 校验 (GET) */ @GetMapping public String verify( @@ -34,7 +47,7 @@ public String verify( @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) { - + log.info("Received WeCom callback verification for appId={}: signature={}, timestamp={}, nonce={}, echostr={}", appId, signature, timestamp, nonce, echostr); @@ -51,7 +64,7 @@ public String verify( } /** - * 企业微信消息/事件推送 (POST) + * 企业微信消息/事件回调 (POST) */ @PostMapping public String handleMessage( @@ -67,38 +80,74 @@ public String handleMessage( WXBizMsgCrypt wxcpt = getWxCrypt(appId); String decryptedMsg = wxcpt.DecryptMsg(signature, timestamp, nonce, body); log.info("Decrypted XML: {}", decryptedMsg); - + WecomMessagePayload payload = WecomMessageParser.parse(decryptedMsg); log.info("Parsed Payload: {}", payload); - - // TODO: 后续可以根据 payload.getMsgType() 或 payload.getEvent() 分发到不同的处理器 - + + dispatchAsync(appId, payload); + return "success"; } catch (AesException e) { log.error("WeCom message decryption failed", e); - return "FAILED"; // 企业微信要求处理失败不返回 success,会重试 + return "FAILED"; // 企业微信要求处理失败不要返回 success,会重试 } catch (Exception e) { log.error("System error during message handling", e); return "ERROR"; } } + /** + * 异步分发到插件,避免阻塞企业微信回调响应。 + */ + private void dispatchAsync(Long appId, WecomMessagePayload payload) { + String type = "TEXT"; + String content = payload.getContent(); + + if ("event".equalsIgnoreCase(payload.getReceiveMsgType())) { + type = "CLICK"; + content = StringUtils.hasText(payload.getEventKey()) + ? payload.getEventKey() + : payload.getEvent(); + } + + if (!StringUtils.hasText(content) && StringUtils.hasText(payload.getPicUrl())) { + content = payload.getPicUrl(); + } + + ActionContext ctx = ActionContext.builder() + .eventId(payload.getMsgId() != null ? String.valueOf(payload.getMsgId()) : UUID.randomUUID().toString()) + .appId(String.valueOf(appId)) + .userId(payload.getFromUserName()) + .userName(null) + .type(type) + .content(content) + .pluginConfig(null) + .build(); + + taskExecutor.execute(() -> { + try { + pluginManagerService.dispatch(ctx); + } catch (Exception ex) { + log.error("Dispatch to plugins failed for appId={} eventId={}", appId, ctx.getEventId(), ex); + } + }); + } + private WXBizMsgCrypt getWxCrypt(Long appId) throws AesException { PortalWecomApp app = wecomAppService.getById(appId); if (app == null) { throw new AesException(AesException.IllegalAesKey, "App not found"); } - - // 校验配置是否完整 + if (app.getToken() == null || app.getEncodingAesKey() == null) { - throw new AesException(AesException.IllegalAesKey, "Token or EncodingAESKey not configured for this app"); + throw new AesException(AesException.IllegalAesKey, "Token or EncodingAESKey not configured for this app"); } PortalCorpConfig corpConfig = corpConfigService.getByUserId(app.getUserId()); if (corpConfig == null || corpConfig.getCorpId() == null) { - throw new AesException(AesException.ValidateCorpidError, "CorpConfig not found"); + throw new AesException(AesException.ValidateCorpidError, "CorpConfig not found"); } return new WXBizMsgCrypt(app.getToken(), app.getEncodingAesKey(), corpConfig.getCorpId()); } -} \ No newline at end of file +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java index b65b029..f75ebc9 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java @@ -13,24 +13,38 @@ public class PluginConnectionManager { // Key: Plugin Key, Value: Response Observer (to send data to plugin) private final Map> connections = new ConcurrentHashMap<>(); + // Key: Plugin Key, Value: Last heartbeat timestamp (ms) + private final Map lastHeartbeats = new ConcurrentHashMap<>(); public void register(String pluginKey, StreamObserver observer) { log.info("Plugin connected: {}", pluginKey); connections.put(pluginKey, observer); + lastHeartbeats.put(pluginKey, System.currentTimeMillis()); } public void unregister(String pluginKey) { if (pluginKey != null) { log.info("Plugin disconnected: {}", pluginKey); connections.remove(pluginKey); + lastHeartbeats.remove(pluginKey); } } public StreamObserver get(String pluginKey) { return connections.get(pluginKey); } - + public boolean isConnected(String pluginKey) { return connections.containsKey(pluginKey); } + + public void updateHeartbeat(String pluginKey) { + if (pluginKey != null) { + lastHeartbeats.put(pluginKey, System.currentTimeMillis()); + } + } + + public Long getLastHeartbeat(String pluginKey) { + return lastHeartbeats.get(pluginKey); + } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java index d525c5f..58f7d81 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginGatewayImpl.java @@ -14,6 +14,11 @@ import dev.qingzhou.push.api.model.ConfigField; import dev.qingzhou.push.api.model.ConfigType; import dev.qingzhou.push.api.model.SelectOption; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import java.util.ArrayList; import java.util.List; @@ -26,6 +31,8 @@ public class PluginGatewayImpl extends PluginGatewayImplBase { private final GrpcAdapter grpcAdapter; private final PushService pushService; private final PluginManagerService pluginManagerService; + private final PortalPluginActionLogMapper actionLogMapper; + private final PortalPluginHeartbeatLogMapper heartbeatLogMapper; @Override public StreamObserver connect(StreamObserver responseObserver) { @@ -103,7 +110,16 @@ private void handlePacket(String pluginKey, PluginUpstreamPacket packet, StreamO .build()); } else if (packet.hasHeartbeat()) { - // Heartbeat logic - just log for now + Heartbeat hb = packet.getHeartbeat(); + connectionManager.updateHeartbeat(pluginKey); + log.debug("Heartbeat from {} inflight={} uptime={}", pluginKey, hb.getCurrentInflight(), hb.getUptimeSeconds()); + saveHeartbeat(pluginKey, hb); + // Currently protocol has no downstream heartbeat ack; tracking timestamp is enough. + } else if (packet.hasActionAck()) { + ActionAck ack = packet.getActionAck(); + log.info("ActionAck from {} eventId={} status={} msg={}", + pluginKey, ack.getEventId(), ack.getStatus(), ack.getMessage()); + saveActionAck(pluginKey, ack); } else if (packet.hasPushRequest()) { dev.qingzhou.pushserver.model.dto.openapi.PushRequest coreReq = grpcAdapter.toCorePushRequest(packet.getPushRequest()); @@ -132,4 +148,33 @@ private void handlePacket(String pluginKey, PluginUpstreamPacket packet, StreamO } } } + + private void saveActionAck(String pluginKey, ActionAck ack) { + PortalPluginActionLog logEntity = new PortalPluginActionLog(); + logEntity.setPluginKey(pluginKey); + logEntity.setEventId(ack.getEventId()); + logEntity.setStatus(ack.getStatusValue()); + logEntity.setMessage(ack.getMessage()); + logEntity.setCreatedAt(System.currentTimeMillis()); + try { + int updated = actionLogMapper.update(logEntity, + new LambdaQueryWrapper() + .eq(PortalPluginActionLog::getPluginKey, pluginKey) + .eq(PortalPluginActionLog::getEventId, ack.getEventId())); + if (updated == 0) { + actionLogMapper.insert(logEntity); + } + } catch (Exception e) { + log.warn("Failed to persist ActionAck for plugin {} event {}", pluginKey, ack.getEventId(), e); + } + } + + private void saveHeartbeat(String pluginKey, Heartbeat hb) { + PortalPluginHeartbeatLog logEntity = new PortalPluginHeartbeatLog(); + logEntity.setPluginKey(pluginKey); + logEntity.setCurrentInflight(hb.getCurrentInflight()); + logEntity.setUptimeSeconds((int) hb.getUptimeSeconds()); + logEntity.setCreatedAt(System.currentTimeMillis()); + heartbeatLogMapper.insert(logEntity); + } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.java new file mode 100644 index 0000000..edb6d07 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalAppPluginConfigMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.java new file mode 100644 index 0000000..25a657c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalPluginActionLogMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.java new file mode 100644 index 0000000..f0c9f16 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.mapper.portal; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface PortalPluginHeartbeatLogMapper extends BaseMapper { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/AppPluginConfigSaveRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/AppPluginConfigSaveRequest.java new file mode 100644 index 0000000..d49ba4d --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/AppPluginConfigSaveRequest.java @@ -0,0 +1,15 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class AppPluginConfigSaveRequest { + + @NotNull(message = "Plugin key is required") + private String pluginKey; + + private String configJson; + + private Integer status; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppPluginConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppPluginConfig.java new file mode 100644 index 0000000..f52b956 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalAppPluginConfig.java @@ -0,0 +1,38 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_app_plugin_config") +public class PortalAppPluginConfig { + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("app_id") + private Long appId; + + @TableField("plugin_key") + private String pluginKey; + + /** + * JSON string of configuration + */ + @TableField("config_json") + private String configJson; + + /** + * 1: Enabled, 0: Disabled + */ + private Integer status; + + @TableField("created_at") + private Long createdAt; + + @TableField("updated_at") + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginActionLog.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginActionLog.java new file mode 100644 index 0000000..3627f5c --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginActionLog.java @@ -0,0 +1,24 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_plugin_action_log") +public class PortalPluginActionLog { + @TableId(type = IdType.AUTO) + private Long id; + private String pluginKey; + private String eventId; + private Integer status; + private String message; + private String appId; + private String appName; + private String userId; + private String type; + private String content; + private String pluginConfig; + private Long createdAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginHeartbeatLog.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginHeartbeatLog.java new file mode 100644 index 0000000..9696274 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/entity/portal/PortalPluginHeartbeatLog.java @@ -0,0 +1,17 @@ +package dev.qingzhou.pushserver.model.entity.portal; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +@Data +@TableName("v2_plugin_heartbeat_log") +public class PortalPluginHeartbeatLog { + @TableId(type = IdType.AUTO) + private Long id; + private String pluginKey; + private Integer currentInflight; + private Integer uptimeSeconds; + private Long createdAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppPluginConfigVo.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppPluginConfigVo.java new file mode 100644 index 0000000..b6fd624 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/vo/portal/PortalAppPluginConfigVo.java @@ -0,0 +1,25 @@ +package dev.qingzhou.pushserver.model.vo.portal; + +import dev.qingzhou.push.api.model.PluginMeta; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PortalAppPluginConfigVo { + // 插件的基本信息 + private String pluginKey; + private String name; + private String description; + + // 插件定义的配置字段 + private PluginMeta meta; + + // 当前应用配置的值 + private String configJson; + + // 在该应用中是否启用 + private Integer status; // 1: Enabled, 0: Disabled + + private Long updatedAt; +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java index bb62a10..14a870b 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.java @@ -9,7 +9,11 @@ import dev.qingzhou.push.api.spi.PushSender; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import java.util.HashMap; @@ -42,11 +46,18 @@ public PluginMeta getMeta() { .description("接收事件的接口地址") .build(), ConfigField.builder() - .name("token") - .label("Verify Token") + .name("headerName") + .label("Auth Header Name") + .type(ConfigType.TEXT) + .required(false) + .description("可选,例如 Authorization") + .build(), + ConfigField.builder() + .name("headerValue") + .label("Auth Header Value") .type(ConfigType.PASSWORD) .required(false) - .description("可选:用于安全校验的 Token") + .description("可选,例如 Bearer xxx") .build() )) .build(); @@ -66,7 +77,8 @@ public void init(PushSender sender) { @Override public void handle(ActionContext context) { String url = context.getConfig("url"); - String token = context.getConfig("token"); + String headerName = context.getConfig("headerName"); + String headerValue = context.getConfig("headerValue"); // 异步执行,避免阻塞主事件循环 CompletableFuture.runAsync(() -> { @@ -77,13 +89,16 @@ public void handle(ActionContext context) { payload.put("userId", context.getUserId()); payload.put("content", context.getContent()); payload.put("timestamp", System.currentTimeMillis()); - - if (token != null) { - payload.put("token", token); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + if (StringUtils.hasText(headerName) && StringUtils.hasText(headerValue)) { + headers.set(headerName, headerValue); } log.debug("Sending webhook to {}", url); - String response = restTemplate.postForObject(url, payload, String.class); + HttpEntity> entity = new HttpEntity<>(payload, headers); + String response = restTemplate.postForObject(url, entity, String.class); log.debug("Webhook response: {}", response); } catch (Exception e) { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppPluginService.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppPluginService.java new file mode 100644 index 0000000..9a6e939 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/PortalAppPluginService.java @@ -0,0 +1,14 @@ +package dev.qingzhou.pushserver.service; + +import dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest; +import dev.qingzhou.pushserver.model.vo.portal.PortalAppPluginConfigVo; +import java.util.List; + +public interface PortalAppPluginService { + + List listByApp(Long userId, Long appId); + + void saveConfig(Long userId, Long appId, AppPluginConfigSaveRequest request); + + void deleteConfig(Long userId, Long appId, String pluginKey); +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java index f1328ba..54616a3 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.java @@ -1,30 +1,49 @@ package dev.qingzhou.pushserver.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import dev.qingzhou.push.api.model.ActionContext; +import dev.qingzhou.push.api.model.ConfigField; import dev.qingzhou.push.api.model.PluginMeta; import dev.qingzhou.push.api.spi.PushPlugin; import dev.qingzhou.push.api.spi.PushSender; import dev.qingzhou.pushserver.grpc.PluginPacketDispatcher; +import dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalWecomApp; +import dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog; +import dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig; import dev.qingzhou.pushserver.plugin.GrpcPluginProxy; import dev.qingzhou.pushserver.service.PluginManagerService; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import jakarta.annotation.PostConstruct; +import org.springframework.util.StringUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.ArrayList; +import java.util.function.Function; +import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class PluginManagerServiceImpl implements PluginManagerService { - private final List localPlugins; + private final List localPlugins; private final PluginPacketDispatcher packetDispatcher; - private final PushSender pushSender; + private final PushSender pushSender; + private final PortalAppPluginConfigMapper appPluginConfigMapper; + private final ObjectMapper objectMapper; + private final PortalPluginActionLogMapper actionLogMapper; + private final PortalWecomAppMapper wecomAppMapper; private final Map remotePlugins = new ConcurrentHashMap<>(); @@ -32,12 +51,12 @@ public class PluginManagerServiceImpl implements PluginManagerService { public void init() { log.info("Found {} local plugins", localPlugins.size()); for (PushPlugin plugin : localPlugins) { - try { - plugin.init(pushSender); - log.info("Initialized local plugin: {}", plugin.getClass().getName()); - } catch (Exception e) { - log.error("Failed to init local plugin {}", plugin.getClass().getName(), e); - } + try { + plugin.init(pushSender); + log.info("Initialized local plugin: {}", plugin.getClass().getName()); + } catch (Exception e) { + log.error("Failed to init local plugin {}", plugin.getClass().getName(), e); + } } } @@ -66,24 +85,191 @@ public List getLocalPlugins() { return localPlugins; } + /** + * Dispatch a user action to the plugins that are both + * 1) registered (local or active remote), and + * 2) enabled for the given app with their own configuration. + */ @Override public void dispatch(ActionContext context) { + if (context == null) { + log.warn("Dispatch skipped because context is null"); + return; + } + + Long appId = parseAppId(context.getAppId()); + Map activeConfigs = Collections.emptyMap(); + + if (appId != null) { + activeConfigs = appPluginConfigMapper.selectList( + new LambdaQueryWrapper() + .eq(PortalAppPluginConfig::getAppId, appId) + .eq(PortalAppPluginConfig::getStatus, 1) + ).stream().collect(Collectors.toMap( + PortalAppPluginConfig::getPluginKey, + Function.identity(), + (existing, replacement) -> replacement + )); + } + + boolean handled = false; for (PushPlugin plugin : getAllPlugins()) { + PluginMeta meta = plugin.getMeta(); + String pluginKey = meta != null && StringUtils.hasText(meta.getId()) + ? meta.getId() + : plugin.getClass().getSimpleName(); + + if (appId != null && !activeConfigs.containsKey(pluginKey)) { + log.debug("Skip plugin {} for app {} (not configured or disabled)", pluginKey, appId); + continue; + } + + Map pluginConfig = buildPluginConfig( + meta, + activeConfigs.get(pluginKey), + context.getPluginConfig() + ); + + ActionContext pluginContext = cloneContextWithConfig(context, pluginConfig); + + // 记录接收 + 请求快照(若已存在则更新) + upsertActionLog(pluginKey, pluginContext, 0, "RECEIVED"); + try { - if (plugin.supports(context)) { - // Try to log name if meta is available, else class name - String name = (plugin.getMeta() != null && plugin.getMeta().getName() != null) - ? plugin.getMeta().getName() + if (plugin.supports(pluginContext)) { + String name = meta != null && StringUtils.hasText(meta.getName()) + ? meta.getName() : plugin.getClass().getSimpleName(); - - log.info("Dispatching event {} to plugin {}", context.getEventId(), name); - plugin.handle(context); - return; // Stop after first match + + log.info("Dispatching event {} (app {}) to plugin {}", pluginContext.getEventId(), appId, name); + plugin.handle(pluginContext); + + // 仅对本地插件记录最终状态;远程插件由其 ActionAck 写日志,避免重复 + if (!(plugin instanceof GrpcPluginProxy)) { + upsertActionLog(pluginKey, pluginContext, 2, "SUCCESS"); + } + handled = true; } } catch (Exception e) { - log.error("Plugin failed to handle event", e); + log.error("Plugin {} failed to handle event {}", pluginKey, pluginContext.getEventId(), e); + if (!(plugin instanceof GrpcPluginProxy)) { + upsertActionLog(pluginKey, pluginContext, 3, "FAILED: " + e.getMessage()); + } } } - log.warn("No plugin handled event {}", context.getEventId()); + + if (!handled) { + log.warn("No plugin handled event {} (app {})", context.getEventId(), appId); + } + } + + private ActionContext cloneContextWithConfig(ActionContext source, Map pluginConfig) { + return ActionContext.builder() + .eventId(source.getEventId()) + .appId(source.getAppId()) + .userId(source.getUserId()) + .userName(source.getUserName()) + .type(source.getType()) + .content(source.getContent()) + .pluginConfig(pluginConfig) + .build(); + } + + private Map buildPluginConfig(PluginMeta meta, + PortalAppPluginConfig storedConfig, + Map runtimeOverrides) { + Map result = new HashMap<>(); + + if (meta != null && meta.getConfigFields() != null) { + for (ConfigField field : meta.getConfigFields()) { + if (StringUtils.hasText(field.getName()) && field.getDefaultValue() != null) { + result.put(field.getName(), field.getDefaultValue()); + } + } + } + + if (storedConfig != null && StringUtils.hasText(storedConfig.getConfigJson())) { + result.putAll(parseConfigJson(storedConfig.getConfigJson())); + } + + if (runtimeOverrides != null && !runtimeOverrides.isEmpty()) { + result.putAll(runtimeOverrides); + } + + return result; + } + + private Map parseConfigJson(String json) { + try { + Map raw = objectMapper.readValue(json, new TypeReference>() {}); + Map parsed = new HashMap<>(); + if (raw != null) { + raw.forEach((k, v) -> parsed.put(k, v == null ? null : String.valueOf(v))); + } + return parsed; + } catch (Exception e) { + log.warn("Failed to parse plugin config json: {}", json, e); + return Collections.emptyMap(); + } + } + + private Long parseAppId(String appId) { + if (!StringUtils.hasText(appId)) { + return null; + } + try { + return Long.parseLong(appId.trim()); + } catch (NumberFormatException ex) { + log.warn("Invalid appId on ActionContext: {}", appId); + return null; + } + } + + private void upsertActionLog(String pluginKey, ActionContext ctx, int status, String message) { + String eventId = ctx.getEventId(); + PortalPluginActionLog entity = new PortalPluginActionLog(); + entity.setPluginKey(pluginKey); + entity.setEventId(eventId); + entity.setStatus(status); + entity.setMessage(message); + entity.setAppId(ctx.getAppId()); + entity.setAppName(resolveAppName(ctx.getAppId())); + entity.setUserId(ctx.getUserId()); + entity.setType(ctx.getType()); + entity.setContent(ctx.getContent()); + entity.setPluginConfig(writeJson(ctx.getPluginConfig())); + entity.setCreatedAt(System.currentTimeMillis()); + try { + int updated = actionLogMapper.update(entity, + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(PortalPluginActionLog::getPluginKey, pluginKey) + .eq(PortalPluginActionLog::getEventId, eventId)); + if (updated == 0) { + actionLogMapper.insert(entity); + } + } catch (Exception ex) { + log.warn("Failed to upsert plugin action log: pluginKey={}, eventId={}, status={}, msg={}", pluginKey, eventId, status, message, ex); + } + } + + private String writeJson(Map map) { + if (map == null || map.isEmpty()) return null; + try { + return objectMapper.writeValueAsString(map); + } catch (Exception e) { + return null; + } + } + + private String resolveAppName(String appId) { + if (!StringUtils.hasText(appId)) { + return null; + } + try { + PortalWecomApp app = wecomAppMapper.selectById(Long.parseLong(appId)); + return app != null ? app.getName() : null; + } catch (Exception e) { + return null; + } } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppPluginServiceImpl.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppPluginServiceImpl.java new file mode 100644 index 0000000..bcdfc24 --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/service/impl/PortalAppPluginServiceImpl.java @@ -0,0 +1,128 @@ +package dev.qingzhou.pushserver.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import dev.qingzhou.push.api.model.PluginMeta; +import dev.qingzhou.push.api.spi.PushPlugin; +import dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper; +import dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest; +import dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig; +import dev.qingzhou.pushserver.model.vo.portal.PortalAppPluginConfigVo; +import dev.qingzhou.pushserver.service.PluginManagerService; +import dev.qingzhou.pushserver.service.PortalAppPluginService; +import dev.qingzhou.pushserver.service.PortalWecomAppService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PortalAppPluginServiceImpl implements PortalAppPluginService { + + private final PortalWecomAppService appService; + private final PluginManagerService pluginManagerService; + private final PortalAppPluginConfigMapper configMapper; + + @Override + public List listByApp(Long userId, Long appId) { + // Ensure user owns the app + appService.requireByUser(userId, appId); + + // Get all active plugins from memory (Local + Remote) + List allPlugins = pluginManagerService.getAllPlugins(); + + // Get stored configs for this app + List storedConfigs = configMapper.selectList( + new LambdaQueryWrapper() + .eq(PortalAppPluginConfig::getAppId, appId) + ); + Map configMap = storedConfigs.stream() + .collect(Collectors.toMap(PortalAppPluginConfig::getPluginKey, Function.identity())); + + List result = new ArrayList<>(); + for (PushPlugin plugin : allPlugins) { + PluginMeta meta = plugin.getMeta(); + if (meta == null) { + // Skip plugins without meta (shouldn't happen usually for valid plugins) + continue; + } + // Use ID from meta as the key. If ID is missing, fallback to class name or specific logic? + // Usually meta.id is the unique key. + // WAIT: In PluginManagerService, registerRemotePlugin uses a pluginKey. + // But PushPlugin interface has getMeta(). + // For remote plugins, the proxy should return the meta provided during registration. + + String pluginKey = meta.getId(); + // Ideally pluginKey should be consistent. Assuming meta.id is the key. + + PortalAppPluginConfig stored = configMap.get(pluginKey); + + PortalAppPluginConfigVo vo = PortalAppPluginConfigVo.builder() + .pluginKey(pluginKey) + .name(meta.getName()) + .description(meta.getDescription()) + .meta(meta) + .configJson(stored != null ? stored.getConfigJson() : null) + .status(stored != null ? stored.getStatus() : 0) // Default to disabled if not configured + .updatedAt(stored != null ? stored.getUpdatedAt() : null) + .build(); + result.add(vo); + } + return result; + } + + @Override + @Transactional + public void saveConfig(Long userId, Long appId, AppPluginConfigSaveRequest request) { + appService.requireByUser(userId, appId); + + String pluginKey = request.getPluginKey(); + + PortalAppPluginConfig existing = configMapper.selectOne( + new LambdaQueryWrapper() + .eq(PortalAppPluginConfig::getAppId, appId) + .eq(PortalAppPluginConfig::getPluginKey, pluginKey) + ); + + long now = System.currentTimeMillis(); + + if (existing == null) { + existing = new PortalAppPluginConfig(); + existing.setAppId(appId); + existing.setPluginKey(pluginKey); + existing.setConfigJson(request.getConfigJson()); + existing.setStatus(request.getStatus() != null ? request.getStatus() : 1); + existing.setCreatedAt(now); + existing.setUpdatedAt(now); + configMapper.insert(existing); + } else { + if (request.getConfigJson() != null) { + existing.setConfigJson(request.getConfigJson()); + } + if (request.getStatus() != null) { + existing.setStatus(request.getStatus()); + } + existing.setUpdatedAt(now); + configMapper.updateById(existing); + } + } + + @Override + @Transactional + public void deleteConfig(Long userId, Long appId, String pluginKey) { + appService.requireByUser(userId, appId); + + configMapper.delete( + new LambdaQueryWrapper() + .eq(PortalAppPluginConfig::getAppId, appId) + .eq(PortalAppPluginConfig::getPluginKey, pluginKey) + ); + } +} From ae72c57f70656a2fb7334e9675344355722f4c02 Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 21:58:52 +0800 Subject: [PATCH 06/15] =?UTF-8?q?feat(monitor):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=BF=83=E8=B7=B3=E7=9B=91=E6=8E=A7=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=AE=9A=E6=9C=9F=E6=A3=80=E6=9F=A5=E5=B9=B6?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E4=B8=8B=E7=BA=BF=E8=B6=85=E6=97=B6=E6=8F=92?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pushserver/config/SchedulingConfig.java | 9 ++++ .../grpc/PluginConnectionManager.java | 7 ++++ .../monitor/PluginHeartbeatMonitor.java | 41 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/config/SchedulingConfig.java create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/monitor/PluginHeartbeatMonitor.java diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/config/SchedulingConfig.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/SchedulingConfig.java new file mode 100644 index 0000000..cb0915d --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/config/SchedulingConfig.java @@ -0,0 +1,9 @@ +package dev.qingzhou.pushserver.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@Configuration +@EnableScheduling +public class SchedulingConfig { +} diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java index f75ebc9..e9dbfc2 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/grpc/PluginConnectionManager.java @@ -47,4 +47,11 @@ public void updateHeartbeat(String pluginKey) { public Long getLastHeartbeat(String pluginKey) { return lastHeartbeats.get(pluginKey); } + + /** + * Snapshot of last heartbeat timestamps for monitoring. + */ + public Map snapshotHeartbeats() { + return new ConcurrentHashMap<>(lastHeartbeats); + } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/monitor/PluginHeartbeatMonitor.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/monitor/PluginHeartbeatMonitor.java new file mode 100644 index 0000000..1d63abd --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/monitor/PluginHeartbeatMonitor.java @@ -0,0 +1,41 @@ +package dev.qingzhou.pushserver.monitor; + +import dev.qingzhou.pushserver.grpc.PluginConnectionManager; +import dev.qingzhou.pushserver.service.PluginManagerService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 定期检查远程插件心跳;超时则强制下线。 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class PluginHeartbeatMonitor { + + private static final long TIMEOUT_MS = 90_000; // 90 秒无心跳视为离线 + + private final PluginConnectionManager connectionManager; + private final PluginManagerService pluginManagerService; + + @Scheduled(fixedDelay = 30_000) + public void checkHeartbeats() { + long now = System.currentTimeMillis(); + for (Map.Entry entry : connectionManager.snapshotHeartbeats().entrySet()) { + String pluginKey = entry.getKey(); + Long last = entry.getValue(); + if (last == null) { + continue; + } + if (now - last > TIMEOUT_MS) { + log.warn("Plugin {} heartbeat timeout ({} ms > {}) - forcing disconnect", pluginKey, now - last, TIMEOUT_MS); + connectionManager.unregister(pluginKey); + pluginManagerService.unregisterRemotePlugin(pluginKey); + } + } + } +} From b115c55ebfb98b5ecd92666ef43f38e3eb1dffec Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 22:36:04 +0800 Subject: [PATCH 07/15] =?UTF-8?q?feat(core):=20=E5=A2=9E=E5=8A=A0=20Lombok?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=E5=B9=B6=E8=B0=83=E6=95=B4=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E6=89=A7=E8=A1=8C=E5=99=A8=E6=B3=A8=E8=A7=A3=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- push-server-api/pom.xml | 15 ++++++++++++++- .../controller/wecom/WecomCallbackController.java | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/push-server-api/pom.xml b/push-server-api/pom.xml index 453f96f..6b41fa8 100644 --- a/push-server-api/pom.xml +++ b/push-server-api/pom.xml @@ -70,7 +70,20 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + true + + + org.projectlombok + lombok + + + + - \ No newline at end of file + diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java index 20806f2..d012219 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java @@ -12,6 +12,7 @@ import dev.qingzhou.pushserver.service.PortalWecomAppService; import lombok.extern.slf4j.Slf4j; import org.springframework.core.task.TaskExecutor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; @@ -30,7 +31,7 @@ public class WecomCallbackController { public WecomCallbackController(PortalWecomAppService wecomAppService, PortalCorpConfigService corpConfigService, PluginManagerService pluginManagerService, - TaskExecutor taskExecutor) { + @Qualifier("applicationTaskExecutor") TaskExecutor taskExecutor) { this.wecomAppService = wecomAppService; this.corpConfigService = corpConfigService; this.pluginManagerService = pluginManagerService; From 9ff039be4bd3ca9da5edff7e9e185b19b992c23f Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 22:54:33 +0800 Subject: [PATCH 08/15] =?UTF-8?q?feat(native-image):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8E=9F=E7=94=9F=E9=95=9C=E5=83=8F=E5=8F=8D=E5=B0=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=96=B0=E5=A2=9E=E6=8F=92=E4=BB=B6=E4=B8=8E?= =?UTF-8?q?=20gRPC=20=E7=9B=B8=E5=85=B3=E7=B1=BB=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native-image/reachability-metadata.json | 1755 +++++++++-------- 1 file changed, 952 insertions(+), 803 deletions(-) diff --git a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json index 1c46924..e5c21ed 100644 --- a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -1,5 +1,8 @@ { "reflection": [ + { + "type": "android.app.Application" + }, { "type": "boolean" }, @@ -130,6 +133,9 @@ } ] }, + { + "type": "com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper" + }, { "type": "com.baomidou.mybatisplus.core.conditions.AbstractWrapper", "methods": [ @@ -183,6 +189,15 @@ { "type": "com.baomidou.mybatisplus.core.conditions.interfaces.Nested" }, + { + "type": "com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper", + "methods": [ + { + "name": "getSqlSelect", + "parameterTypes": [] + } + ] + }, { "type": "com.baomidou.mybatisplus.core.conditions.query.Query" }, @@ -274,12 +289,6 @@ "com.baomidou.mybatisplus.core.conditions.Wrapper" ] }, - { - "name": "getById", - "parameterTypes": [ - "java.io.Serializable" - ] - }, { "name": "list", "parameterTypes": [ @@ -421,6 +430,15 @@ { "type": "com.google.gson.Gson" }, + { + "type": "com.google.protobuf.ExtensionRegistry", + "methods": [ + { + "name": "getEmptyRegistry", + "parameterTypes": [] + } + ] + }, { "type": "com.rometools.rome.feed.WireFeed" }, @@ -487,6 +505,26 @@ } ] }, + { + "type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [ + "javax.crypto.KDFParameters" + ] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "com.sun.crypto.provider.TlsKeyMaterialGenerator", "methods": [ @@ -538,6 +576,18 @@ { "type": "com.zaxxer.hikari.pool.PoolEntry" }, + { + "type": "dev.qingzhou.push.api.grpc.PluginGatewayGrpc$AsyncService" + }, + { + "type": "dev.qingzhou.push.api.grpc.PluginGatewayGrpc$PluginGatewayImplBase" + }, + { + "type": "dev.qingzhou.push.api.spi.PushPlugin" + }, + { + "type": "dev.qingzhou.push.api.spi.PushSender" + }, { "type": "dev.qingzhou.pushserver.PushServerApplication", "methods": [ @@ -581,6 +631,53 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.config.GrpcServerConfig", + "fields": [ + { + "name": "port" + } + ], + "methods": [ + { + "name": "start", + "parameterTypes": [] + }, + { + "name": "stop", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.config.GrpcServerConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.grpc.PluginGatewayImpl", + "dev.qingzhou.pushserver.grpc.PluginAuthInterceptor" + ] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, { "type": "dev.qingzhou.pushserver.config.JsonDtoPackageHints", "methods": [ @@ -923,6 +1020,35 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.config.SchedulingConfig" + }, + { + "type": "dev.qingzhou.pushserver.config.SchedulingConfig$$SpringCGLIB$$0", + "fields": [ + { + "name": "$$beanFactory" + }, + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "CGLIB$SET_STATIC_CALLBACKS", + "parameterTypes": [ + "org.springframework.cglib.proxy.Callback[]" + ] + } + ] + }, { "type": "dev.qingzhou.pushserver.config.WebConfig" }, @@ -962,6 +1088,23 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.SystemConfigService" ] + }, + { + "name": "captcha", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.CaptchaController$CaptchaResponse", + "methods": [ + { + "name": "enabled", + "parameterTypes": [] + }, + { + "name": "siteKey", + "parameterTypes": [] } ] }, @@ -1018,20 +1161,6 @@ "dev.qingzhou.pushserver.service.PortalAppApiKeyService" ] }, - { - "name": "create", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "createApiKey", - "parameterTypes": [ - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, { "name": "getApiKey", "parameterTypes": [ @@ -1044,20 +1173,16 @@ "parameterTypes": [ "jakarta.servlet.http.HttpSession" ] - }, - { - "name": "sync", - "parameterTypes": [ - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalAppPluginController", + "methods": [ { - "name": "updateApiKey", + "name": "", "parameterTypes": [ - "java.lang.Long", - "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", - "jakarta.servlet.http.HttpSession" + "dev.qingzhou.pushserver.service.PortalAppPluginService" ] } ] @@ -1087,19 +1212,6 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalCorpConfigService" ] - }, - { - "name": "getCorp", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", - "jakarta.servlet.http.HttpSession" - ] } ] }, @@ -1136,12 +1248,6 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalUserService" ] - }, - { - "name": "me", - "parameterTypes": [ - "jakarta.servlet.http.HttpSession" - ] } ] }, @@ -1155,17 +1261,6 @@ "dev.qingzhou.pushserver.service.PortalMessageLogService" ] }, - { - "name": "logs", - "parameterTypes": [ - "java.lang.Integer", - "java.lang.Integer", - "int", - "java.lang.Boolean", - "java.lang.Long", - "jakarta.servlet.http.HttpSession" - ] - }, { "name": "send", "parameterTypes": [ @@ -1176,25 +1271,39 @@ ] }, { - "type": "dev.qingzhou.pushserver.controller.PortalProxyController", + "type": "dev.qingzhou.pushserver.controller.PortalPluginController", "methods": [ { "name": "", "parameterTypes": [ - "dev.qingzhou.pushserver.service.PortalProxyConfigService" + "dev.qingzhou.pushserver.service.PortalPluginService" ] }, { - "name": "getProxy", + "name": "list", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalPluginLogController", + "methods": [ + { + "name": "", "parameterTypes": [ - "jakarta.servlet.http.HttpSession" + "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper", + "dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper" ] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.controller.PortalProxyController", + "methods": [ { - "name": "upsert", + "name": "", "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", - "jakarta.servlet.http.HttpSession" + "dev.qingzhou.pushserver.service.PortalProxyConfigService" ] } ] @@ -1244,13 +1353,6 @@ "dev.qingzhou.pushserver.service.PortalAppApiKeyService", "dev.qingzhou.pushserver.service.PortalMessageService" ] - }, - { - "name": "send", - "parameterTypes": [ - "java.lang.String", - "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest" - ] } ] }, @@ -1261,7 +1363,9 @@ "name": "", "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalWecomAppService", - "dev.qingzhou.pushserver.service.PortalCorpConfigService" + "dev.qingzhou.pushserver.service.PortalCorpConfigService", + "dev.qingzhou.pushserver.service.PluginManagerService", + "org.springframework.core.task.TaskExecutor" ] } ] @@ -1285,28 +1389,57 @@ ] }, { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo", + "type": "dev.qingzhou.pushserver.grpc.GrpcAdapter", "methods": [ { "name": "", "parameterTypes": [] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.grpc.PluginAuthInterceptor", + "methods": [ { - "name": "setAvatarUrl", + "name": "", "parameterTypes": [ - "java.lang.String" + "dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper" ] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.grpc.PluginConnectionManager", + "methods": [ { - "name": "setDescription", + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.grpc.PluginGatewayImpl", + "methods": [ + { + "name": "", "parameterTypes": [ - "java.lang.String" + "dev.qingzhou.pushserver.grpc.PluginConnectionManager", + "dev.qingzhou.pushserver.grpc.GrpcAdapter", + "dev.qingzhou.pushserver.service.PushService", + "dev.qingzhou.pushserver.service.PluginManagerService", + "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper", + "dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper" ] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.grpc.PluginPacketDispatcher", + "methods": [ { - "name": "setName", + "name": "", "parameterTypes": [ - "java.lang.String" + "dev.qingzhou.pushserver.grpc.PluginConnectionManager" ] } ] @@ -1462,15 +1595,6 @@ } ] }, - { - "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Text", - "methods": [ - { - "name": "getContent", - "parameterTypes": [] - } - ] - }, { "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$TextCard", "methods": [ @@ -1566,6 +1690,9 @@ { "type": "dev.qingzhou.pushserver.mapper.portal.PortalAppApiKeyMapper" }, + { + "type": "dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper" + }, { "type": "dev.qingzhou.pushserver.mapper.portal.PortalCorpConfigMapper" }, @@ -1573,112 +1700,25 @@ "type": "dev.qingzhou.pushserver.mapper.portal.PortalMessageLogMapper" }, { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" + "type": "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper" }, { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" + "type": "dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper" }, { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" - }, - { - "type": "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" + "type": "dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper" }, { - "type": "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setContent", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setMsgType", - "parameterTypes": [ - "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" - ] - }, - { - "name": "setToAll", - "parameterTypes": [ - "java.lang.Boolean" - ] - } - ] + "type": "dev.qingzhou.pushserver.mapper.portal.PortalProxyConfigMapper" }, { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", - "fields": [ - { - "name": "rateLimitPerMinute" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setRateLimitPerMinute", - "parameterTypes": [ - "java.lang.Integer" - ] - } - ] + "type": "dev.qingzhou.pushserver.mapper.portal.PortalSystemConfigMapper" }, { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", - "fields": [ - { - "name": "agentId" - }, - { - "name": "secret" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setAgentId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setSecret", - "parameterTypes": [ - "java.lang.String" - ] - } - ] + "type": "dev.qingzhou.pushserver.mapper.portal.PortalUserMapper" }, { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", - "fields": [ - { - "name": "corpId" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setCorpId", - "parameterTypes": [ - "java.lang.String" - ] - } - ] + "type": "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" }, { "type": "dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest", @@ -1731,6 +1771,12 @@ "java.util.List" ] }, + { + "name": "setBtnText", + "parameterTypes": [ + "java.lang.String" + ] + }, { "name": "setContent", "parameterTypes": [ @@ -1806,142 +1852,10 @@ "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" }, { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", - "fields": [ - { - "name": "host" - }, - { - "name": "port" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "setActive", - "parameterTypes": [ - "java.lang.Boolean" - ] - }, - { - "name": "setExitIp", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setHost", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPassword", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setPort", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setType", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUsername", - "parameterTypes": [ - "java.lang.String" - ] - } - ] + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey" }, { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getApiKeyHash", - "parameterTypes": [] - }, - { - "name": "getApiKeyPlain", - "parameterTypes": [] - }, - { - "name": "getAppId", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getRateLimitPerMinute", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "setApiKeyHash", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setApiKeyPlain", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setAppId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setRateLimitPerMinute", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setUpdatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig" }, { "type": "dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig", @@ -1950,26 +1864,6 @@ "name": "", "parameterTypes": [] }, - { - "name": "getCorpId", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, { "name": "setCorpId", "parameterTypes": [ @@ -2018,160 +1912,7 @@ "parameterTypes": [] }, { - "name": "getContent", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getErrorMessage", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getMsgType", - "parameterTypes": [] - }, - { - "name": "getRequestJson", - "parameterTypes": [] - }, - { - "name": "getResponseJson", - "parameterTypes": [] - }, - { - "name": "getSuccess", - "parameterTypes": [] - }, - { - "name": "getTitle", - "parameterTypes": [] - }, - { - "name": "getToAll", - "parameterTypes": [] - }, - { - "name": "getToParty", - "parameterTypes": [] - }, - { - "name": "getToUser", - "parameterTypes": [] - }, - { - "name": "getUrl", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, - { - "name": "setAgentId", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setAppId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setContent", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setCreatedAt", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setDescription", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "setMsgType", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setRequestJson", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setResponseJson", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setSuccess", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setTitle", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setToAll", - "parameterTypes": [ - "java.lang.Integer" - ] - }, - { - "name": "setUrl", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "setUserId", - "parameterTypes": [ - "java.lang.Long" - ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "getActive", + "name": "getContent", "parameterTypes": [] }, { @@ -2179,11 +1920,11 @@ "parameterTypes": [] }, { - "name": "getExitIp", + "name": "getDescription", "parameterTypes": [] }, { - "name": "getHost", + "name": "getErrorMessage", "parameterTypes": [] }, { @@ -2191,51 +1932,67 @@ "parameterTypes": [] }, { - "name": "getPassword", + "name": "getMsgType", "parameterTypes": [] }, { - "name": "getPort", + "name": "getRequestJson", "parameterTypes": [] }, { - "name": "getType", + "name": "getResponseJson", "parameterTypes": [] }, { - "name": "getUpdatedAt", + "name": "getSuccess", "parameterTypes": [] }, { - "name": "getUserId", + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getToAll", + "parameterTypes": [] + }, + { + "name": "getToParty", + "parameterTypes": [] + }, + { + "name": "getToUser", + "parameterTypes": [] + }, + { + "name": "getUrl", "parameterTypes": [] }, { - "name": "getUsername", + "name": "getUserId", "parameterTypes": [] }, { - "name": "setActive", + "name": "setAgentId", "parameterTypes": [ - "java.lang.Boolean" + "java.lang.String" ] }, { - "name": "setCreatedAt", + "name": "setAppId", "parameterTypes": [ "java.lang.Long" ] }, { - "name": "setExitIp", + "name": "setContent", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setHost", + "name": "setCreatedAt", "parameterTypes": [ - "java.lang.String" + "java.lang.Long" ] }, { @@ -2245,43 +2002,55 @@ ] }, { - "name": "setPassword", + "name": "setMsgType", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setPort", + "name": "setRequestJson", "parameterTypes": [ - "java.lang.Integer" + "java.lang.String" ] }, { - "name": "setType", + "name": "setResponseJson", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setUpdatedAt", + "name": "setSuccess", "parameterTypes": [ - "java.lang.Long" + "java.lang.Integer" ] }, { - "name": "setUserId", + "name": "setToAll", "parameterTypes": [ - "java.lang.Long" + "java.lang.Integer" ] }, { - "name": "setUsername", + "name": "setUserId", "parameterTypes": [ - "java.lang.String" + "java.lang.Long" ] } ] }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPlugin" + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog" + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog" + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig" + }, { "type": "dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig", "methods": [ @@ -2361,50 +2130,6 @@ "name": "", "parameterTypes": [] }, - { - "name": "getAgentId", - "parameterTypes": [] - }, - { - "name": "getAvatarUrl", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] - }, - { - "name": "getDescription", - "parameterTypes": [] - }, - { - "name": "getEncodingAesKey", - "parameterTypes": [] - }, - { - "name": "getId", - "parameterTypes": [] - }, - { - "name": "getName", - "parameterTypes": [] - }, - { - "name": "getSecret", - "parameterTypes": [] - }, - { - "name": "getToken", - "parameterTypes": [] - }, - { - "name": "getUpdatedAt", - "parameterTypes": [] - }, - { - "name": "getUserId", - "parameterTypes": [] - }, { "name": "setAgentId", "parameterTypes": [ @@ -2429,6 +2154,12 @@ "java.lang.String" ] }, + { + "name": "setEncodingAesKey", + "parameterTypes": [ + "java.lang.String" + ] + }, { "name": "setId", "parameterTypes": [ @@ -2447,6 +2178,12 @@ "java.lang.String" ] }, + { + "name": "setToken", + "parameterTypes": [ + "java.lang.String" + ] + }, { "name": "setUpdatedAt", "parameterTypes": [ @@ -2474,6 +2211,19 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse$DistributionSlice", + "methods": [ + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getValue", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardChartsResponse$TrendPoint", "methods": [ @@ -2487,6 +2237,31 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardLogResponse", + "methods": [ + { + "name": "getAppName", + "parameterTypes": [] + }, + { + "name": "getErrorMsg", + "parameterTypes": [] + }, + { + "name": "getReceiver", + "parameterTypes": [] + }, + { + "name": "getStatus", + "parameterTypes": [] + }, + { + "name": "getTime", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.vo.portal.DashboardStatsResponse", "methods": [ @@ -2570,15 +2345,6 @@ } ] }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse", - "methods": [ - { - "name": "getCorpId", - "parameterTypes": [] - } - ] - }, { "type": "dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse", "methods": [ @@ -2641,43 +2407,14 @@ ] }, { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse", - "methods": [ - { - "name": "getPage", - "parameterTypes": [] - }, - { - "name": "getPageSize", - "parameterTypes": [] - }, - { - "name": "getRecords", - "parameterTypes": [] - }, - { - "name": "getTotal", - "parameterTypes": [] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse", + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPluginVo", "methods": [ - { - "name": "getActive", - "parameterTypes": [] - }, { "name": "getCreatedAt", "parameterTypes": [] }, { - "name": "getExitIp", - "parameterTypes": [] - }, - { - "name": "getHost", + "name": "getDescription", "parameterTypes": [] }, { @@ -2685,45 +2422,51 @@ "parameterTypes": [] }, { - "name": "getPassword", + "name": "getIsBuiltin", "parameterTypes": [] }, { - "name": "getPort", + "name": "getIsConnected", "parameterTypes": [] }, { - "name": "getType", + "name": "getName", "parameterTypes": [] }, { - "name": "getUpdatedAt", + "name": "getPluginKey", "parameterTypes": [] }, { - "name": "getUsername", + "name": "getStatus", "parameterTypes": [] } ] }, { - "type": "dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse", + "type": "dev.qingzhou.pushserver.monitor.PluginHeartbeatMonitor", "methods": [ { - "name": "getAccount", - "parameterTypes": [] - }, - { - "name": "getCreatedAt", - "parameterTypes": [] + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.grpc.PluginConnectionManager", + "dev.qingzhou.pushserver.service.PluginManagerService" + ] }, { - "name": "getId", + "name": "checkHeartbeats", "parameterTypes": [] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.plugin.builtin.WebhookPlugin", + "methods": [ { - "name": "getUpdatedAt", - "parameterTypes": [] + "name": "", + "parameterTypes": [ + "com.fasterxml.jackson.databind.ObjectMapper" + ] } ] }, @@ -2764,12 +2507,18 @@ { "type": "dev.qingzhou.pushserver.service.DashboardService" }, + { + "type": "dev.qingzhou.pushserver.service.PluginManagerService" + }, { "type": "dev.qingzhou.pushserver.service.PortalAccessTokenService" }, { "type": "dev.qingzhou.pushserver.service.PortalAppApiKeyService" }, + { + "type": "dev.qingzhou.pushserver.service.PortalAppPluginService" + }, { "type": "dev.qingzhou.pushserver.service.PortalCorpConfigService" }, @@ -2779,6 +2528,9 @@ { "type": "dev.qingzhou.pushserver.service.PortalMessageService" }, + { + "type": "dev.qingzhou.pushserver.service.PortalPluginService" + }, { "type": "dev.qingzhou.pushserver.service.PortalProxyConfigService" }, @@ -2806,6 +2558,27 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.service.impl.PluginManagerServiceImpl", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.util.List", + "dev.qingzhou.pushserver.grpc.PluginPacketDispatcher", + "dev.qingzhou.push.api.spi.PushSender", + "dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper", + "com.fasterxml.jackson.databind.ObjectMapper", + "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper", + "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" + ] + }, + { + "name": "init", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.service.impl.PortalAccessTokenServiceImpl", "methods": [ @@ -2833,32 +2606,35 @@ "java.lang.Long", "java.lang.Long" ] - }, + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl$$SpringCGLIB$$0", + "fields": [ { - "name": "requireAppByApiKey", - "parameterTypes": [ - "java.lang.String" - ] + "name": "CGLIB$CALLBACK_FILTER" }, { - "name": "rotateKey", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] - }, + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalAppPluginServiceImpl", + "methods": [ { - "name": "updateRateLimit", + "name": "", "parameterTypes": [ - "java.lang.Long", - "java.lang.Long", - "java.lang.Integer" + "dev.qingzhou.pushserver.service.PortalWecomAppService", + "dev.qingzhou.pushserver.service.PluginManagerService", + "dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper" ] } ] }, { - "type": "dev.qingzhou.pushserver.service.impl.PortalAppApiKeyServiceImpl$$SpringCGLIB$$0", + "type": "dev.qingzhou.pushserver.service.impl.PortalAppPluginServiceImpl$$SpringCGLIB$$0", "fields": [ { "name": "CGLIB$CALLBACK_FILTER" @@ -2875,23 +2651,10 @@ "name": "", "parameterTypes": [] }, - { - "name": "getByUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, { "name": "requireByUserId", - "parameterTypes": [ - "java.lang.Long" - ] - }, - { - "name": "upsert", - "parameterTypes": [ - "java.lang.Long", - "java.lang.String" + "parameterTypes": [ + "java.lang.Long" ] } ] @@ -2913,16 +2676,6 @@ { "name": "", "parameterTypes": [] - }, - { - "name": "pageLogs", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long", - "java.lang.Boolean", - "int", - "int" - ] } ] }, @@ -2955,25 +2708,46 @@ ] }, { - "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl", + "type": "dev.qingzhou.pushserver.service.impl.PortalPluginServiceImpl", "methods": [ { "name": "", "parameterTypes": [ - "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" + "dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper", + "dev.qingzhou.pushserver.service.PluginManagerService", + "dev.qingzhou.pushserver.grpc.PluginConnectionManager" ] }, { - "name": "getByUserId", + "name": "listPlugins", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalPluginServiceImpl$$SpringCGLIB$$0", + "fields": [ + { + "name": "CGLIB$CALLBACK_FILTER" + }, + { + "name": "CGLIB$FACTORY_DATA" + } + ] + }, + { + "type": "dev.qingzhou.pushserver.service.impl.PortalProxyConfigServiceImpl", + "methods": [ + { + "name": "", "parameterTypes": [ - "java.lang.Long" + "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" ] }, { - "name": "upsert", + "name": "getByUserId", "parameterTypes": [ - "java.lang.Long", - "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest" + "java.lang.Long" ] } ] @@ -3029,14 +2803,6 @@ "dev.qingzhou.pushserver.service.PortalProxyConfigService" ] }, - { - "name": "addApp", - "parameterTypes": [ - "java.lang.Long", - "java.lang.String", - "java.lang.String" - ] - }, { "name": "listByUser", "parameterTypes": [ @@ -3049,13 +2815,6 @@ "java.lang.Long", "java.lang.Long" ] - }, - { - "name": "syncApp", - "parameterTypes": [ - "java.lang.Long", - "java.lang.Long" - ] } ] }, @@ -3081,6 +2840,17 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.service.impl.ServerPushSender", + "methods": [ + { + "name": "", + "parameterTypes": [ + "dev.qingzhou.pushserver.service.PushService" + ] + } + ] + }, { "type": "dev.qingzhou.pushserver.service.impl.SystemConfigServiceImpl", "methods": [ @@ -3097,6 +2867,14 @@ "java.lang.String" ] }, + { + "name": "getTurnstileSecretKey", + "parameterTypes": [] + }, + { + "name": "getTurnstileSiteKey", + "parameterTypes": [] + }, { "name": "isTurnstileEnabled", "parameterTypes": [] @@ -3120,6 +2898,114 @@ { "type": "int[]" }, + { + "type": "io.grpc.BindableService" + }, + { + "type": "io.grpc.ServerInterceptor" + }, + { + "type": "io.grpc.census.InternalCensusStatsAccessor" + }, + { + "type": "io.grpc.census.InternalCensusTracingAccessor" + }, + { + "type": "io.grpc.netty.shaded.io.grpc.netty.NettyServerProvider" + }, + { + "type": "io.grpc.netty.shaded.io.netty.bootstrap.ServerBootstrap$1" + }, + { + "type": "io.grpc.netty.shaded.io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor" + }, + { + "type": "io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.ChannelOutboundBuffer" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.DefaultChannelConfig" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$TailContext" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.DefaultFileRegion" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.epoll.Epoll", + "methods": [ + { + "name": "isAvailable", + "parameterTypes": [] + }, + { + "name": "unavailabilityCause", + "parameterTypes": [] + } + ] + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.epoll.NativeDatagramPacketArray$NativeDatagramPacket" + }, + { + "type": "io.grpc.netty.shaded.io.netty.channel.unix.PeerCredentials" + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.AbstractReferenceCounted", + "fields": [ + { + "name": "refCnt" + } + ] + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.DefaultAttributeMap" + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.concurrent.DefaultPromise" + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor" + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields", + "fields": [ + { + "name": "producerLimit" + } + ] + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields", + "fields": [ + { + "name": "consumerIndex" + } + ] + }, + { + "type": "io.grpc.netty.shaded.io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields", + "fields": [ + { + "name": "producerIndex" + } + ] + }, + { + "type": "io.grpc.okhttp.OkHttpServerProvider" + }, { "type": "io.micrometer.context.ContextSnapshot" }, @@ -3246,65 +3132,6 @@ { "type": "jakarta.validation.bootstrap.GenericBootstrap" }, - { - "type": "jakarta.validation.constraints.Max", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - }, - { - "name": "value", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.constraints.Min", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - }, - { - "name": "value", - "parameterTypes": [] - } - ] - }, - { - "type": "jakarta.validation.constraints.NotBlank", - "methods": [ - { - "name": "groups", - "parameterTypes": [] - }, - { - "name": "message", - "parameterTypes": [] - }, - { - "name": "payload", - "parameterTypes": [] - } - ] - }, { "type": "jakarta.validation.constraints.NotNull", "methods": [ @@ -3340,6 +3167,9 @@ { "type": "java.io.Closeable" }, + { + "type": "java.io.FileDescriptor" + }, { "type": "java.io.Serializable" }, @@ -3349,6 +3179,9 @@ { "type": "java.lang.AutoCloseable" }, + { + "type": "java.lang.BaseVirtualThread" + }, { "type": "java.lang.Boolean", "jniAccessible": true, @@ -3427,6 +3260,22 @@ { "type": "java.lang.Object[]" }, + { + "type": "java.lang.ProcessHandle", + "methods": [ + { + "name": "current", + "parameterTypes": [] + }, + { + "name": "pid", + "parameterTypes": [] + } + ] + }, + { + "type": "java.lang.Record" + }, { "type": "java.lang.RuntimeException" }, @@ -3440,7 +3289,13 @@ "type": "java.lang.System" }, { - "type": "java.lang.Thread" + "type": "java.lang.Thread", + "methods": [ + { + "name": "isVirtual", + "parameterTypes": [] + } + ] }, { "type": "java.lang.Thread$Builder" @@ -3559,12 +3414,41 @@ { "type": "java.net.http.HttpClient" }, + { + "type": "java.nio.Bits" + }, + { + "type": "java.nio.Buffer", + "fields": [ + { + "name": "address" + } + ] + }, + { + "type": "java.nio.ByteBuffer" + }, + { + "type": "java.nio.DirectByteBuffer" + }, + { + "type": "java.nio.channels.FileChannel" + }, + { + "type": "java.nio.channels.spi.SelectorProvider" + }, { "type": "java.security.AlgorithmParametersSpi" }, { "type": "java.security.KeyStoreSpi" }, + { + "type": "java.security.interfaces.ECPrivateKey" + }, + { + "type": "java.security.interfaces.ECPublicKey" + }, { "type": "java.security.interfaces.RSAPrivateKey" }, @@ -3595,6 +3479,9 @@ { "type": "java.text.ListFormat" }, + { + "type": "java.time.Instant" + }, { "type": "java.util.AbstractMap" }, @@ -3652,9 +3539,21 @@ { "type": "java.util.concurrent.Executor" }, + { + "type": "java.util.concurrent.ScheduledExecutorService" + }, { "type": "java.util.concurrent.ThreadFactory" }, + { + "type": "java.util.concurrent.atomic.LongAdder", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "java.util.logging.LogManager" }, @@ -3701,7 +3600,13 @@ "type": "jdk.internal.loader.ClassLoaders$PlatformClassLoader" }, { - "type": "jdk.internal.misc.Unsafe" + "type": "jdk.internal.misc.Unsafe", + "methods": [ + { + "name": "getUnsafe", + "parameterTypes": [] + } + ] }, { "type": "kotlin.Metadata" @@ -3733,6 +3638,12 @@ { "type": "oracle.ucp.jdbc.PoolDataSourceImpl" }, + { + "type": "org.aopalliance.aop.Advice" + }, + { + "type": "org.aopalliance.intercept.Interceptor" + }, { "type": "org.aopalliance.intercept.MethodInterceptor" }, @@ -3987,13 +3898,6 @@ "java.lang.String", "java.lang.Object" ] - }, - { - "name": "update", - "parameterTypes": [ - "java.lang.String", - "java.lang.Object" - ] } ] }, @@ -4074,15 +3978,6 @@ { "type": "org.hibernate.validator.HibernateValidator" }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, { "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator", "methods": [ @@ -4092,30 +3987,6 @@ } ] }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMaxValidator" - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMinValidator" - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MaxValidatorForInteger", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MinValidatorForInteger", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, { "type": "org.hibernate.validator.internal.engine.AbstractConfigurationImpl", "methods": [ @@ -4285,6 +4156,9 @@ { "type": "org.slf4j.spi.LocationAwareLogger" }, + { + "type": "org.springframework.aop.Advisor" + }, { "type": "org.springframework.aop.PointcutAdvisor" }, @@ -4362,6 +4236,9 @@ { "type": "org.springframework.aop.support.AbstractPointcutAdvisor" }, + { + "type": "org.springframework.aot.generate.Generated" + }, { "type": "org.springframework.aot.hint.annotation.Reflective" }, @@ -4422,6 +4299,9 @@ { "type": "org.springframework.beans.factory.config.BeanPostProcessor" }, + { + "type": "org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor" + }, { "type": "org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor" }, @@ -4457,6 +4337,9 @@ } ] }, + { + "type": "org.springframework.boot.LazyInitializationExcludeFilter" + }, { "type": "org.springframework.boot.SpringBootConfiguration" }, @@ -4873,6 +4756,9 @@ { "type": "org.springframework.boot.autoconfigure.ssl.SslPropertiesBundleRegistrar" }, + { + "type": "org.springframework.boot.autoconfigure.task.ScheduledBeanLazyInitializationExcludeFilter" + }, { "type": "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", "methods": [ @@ -4989,6 +4875,10 @@ { "name": "", "parameterTypes": [] + }, + { + "name": "scheduledBeanLazyInitializationExcludeFilter", + "parameterTypes": [] } ] }, @@ -5010,7 +4900,19 @@ ] }, { - "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration" + "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "taskScheduler", + "parameterTypes": [ + "org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder" + ] + } + ] }, { "type": "org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration", @@ -5335,7 +5237,31 @@ "type": "org.springframework.boot.http.converter.autoconfigure.DefaultServerHttpMessageConvertersCustomizer" }, { - "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration" + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] }, { "type": "org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration", @@ -5405,6 +5331,9 @@ } ] }, + { + "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer" + }, { "type": "org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$PreferJackson2OrJacksonUnavailableCondition", "methods": [ @@ -7129,9 +7058,37 @@ { "type": "org.springframework.scheduling.SchedulingTaskExecutor" }, + { + "type": "org.springframework.scheduling.TaskScheduler" + }, { "type": "org.springframework.scheduling.annotation.AsyncConfigurer" }, + { + "type": "org.springframework.scheduling.annotation.EnableScheduling" + }, + { + "type": "org.springframework.scheduling.annotation.Scheduled" + }, + { + "type": "org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" + }, + { + "type": "org.springframework.scheduling.annotation.Schedules" + }, + { + "type": "org.springframework.scheduling.annotation.SchedulingConfiguration", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "scheduledAnnotationProcessor", + "parameterTypes": [] + } + ] + }, { "type": "org.springframework.scheduling.concurrent.CustomizableThreadFactory" }, @@ -7144,6 +7101,9 @@ { "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler" }, + { + "type": "org.springframework.scheduling.config.ScheduledTaskHolder" + }, { "type": "org.springframework.security.access.SecurityConfig" }, @@ -8426,6 +8386,25 @@ { "name": "theUnsafe" } + ], + "methods": [ + { + "name": "invokeCleaner", + "parameterTypes": [ + "java.nio.ByteBuffer" + ] + } + ] + }, + { + "type": "sun.nio.ch.SelectorImpl", + "fields": [ + { + "name": "publicSelectedKeys" + }, + { + "name": "selectedKeys" + } ] }, { @@ -8861,6 +8840,13 @@ ] } }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper" + ] + } + }, { "type": { "proxy": [ @@ -8875,6 +8861,27 @@ ] } }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper" + ] + } + }, + { + "type": { + "proxy": [ + "dev.qingzhou.pushserver.mapper.portal.PortalPluginMapper" + ] + } + }, { "type": { "proxy": [ @@ -9007,6 +9014,22 @@ ] } }, + { + "type": { + "lambda": { + "declaringClass": "dev.qingzhou.pushserver.service.impl.PortalPluginServiceImpl", + "interfaces": [ + "com.baomidou.mybatisplus.core.toolkit.support.SFunction" + ] + } + }, + "methods": [ + { + "name": "writeReplace", + "parameterTypes": [] + } + ] + }, { "type": { "lambda": { @@ -9052,12 +9075,24 @@ { "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.xml" + }, { "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" }, { "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.xml" + }, + { + "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.xml" + }, { "glob": "/dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" }, @@ -9082,6 +9117,9 @@ { "glob": "META-INF/services/com.baomidou.mybatisplus.core.spi.CompatibleSet" }, + { + "glob": "META-INF/services/io.grpc.ServerProvider" + }, { "glob": "META-INF/services/jakarta.el.ExpressionFactory" }, @@ -9259,12 +9297,27 @@ { "glob": "data.sql" }, + { + "glob": "dev/qingzhou/push/api/grpc/PluginGatewayGrpc$AsyncService.class" + }, + { + "glob": "dev/qingzhou/push/api/grpc/PluginGatewayGrpc$PluginGatewayImplBase.class" + }, + { + "glob": "dev/qingzhou/push/api/spi/PushPlugin.class" + }, + { + "glob": "dev/qingzhou/push/api/spi/PushSender.class" + }, { "glob": "dev/qingzhou/pushserver" }, { "glob": "dev/qingzhou/pushserver/aspect/SecurityInterceptor.class" }, + { + "glob": "dev/qingzhou/pushserver/config/GrpcServerConfig.class" + }, { "glob": "dev/qingzhou/pushserver/config/JsonDtoPackageHints$DtoHints.class" }, @@ -9304,6 +9357,9 @@ { "glob": "dev/qingzhou/pushserver/config/PushConfiguration.class" }, + { + "glob": "dev/qingzhou/pushserver/config/SchedulingConfig.class" + }, { "glob": "dev/qingzhou/pushserver/config/WebConfig.class" }, @@ -9322,6 +9378,9 @@ { "glob": "dev/qingzhou/pushserver/controller/PortalAppController.class" }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalAppPluginController.class" + }, { "glob": "dev/qingzhou/pushserver/controller/PortalAuthController.class" }, @@ -9340,6 +9399,12 @@ { "glob": "dev/qingzhou/pushserver/controller/PortalMessageController.class" }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalPluginController.class" + }, + { + "glob": "dev/qingzhou/pushserver/controller/PortalPluginLogController.class" + }, { "glob": "dev/qingzhou/pushserver/controller/PortalProxyController.class" }, @@ -9361,18 +9426,45 @@ { "glob": "dev/qingzhou/pushserver/exception/PortalExceptionHandler.class" }, + { + "glob": "dev/qingzhou/pushserver/grpc/GrpcAdapter.class" + }, + { + "glob": "dev/qingzhou/pushserver/grpc/PluginAuthInterceptor.class" + }, + { + "glob": "dev/qingzhou/pushserver/grpc/PluginConnectionManager.class" + }, + { + "glob": "dev/qingzhou/pushserver/grpc/PluginGatewayImpl.class" + }, + { + "glob": "dev/qingzhou/pushserver/grpc/PluginPacketDispatcher.class" + }, { "glob": "dev/qingzhou/pushserver/manager/wecom/WecomApiClient.class" }, { "glob": "dev/qingzhou/pushserver/mapper/portal/PortalAppApiKeyMapper.xml" }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalAppPluginConfigMapper.xml" + }, { "glob": "dev/qingzhou/pushserver/mapper/portal/PortalCorpConfigMapper.xml" }, { "glob": "dev/qingzhou/pushserver/mapper/portal/PortalMessageLogMapper.xml" }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalPluginActionLogMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalPluginHeartbeatLogMapper.xml" + }, + { + "glob": "dev/qingzhou/pushserver/mapper/portal/PortalPluginMapper.xml" + }, { "glob": "dev/qingzhou/pushserver/mapper/portal/PortalProxyConfigMapper.xml" }, @@ -9385,6 +9477,12 @@ { "glob": "dev/qingzhou/pushserver/mapper/portal/PortalWecomAppMapper.xml" }, + { + "glob": "dev/qingzhou/pushserver/monitor/PluginHeartbeatMonitor.class" + }, + { + "glob": "dev/qingzhou/pushserver/plugin/builtin/WebhookPlugin.class" + }, { "glob": "dev/qingzhou/pushserver/security/CaptchaService.class" }, @@ -9400,12 +9498,18 @@ { "glob": "dev/qingzhou/pushserver/service/DashboardService.class" }, + { + "glob": "dev/qingzhou/pushserver/service/PluginManagerService.class" + }, { "glob": "dev/qingzhou/pushserver/service/PortalAccessTokenService.class" }, { "glob": "dev/qingzhou/pushserver/service/PortalAppApiKeyService.class" }, + { + "glob": "dev/qingzhou/pushserver/service/PortalAppPluginService.class" + }, { "glob": "dev/qingzhou/pushserver/service/PortalCorpConfigService.class" }, @@ -9415,6 +9519,9 @@ { "glob": "dev/qingzhou/pushserver/service/PortalMessageService.class" }, + { + "glob": "dev/qingzhou/pushserver/service/PortalPluginService.class" + }, { "glob": "dev/qingzhou/pushserver/service/PortalProxyConfigService.class" }, @@ -9433,6 +9540,9 @@ { "glob": "dev/qingzhou/pushserver/service/impl/DashboardServiceImpl.class" }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PluginManagerServiceImpl.class" + }, { "glob": "dev/qingzhou/pushserver/service/impl/PortalAccessTokenServiceImpl$CachedToken.class" }, @@ -9442,6 +9552,9 @@ { "glob": "dev/qingzhou/pushserver/service/impl/PortalAppApiKeyServiceImpl.class" }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalAppPluginServiceImpl.class" + }, { "glob": "dev/qingzhou/pushserver/service/impl/PortalCorpConfigServiceImpl.class" }, @@ -9451,6 +9564,9 @@ { "glob": "dev/qingzhou/pushserver/service/impl/PortalMessageServiceImpl.class" }, + { + "glob": "dev/qingzhou/pushserver/service/impl/PortalPluginServiceImpl.class" + }, { "glob": "dev/qingzhou/pushserver/service/impl/PortalProxyConfigServiceImpl.class" }, @@ -9463,12 +9579,21 @@ { "glob": "dev/qingzhou/pushserver/service/impl/PushServiceImpl.class" }, + { + "glob": "dev/qingzhou/pushserver/service/impl/ServerPushSender.class" + }, { "glob": "dev/qingzhou/pushserver/service/impl/SystemConfigServiceImpl.class" }, { "glob": "git.properties" }, + { + "glob": "io/grpc/BindableService.class" + }, + { + "glob": "io/grpc/ServerInterceptor.class" + }, { "glob": "jakarta/servlet/LocalStrings.properties" }, @@ -10165,6 +10290,33 @@ { "glob": "org/springframework/boot/gson/autoconfigure/GsonAutoConfiguration.class" }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$GsonHttpConvertersCustomizer.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$GsonHttpMessageConverterConfiguration.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$Jackson2Available.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$JacksonAvailable.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition$JsonbPreferred.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$JacksonAndJsonbUnavailableCondition.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition$GsonPreferred.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition$JacksonJsonbUnavailable.class" + }, + { + "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration$PreferGsonOrJacksonAndJsonbUnavailableCondition.class" + }, { "glob": "org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.class" }, @@ -10621,6 +10773,12 @@ { "glob": "org/springframework/core/annotation/Order.class" }, + { + "glob": "org/springframework/scheduling/annotation/EnableScheduling.class" + }, + { + "glob": "org/springframework/scheduling/annotation/SchedulingConfiguration.class" + }, { "glob": "org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration$AuthenticationManagerDelegator.class" }, @@ -10745,25 +10903,22 @@ "glob": "sqlite-jdbc.properties" }, { - "glob": "static/.well-known/appspecific/com.chrome.devtools.json" - }, - { - "glob": "static/assets/DashboardView-BKGHLnEP.js" + "glob": "static/assets/DashboardView-BQ-3UbUb.js" }, { "glob": "static/assets/DashboardView-DHdOv7sv.css" }, { - "glob": "static/assets/ProxyView-BNsKsUAn.css" + "glob": "static/assets/PluginsView-CeF5nKD9.css" }, { - "glob": "static/assets/ProxyView-CdQhhSdP.js" + "glob": "static/assets/PluginsView-DhuTfWVL.js" }, { - "glob": "static/assets/index-C9wU1arO.js" + "glob": "static/assets/index-BQHYf_DI.js" }, { - "glob": "static/assets/index-sMwk8kPl.css" + "glob": "static/assets/index-FpXCWsKn.css" }, { "glob": "static/favicon.png" @@ -10774,12 +10929,6 @@ { "glob": "static/logo.png" }, - { - "glob": "static/system/content.css.map" - }, - { - "glob": "static/system/sidebar.css.map" - }, { "module": "java.base", "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" From 5b839c52013ed847802b2b4e0bcdb094a6710852 Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 23:12:48 +0800 Subject: [PATCH 09/15] =?UTF-8?q?feat(native-image):=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E5=8F=8D=E5=B0=84=E9=85=8D=E7=BD=AE=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E6=8F=92=E4=BB=B6=E5=92=8C=20Portal=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E7=9B=B8=E5=85=B3=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native-image/reachability-metadata.json | 1704 ++++++++++++++++- 1 file changed, 1616 insertions(+), 88 deletions(-) diff --git a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json index e5c21ed..42ea9e1 100644 --- a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -167,6 +167,10 @@ { "type": "com.baomidou.mybatisplus.core.conditions.Wrapper", "methods": [ + { + "name": "getSqlSet", + "parameterTypes": [] + }, { "name": "isEmptyOfNormal", "parameterTypes": [] @@ -225,6 +229,13 @@ "com.baomidou.mybatisplus.core.conditions.Wrapper", "boolean" ] + }, + { + "name": "selectPage", + "parameterTypes": [ + "com.baomidou.mybatisplus.core.metadata.IPage", + "com.baomidou.mybatisplus.core.conditions.Wrapper" + ] } ] }, @@ -233,16 +244,9 @@ }, { "type": "com.baomidou.mybatisplus.core.override.MybatisMapperProxy", - "methods": [ - { - "name": "getMapperInterface", - "parameterTypes": [] - }, - { - "name": "getSqlSession", - "parameterTypes": [] - } - ] + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true }, { "type": "com.baomidou.mybatisplus.core.toolkit.Wrappers$EmptyWrapper" @@ -289,6 +293,12 @@ "com.baomidou.mybatisplus.core.conditions.Wrapper" ] }, + { + "name": "getById", + "parameterTypes": [ + "java.io.Serializable" + ] + }, { "name": "list", "parameterTypes": [ @@ -393,6 +403,7 @@ }, { "type": "com.github.benmanes.caffeine.cache.PS", + "allDeclaredConstructors": true, "fields": [ { "name": "key" @@ -402,22 +413,30 @@ } ] }, + { + "type": "com.github.benmanes.caffeine.cache.PSL", + "allDeclaredConstructors": true + }, { "type": "com.github.benmanes.caffeine.cache.PSW", + "allDeclaredConstructors": true, "fields": [ { "name": "writeTime" } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } ] }, + { + "type": "com.github.benmanes.caffeine.cache.SS", + "allDeclaredConstructors": true + }, + { + "type": "com.github.benmanes.caffeine.cache.SSL", + "allDeclaredConstructors": true + }, { "type": "com.github.benmanes.caffeine.cache.SSW", + "allDeclaredConstructors": true, "fields": [ { "name": "FACTORY" @@ -582,6 +601,74 @@ { "type": "dev.qingzhou.push.api.grpc.PluginGatewayGrpc$PluginGatewayImplBase" }, + { + "type": "dev.qingzhou.push.api.model.ConfigField", + "methods": [ + { + "name": "getDefaultValue", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getLabel", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getOptions", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "isRequired", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.push.api.model.ConfigField[]" + }, + { + "type": "dev.qingzhou.push.api.model.ConfigType" + }, + { + "type": "dev.qingzhou.push.api.model.PluginMeta", + "methods": [ + { + "name": "getConfigFields", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getMaxConcurrency", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getVersion", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.push.api.spi.PushPlugin" }, @@ -1161,6 +1248,27 @@ "dev.qingzhou.pushserver.service.PortalAppApiKeyService" ] }, + { + "name": "create", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "createApiKey", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "deleteApiKey", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, { "name": "getApiKey", "parameterTypes": [ @@ -1173,6 +1281,29 @@ "parameterTypes": [ "jakarta.servlet.http.HttpSession" ] + }, + { + "name": "sync", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "update", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.PortalAppUpdateRequest", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "updateApiKey", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", + "jakarta.servlet.http.HttpSession" + ] } ] }, @@ -1184,6 +1315,21 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalAppPluginService" ] + }, + { + "name": "list", + "parameterTypes": [ + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "saveConfig", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest", + "jakarta.servlet.http.HttpSession" + ] } ] }, @@ -1212,6 +1358,19 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalCorpConfigService" ] + }, + { + "name": "getCorp", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", + "jakarta.servlet.http.HttpSession" + ] } ] }, @@ -1237,6 +1396,12 @@ { "name": "getInitStatus", "parameterTypes": [] + }, + { + "name": "initialize", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalInitRequest" + ] } ] }, @@ -1248,6 +1413,12 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalUserService" ] + }, + { + "name": "me", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] } ] }, @@ -1261,6 +1432,17 @@ "dev.qingzhou.pushserver.service.PortalMessageLogService" ] }, + { + "name": "logs", + "parameterTypes": [ + "java.lang.Integer", + "java.lang.Integer", + "int", + "java.lang.Boolean", + "java.lang.Long", + "jakarta.servlet.http.HttpSession" + ] + }, { "name": "send", "parameterTypes": [ @@ -1294,6 +1476,14 @@ "dev.qingzhou.pushserver.mapper.portal.PortalPluginActionLogMapper", "dev.qingzhou.pushserver.mapper.portal.PortalPluginHeartbeatLogMapper" ] + }, + { + "name": "listActions", + "parameterTypes": [ + "java.lang.String", + "int", + "int" + ] } ] }, @@ -1305,6 +1495,19 @@ "parameterTypes": [ "dev.qingzhou.pushserver.service.PortalProxyConfigService" ] + }, + { + "name": "getProxy", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, + { + "name": "upsert", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", + "jakarta.servlet.http.HttpSession" + ] } ] }, @@ -1353,6 +1556,13 @@ "dev.qingzhou.pushserver.service.PortalAppApiKeyService", "dev.qingzhou.pushserver.service.PortalMessageService" ] + }, + { + "name": "send", + "parameterTypes": [ + "java.lang.String", + "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest" + ] } ] }, @@ -1367,6 +1577,16 @@ "dev.qingzhou.pushserver.service.PluginManagerService", "org.springframework.core.task.TaskExecutor" ] + }, + { + "name": "handleMessage", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String", + "java.lang.String", + "java.lang.String", + "java.lang.String" + ] } ] }, @@ -1385,6 +1605,12 @@ { "name": "", "parameterTypes": [] + }, + { + "name": "handlePortalException", + "parameterTypes": [ + "dev.qingzhou.pushserver.exception.PortalException" + ] } ] }, @@ -1444,6 +1670,33 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomAgentInfo", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAvatarUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setName", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, { "type": "dev.qingzhou.pushserver.manager.wecom.WecomApiClient", "methods": [ @@ -1595,6 +1848,15 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$Text", + "methods": [ + { + "name": "getContent", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.manager.wecom.WecomMessagePayload$TextCard", "methods": [ @@ -1721,35 +1983,261 @@ "type": "dev.qingzhou.pushserver.mapper.portal.PortalWecomAppMapper" }, { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest", + "type": "dev.qingzhou.pushserver.model.dto.openapi.OpenApiMessageSendRequest", "methods": [ { "name": "", "parameterTypes": [] }, { - "name": "setAccount", + "name": "setArticles", + "parameterTypes": [ + "java.util.List" + ] + }, + { + "name": "setBtnText", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setCaptcha", + "name": "setContent", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setPassword", + "name": "setDescription", "parameterTypes": [ "java.lang.String" ] - } - ] - }, - { - "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", - "fields": [ + }, + { + "name": "setMsgType", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" + ] + }, + { + "name": "setTitle", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setToAll", + "parameterTypes": [ + "java.lang.Boolean" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest", + "fields": [ + { + "name": "pluginKey" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setConfigJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPluginKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setStatus", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppApiKeyUpdateRequest", + "fields": [ + { + "name": "rateLimitPerMinute" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setRateLimitPerMinute", + "parameterTypes": [ + "java.lang.Integer" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppCreateRequest", + "fields": [ + { + "name": "agentId" + }, + { + "name": "secret" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAgentId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSecret", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalAppUpdateRequest", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setEncodingAesKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setToken", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalCorpConfigRequest", + "fields": [ + { + "name": "corpId" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setCorpId", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalInitRequest", + "fields": [ + { + "name": "password" + }, + { + "name": "username" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setTurnstileEnabled", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setTurnstileSecretKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setTurnstileSiteKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUsername", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalLoginRequest", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "setAccount", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCaptcha", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPassword", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageSendRequest", + "fields": [ { "name": "appId" } @@ -1807,6 +2295,12 @@ "java.lang.Boolean" ] }, + { + "name": "setToUser", + "parameterTypes": [ + "java.lang.String" + ] + }, { "name": "setUrl", "parameterTypes": [ @@ -1852,67 +2346,81 @@ "type": "dev.qingzhou.pushserver.model.dto.portal.PortalMessageType" }, { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey" - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig" - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig", + "type": "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest", + "fields": [ + { + "name": "host" + }, + { + "name": "port" + } + ], "methods": [ { "name": "", "parameterTypes": [] }, { - "name": "setCorpId", + "name": "setActive", + "parameterTypes": [ + "java.lang.Boolean" + ] + }, + { + "name": "setExitIp", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setCreatedAt", + "name": "setHost", "parameterTypes": [ - "java.lang.Long" + "java.lang.String" ] }, { - "name": "setId", + "name": "setPassword", "parameterTypes": [ - "java.lang.Long" + "java.lang.String" ] }, { - "name": "setUpdatedAt", + "name": "setPort", "parameterTypes": [ - "java.lang.Long" + "java.lang.Integer" ] }, { - "name": "setUserId", + "name": "setType", "parameterTypes": [ - "java.lang.Long" + "java.lang.String" + ] + }, + { + "name": "setUsername", + "parameterTypes": [ + "java.lang.String" ] } ] }, { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog", + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey", "methods": [ { "name": "", "parameterTypes": [] }, { - "name": "getAgentId", + "name": "getApiKeyHash", "parameterTypes": [] }, { - "name": "getAppId", + "name": "getApiKeyPlain", "parameterTypes": [] }, { - "name": "getContent", + "name": "getAppId", "parameterTypes": [] }, { @@ -1920,79 +2428,574 @@ "parameterTypes": [] }, { - "name": "getDescription", + "name": "getId", "parameterTypes": [] }, { - "name": "getErrorMessage", + "name": "getRateLimitPerMinute", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "setApiKeyHash", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setApiKeyPlain", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setRateLimitPerMinute", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppPluginConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getConfigJson", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getPluginKey", + "parameterTypes": [] + }, + { + "name": "getStatus", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setConfigJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setPluginKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setStatus", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalCorpConfig", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getCorpId", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setCorpId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUpdatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalMessageLog", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getContent", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getErrorMessage", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getMsgType", + "parameterTypes": [] + }, + { + "name": "getRequestJson", + "parameterTypes": [] + }, + { + "name": "getResponseJson", + "parameterTypes": [] + }, + { + "name": "getSuccess", + "parameterTypes": [] + }, + { + "name": "getTitle", + "parameterTypes": [] + }, + { + "name": "getToAll", + "parameterTypes": [] + }, + { + "name": "getToParty", + "parameterTypes": [] + }, + { + "name": "getToUser", + "parameterTypes": [] + }, + { + "name": "getUrl", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setAgentId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setContent", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setDescription", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setMsgType", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setRequestJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setResponseJson", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSuccess", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setTitle", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setToAll", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setToUser", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUrl", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.Long" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPlugin" + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getAppId", + "parameterTypes": [] + }, + { + "name": "getAppName", + "parameterTypes": [] + }, + { + "name": "getContent", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getEventId", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getMessage", + "parameterTypes": [] + }, + { + "name": "getPluginConfig", + "parameterTypes": [] + }, + { + "name": "getPluginKey", + "parameterTypes": [] + }, + { + "name": "getStatus", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, + { + "name": "setAppId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setAppName", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setContent", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setCreatedAt", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setEventId", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "setMessage", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPluginConfig", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setPluginKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setStatus", + "parameterTypes": [ + "java.lang.Integer" + ] + }, + { + "name": "setType", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setUserId", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog" + }, + { + "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig", + "methods": [ + { + "name": "", "parameterTypes": [] }, { - "name": "getId", + "name": "getActive", "parameterTypes": [] }, { - "name": "getMsgType", + "name": "getCreatedAt", "parameterTypes": [] }, { - "name": "getRequestJson", + "name": "getExitIp", "parameterTypes": [] }, { - "name": "getResponseJson", + "name": "getHost", "parameterTypes": [] }, { - "name": "getSuccess", + "name": "getId", "parameterTypes": [] }, { - "name": "getTitle", + "name": "getPassword", "parameterTypes": [] }, { - "name": "getToAll", + "name": "getPort", "parameterTypes": [] }, { - "name": "getToParty", + "name": "getType", "parameterTypes": [] }, { - "name": "getToUser", + "name": "getUpdatedAt", "parameterTypes": [] }, { - "name": "getUrl", + "name": "getUserId", "parameterTypes": [] }, { - "name": "getUserId", + "name": "getUsername", "parameterTypes": [] }, { - "name": "setAgentId", + "name": "setActive", "parameterTypes": [ - "java.lang.String" + "java.lang.Boolean" ] }, { - "name": "setAppId", + "name": "setCreatedAt", "parameterTypes": [ "java.lang.Long" ] }, { - "name": "setContent", + "name": "setExitIp", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setCreatedAt", + "name": "setHost", "parameterTypes": [ - "java.lang.Long" + "java.lang.String" ] }, { @@ -2002,55 +3005,43 @@ ] }, { - "name": "setMsgType", + "name": "setPassword", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setRequestJson", + "name": "setPort", "parameterTypes": [ - "java.lang.String" + "java.lang.Integer" ] }, { - "name": "setResponseJson", + "name": "setType", "parameterTypes": [ "java.lang.String" ] }, { - "name": "setSuccess", + "name": "setUpdatedAt", "parameterTypes": [ - "java.lang.Integer" + "java.lang.Long" ] }, { - "name": "setToAll", + "name": "setUserId", "parameterTypes": [ - "java.lang.Integer" + "java.lang.Long" ] }, { - "name": "setUserId", + "name": "setUsername", "parameterTypes": [ - "java.lang.Long" + "java.lang.String" ] } ] }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPlugin" - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginActionLog" - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalPluginHeartbeatLog" - }, - { - "type": "dev.qingzhou.pushserver.model.entity.portal.PortalProxyConfig" - }, { "type": "dev.qingzhou.pushserver.model.entity.portal.PortalSystemConfig", "methods": [ @@ -2058,6 +3049,22 @@ "name": "", "parameterTypes": [] }, + { + "name": "getConfigKey", + "parameterTypes": [] + }, + { + "name": "getConfigValue", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, { "name": "setConfigKey", "parameterTypes": [ @@ -2091,6 +3098,26 @@ "name": "", "parameterTypes": [] }, + { + "name": "getAccount", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getPasswordHash", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, { "name": "setAccount", "parameterTypes": [ @@ -2130,6 +3157,50 @@ "name": "", "parameterTypes": [] }, + { + "name": "getAgentId", + "parameterTypes": [] + }, + { + "name": "getAvatarUrl", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getEncodingAesKey", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getSecret", + "parameterTypes": [] + }, + { + "name": "getToken", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUserId", + "parameterTypes": [] + }, { "name": "setAgentId", "parameterTypes": [ @@ -2312,6 +3383,39 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppPluginConfigVo", + "methods": [ + { + "name": "getConfigJson", + "parameterTypes": [] + }, + { + "name": "getDescription", + "parameterTypes": [] + }, + { + "name": "getMeta", + "parameterTypes": [] + }, + { + "name": "getName", + "parameterTypes": [] + }, + { + "name": "getPluginKey", + "parameterTypes": [] + }, + { + "name": "getStatus", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.vo.portal.PortalAppResponse", "methods": [ @@ -2345,6 +3449,15 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalCorpResponse", + "methods": [ + { + "name": "getCorpId", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.vo.portal.PortalMessageLogResponse", "methods": [ @@ -2406,6 +3519,27 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPageResponse", + "methods": [ + { + "name": "getPage", + "parameterTypes": [] + }, + { + "name": "getPageSize", + "parameterTypes": [] + }, + { + "name": "getRecords", + "parameterTypes": [] + }, + { + "name": "getTotal", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.vo.portal.PortalPluginVo", "methods": [ @@ -2443,6 +3577,72 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalProxyConfigResponse", + "methods": [ + { + "name": "getActive", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getExitIp", + "parameterTypes": [] + }, + { + "name": "getHost", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getPassword", + "parameterTypes": [] + }, + { + "name": "getPort", + "parameterTypes": [] + }, + { + "name": "getType", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + }, + { + "name": "getUsername", + "parameterTypes": [] + } + ] + }, + { + "type": "dev.qingzhou.pushserver.model.vo.portal.PortalUserResponse", + "methods": [ + { + "name": "getAccount", + "parameterTypes": [] + }, + { + "name": "getCreatedAt", + "parameterTypes": [] + }, + { + "name": "getId", + "parameterTypes": [] + }, + { + "name": "getUpdatedAt", + "parameterTypes": [] + } + ] + }, { "type": "dev.qingzhou.pushserver.monitor.PluginHeartbeatMonitor", "methods": [ @@ -2604,7 +3804,34 @@ "name": "findByAppId", "parameterTypes": [ "java.lang.Long", - "java.lang.Long" + "java.lang.Long" + ] + }, + { + "name": "removeByAppId", + "parameterTypes": [ + "java.lang.Long" + ] + }, + { + "name": "requireAppByApiKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "rotateKey", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "updateRateLimit", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "java.lang.Integer" ] } ] @@ -2630,6 +3857,21 @@ "dev.qingzhou.pushserver.service.PluginManagerService", "dev.qingzhou.pushserver.mapper.portal.PortalAppPluginConfigMapper" ] + }, + { + "name": "listByApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "saveConfig", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.AppPluginConfigSaveRequest" + ] } ] }, @@ -2651,11 +3893,24 @@ "name": "", "parameterTypes": [] }, + { + "name": "getByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, { "name": "requireByUserId", "parameterTypes": [ "java.lang.Long" ] + }, + { + "name": "upsert", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String" + ] } ] }, @@ -2676,6 +3931,16 @@ { "name": "", "parameterTypes": [] + }, + { + "name": "pageLogs", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "java.lang.Boolean", + "int", + "int" + ] } ] }, @@ -2749,6 +4014,13 @@ "parameterTypes": [ "java.lang.Long" ] + }, + { + "name": "upsert", + "parameterTypes": [ + "java.lang.Long", + "dev.qingzhou.pushserver.model.dto.portal.PortalProxyConfigRequest" + ] } ] }, @@ -2777,6 +4049,13 @@ "parameterTypes": [ "java.lang.String" ] + }, + { + "name": "register", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] } ] }, @@ -2803,6 +4082,14 @@ "dev.qingzhou.pushserver.service.PortalProxyConfigService" ] }, + { + "name": "addApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.String", + "java.lang.String" + ] + }, { "name": "listByUser", "parameterTypes": [ @@ -2815,6 +4102,23 @@ "java.lang.Long", "java.lang.Long" ] + }, + { + "name": "syncApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long" + ] + }, + { + "name": "updateApp", + "parameterTypes": [ + "java.lang.Long", + "java.lang.Long", + "java.lang.String", + "java.lang.String", + "java.lang.String" + ] } ] }, @@ -2878,6 +4182,14 @@ { "name": "isTurnstileEnabled", "parameterTypes": [] + }, + { + "name": "setTurnstileConfig", + "parameterTypes": [ + "boolean", + "java.lang.String", + "java.lang.String" + ] } ] }, @@ -3132,6 +4444,65 @@ { "type": "jakarta.validation.bootstrap.GenericBootstrap" }, + { + "type": "jakarta.validation.constraints.Max", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + }, + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.Min", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + }, + { + "name": "value", + "parameterTypes": [] + } + ] + }, + { + "type": "jakarta.validation.constraints.NotBlank", + "methods": [ + { + "name": "groups", + "parameterTypes": [] + }, + { + "name": "message", + "parameterTypes": [] + }, + { + "name": "payload", + "parameterTypes": [] + } + ] + }, { "type": "jakarta.validation.constraints.NotNull", "methods": [ @@ -3233,6 +4604,9 @@ { "type": "java.lang.Error" }, + { + "type": "java.lang.Iterable" + }, { "type": "java.lang.Long" }, @@ -3482,6 +4856,12 @@ { "type": "java.time.Instant" }, + { + "type": "java.util.AbstractCollection" + }, + { + "type": "java.util.AbstractList" + }, { "type": "java.util.AbstractMap" }, @@ -3494,6 +4874,12 @@ } ] }, + { + "type": "java.util.Collection" + }, + { + "type": "java.util.Collections$EmptyList" + }, { "type": "java.util.Enumeration" }, @@ -3503,9 +4889,18 @@ { "type": "java.util.HashSet" }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableCollection" + }, + { + "type": "java.util.ImmutableCollections$AbstractImmutableList" + }, { "type": "java.util.ImmutableCollections$AbstractImmutableMap" }, + { + "type": "java.util.ImmutableCollections$ListN" + }, { "type": "java.util.ImmutableCollections$Map1" }, @@ -3527,6 +4922,9 @@ { "type": "java.util.MapCustomizer" }, + { + "type": "java.util.RandomAccess" + }, { "type": "java.util.SequencedCollection" }, @@ -3764,6 +5162,12 @@ { "type": "org.apache.ibatis.annotations.Mapper" }, + { + "type": "org.apache.ibatis.binding.MapperProxy", + "allDeclaredFields": true, + "allDeclaredMethods": true, + "allDeclaredConstructors": true + }, { "type": "org.apache.ibatis.executor.Executor", "methods": [ @@ -3836,6 +5240,23 @@ } ] }, + { + "type": "org.apache.ibatis.mapping.BoundSql", + "fields": [ + { + "name": "parameterMappings" + }, + { + "name": "sql" + } + ], + "methods": [ + { + "name": "getAdditionalParameters", + "parameterTypes": [] + } + ] + }, { "type": "org.apache.ibatis.ognl.OgnlRuntime$ClassPropertyMethodCache" }, @@ -3878,6 +5299,13 @@ { "type": "org.apache.ibatis.session.SqlSession", "methods": [ + { + "name": "delete", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] + }, { "name": "insert", "parameterTypes": [ @@ -3898,6 +5326,13 @@ "java.lang.String", "java.lang.Object" ] + }, + { + "name": "update", + "parameterTypes": [ + "java.lang.String", + "java.lang.Object" + ] } ] }, @@ -3978,6 +5413,15 @@ { "type": "org.hibernate.validator.HibernateValidator" }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotBlankValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "org.hibernate.validator.internal.constraintvalidators.bv.NotNullValidator", "methods": [ @@ -3987,6 +5431,30 @@ } ] }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMaxValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.AbstractMinValidator" + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MaxValidatorForInteger", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "type": "org.hibernate.validator.internal.constraintvalidators.bv.number.bound.MinValidatorForInteger", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, { "type": "org.hibernate.validator.internal.engine.AbstractConfigurationImpl", "methods": [ @@ -9014,6 +10482,54 @@ ] } }, + { + "type": { + "lambda": { + "declaringClass": "dev.qingzhou.pushserver.controller.PortalPluginLogController", + "interfaces": [ + "com.baomidou.mybatisplus.core.toolkit.support.SFunction" + ] + } + }, + "methods": [ + { + "name": "writeReplace", + "parameterTypes": [] + } + ] + }, + { + "type": { + "lambda": { + "declaringClass": "dev.qingzhou.pushserver.service.impl.PluginManagerServiceImpl", + "interfaces": [ + "com.baomidou.mybatisplus.core.toolkit.support.SFunction" + ] + } + }, + "methods": [ + { + "name": "writeReplace", + "parameterTypes": [] + } + ] + }, + { + "type": { + "lambda": { + "declaringClass": "dev.qingzhou.pushserver.service.impl.PortalAppPluginServiceImpl", + "interfaces": [ + "com.baomidou.mybatisplus.core.toolkit.support.SFunction" + ] + } + }, + "methods": [ + { + "name": "writeReplace", + "parameterTypes": [] + } + ] + }, { "type": { "lambda": { @@ -10908,12 +12424,24 @@ { "glob": "static/assets/DashboardView-DHdOv7sv.css" }, + { + "glob": "static/assets/InitView-Bab0GDZm.css" + }, + { + "glob": "static/assets/InitView-Dn7DLcnb.js" + }, { "glob": "static/assets/PluginsView-CeF5nKD9.css" }, { "glob": "static/assets/PluginsView-DhuTfWVL.js" }, + { + "glob": "static/assets/ProxyView-BNsKsUAn.css" + }, + { + "glob": "static/assets/ProxyView-DRIg8mIw.js" + }, { "glob": "static/assets/index-BQHYf_DI.js" }, From c8c2cb6905db64aa7be0d7bac0b5cc643f0236aa Mon Sep 17 00:00:00 2001 From: ma Date: Tue, 3 Feb 2026 23:19:03 +0800 Subject: [PATCH 10/15] =?UTF-8?q?feat(native-image):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8F=8D=E5=B0=84=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81=20gRPC?= =?UTF-8?q?=20=E7=9A=84=20Netty=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- push-server-core/pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/push-server-core/pom.xml b/push-server-core/pom.xml index 24fcfdf..445f5b9 100644 --- a/push-server-core/pom.xml +++ b/push-server-core/pom.xml @@ -145,8 +145,7 @@ --initialize-at-build-time=org.sqlite.util.ProcessRunner --initialize-at-build-time=org.sqlite.util.OSInfo --initialize-at-run-time=org.apache.ibatis - - --initialize-at-run-time=org.mybatis + --initialize-at-run-time=io.grpc.netty.shaded.io.netty --initialize-at-run-time=org.mybatis --initialize-at-run-time=org.apache.ibatis.logging --initialize-at-run-time=org.apache.ibatis.logging.LogFactory From ee5696e9e4e6179c373f9a41fafb123a8bb182d7 Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 11 Feb 2026 14:11:20 +0800 Subject: [PATCH 11/15] =?UTF-8?q?feat(native-image):=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E5=8F=8D=E5=B0=84=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81=E6=96=B0?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E5=92=8C=E4=BA=8B=E4=BB=B6=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/proto/plugin_gateway.proto | 1 + .../wecom/WecomCallbackController.java | 3 ++ .../pushserver/plugin/GrpcPluginProxy.java | 2 + .../native-image/reachability-metadata.json | 54 +++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/push-server-api/src/main/proto/plugin_gateway.proto b/push-server-api/src/main/proto/plugin_gateway.proto index a97e544..644d9a5 100644 --- a/push-server-api/src/main/proto/plugin_gateway.proto +++ b/push-server-api/src/main/proto/plugin_gateway.proto @@ -92,6 +92,7 @@ enum UserActionType { USER_ACTION_TYPE_UNSPECIFIED = 0; USER_ACTION_TYPE_TEXT = 1; USER_ACTION_TYPE_CLICK = 2; + USER_ACTION_TYPE_IMAGE = 3; } message UserActionEvent { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java index d012219..e2884cf 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/wecom/WecomCallbackController.java @@ -109,6 +109,9 @@ private void dispatchAsync(Long appId, WecomMessagePayload payload) { content = StringUtils.hasText(payload.getEventKey()) ? payload.getEventKey() : payload.getEvent(); + } else if ("image".equalsIgnoreCase(payload.getReceiveMsgType())) { + type = "IMAGE"; + content = payload.getPicUrl(); } if (!StringUtils.hasText(content) && StringUtils.hasText(payload.getPicUrl())) { diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java index b79b8d9..e9bf903 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/plugin/GrpcPluginProxy.java @@ -45,6 +45,8 @@ public void handle(ActionContext context) { if ("CLICK".equalsIgnoreCase(context.getType())) { eventBuilder.setType(UserActionType.USER_ACTION_TYPE_CLICK); + } else if ("IMAGE".equalsIgnoreCase(context.getType())) { + eventBuilder.setType(UserActionType.USER_ACTION_TYPE_IMAGE); } else { eventBuilder.setType(UserActionType.USER_ACTION_TYPE_TEXT); } diff --git a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json index 42ea9e1..5d2a3d7 100644 --- a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -1464,6 +1464,12 @@ { "name": "list", "parameterTypes": [] + }, + { + "name": "switchStatus", + "parameterTypes": [ + "java.util.Map" + ] } ] }, @@ -1496,6 +1502,12 @@ "dev.qingzhou.pushserver.service.PortalProxyConfigService" ] }, + { + "name": "delete", + "parameterTypes": [ + "jakarta.servlet.http.HttpSession" + ] + }, { "name": "getProxy", "parameterTypes": [ @@ -2726,6 +2738,12 @@ "java.lang.String" ] }, + { + "name": "setErrorMessage", + "parameterTypes": [ + "java.lang.String" + ] + }, { "name": "setId", "parameterTypes": [ @@ -3986,6 +4004,13 @@ { "name": "listPlugins", "parameterTypes": [] + }, + { + "name": "switchStatus", + "parameterTypes": [ + "java.lang.Integer", + "java.lang.Integer" + ] } ] }, @@ -4009,6 +4034,12 @@ "dev.qingzhou.pushserver.manager.wecom.WecomApiClient" ] }, + { + "name": "deleteByUserId", + "parameterTypes": [ + "java.lang.Long" + ] + }, { "name": "getByUserId", "parameterTypes": [ @@ -4541,6 +4572,17 @@ { "type": "java.io.FileDescriptor" }, + { + "type": "java.io.FileNotFoundException", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, { "type": "java.io.Serializable" }, @@ -12421,6 +12463,9 @@ { "glob": "static/assets/DashboardView-BQ-3UbUb.js" }, + { + "glob": "static/assets/DashboardView-D6Hck1UZ.js" + }, { "glob": "static/assets/DashboardView-DHdOv7sv.css" }, @@ -12430,9 +12475,15 @@ { "glob": "static/assets/InitView-Dn7DLcnb.js" }, + { + "glob": "static/assets/PluginsView-CNi8y0aY.css" + }, { "glob": "static/assets/PluginsView-CeF5nKD9.css" }, + { + "glob": "static/assets/PluginsView-CssPFoQP.js" + }, { "glob": "static/assets/PluginsView-DhuTfWVL.js" }, @@ -12442,6 +12493,9 @@ { "glob": "static/assets/ProxyView-DRIg8mIw.js" }, + { + "glob": "static/assets/index-BHUmIbXc.js" + }, { "glob": "static/assets/index-BQHYf_DI.js" }, From bd11f93793be9ae99ae1a50055fb1a26686d8c27 Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 11 Feb 2026 17:08:56 +0800 Subject: [PATCH 12/15] =?UTF-8?q?feat(controller):=20=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?Turnstile=20=E9=85=8D=E7=BD=AE=E8=8E=B7=E5=8F=96=E4=B8=8E?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/PortalSystemController.java | 32 +++++++++++++++++++ .../dto/portal/TurnstileConfigRequest.java | 10 ++++++ 2 files changed, 42 insertions(+) create mode 100644 push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/TurnstileConfigRequest.java diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java index 07daa58..f16cf51 100644 --- a/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/controller/PortalSystemController.java @@ -1,6 +1,7 @@ package dev.qingzhou.pushserver.controller; import dev.qingzhou.pushserver.common.PortalResponse; +import dev.qingzhou.pushserver.model.dto.portal.TurnstileConfigRequest; import dev.qingzhou.pushserver.service.SystemConfigService; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; @@ -38,4 +39,35 @@ public PortalResponse setIgnoreVersion(@RequestBody Map bo } return PortalResponse.ok(null); } + + @GetMapping("/turnstile") + public PortalResponse getTurnstileConfig() { + TurnstileConfigRequest config = new TurnstileConfigRequest(); + config.setEnabled(systemConfigService.isTurnstileEnabled()); + config.setSiteKey(systemConfigService.getTurnstileSiteKey()); + + String secretKey = systemConfigService.getTurnstileSecretKey(); + if (secretKey != null && !secretKey.isBlank()) { + config.setSecretKey("******"); + } else { + config.setSecretKey(""); + } + + return PortalResponse.ok(config); + } + + @PutMapping("/turnstile") + public PortalResponse updateTurnstileConfig(@RequestBody TurnstileConfigRequest request) { + String secretKey = request.getSecretKey(); + if ("******".equals(secretKey)) { + secretKey = systemConfigService.getTurnstileSecretKey(); + } + + systemConfigService.setTurnstileConfig( + request.isEnabled(), + request.getSiteKey(), + secretKey + ); + return PortalResponse.ok(null); + } } diff --git a/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/TurnstileConfigRequest.java b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/TurnstileConfigRequest.java new file mode 100644 index 0000000..165bcba --- /dev/null +++ b/push-server-core/src/main/java/dev/qingzhou/pushserver/model/dto/portal/TurnstileConfigRequest.java @@ -0,0 +1,10 @@ +package dev.qingzhou.pushserver.model.dto.portal; + +import lombok.Data; + +@Data +public class TurnstileConfigRequest { + private boolean enabled; + private String siteKey; + private String secretKey; +} From 971ce8b5322418cb2c1c38094fc6d91462fceade Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 11 Feb 2026 17:09:01 +0800 Subject: [PATCH 13/15] =?UTF-8?q?feat(native-image):=20=E6=89=A9=E5=B1=95?= =?UTF-8?q?=E5=8F=8D=E5=B0=84=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81=20Turnst?= =?UTF-8?q?ile=20=E9=85=8D=E7=BD=AE=E5=8F=8A=E9=9D=99=E6=80=81=E8=B5=84?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../native-image/reachability-metadata.json | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json index 5d2a3d7..b00cc61 100644 --- a/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json +++ b/push-server-core/src/main/resources/META-INF/native-image/reachability-metadata.json @@ -1541,9 +1541,19 @@ "name": "getIgnoreVersion", "parameterTypes": [] }, + { + "name": "getTurnstileConfig", + "parameterTypes": [] + }, { "name": "getVersion", "parameterTypes": [] + }, + { + "name": "updateTurnstileConfig", + "parameterTypes": [ + "dev.qingzhou.pushserver.model.dto.portal.TurnstileConfigRequest" + ] } ] }, @@ -2416,6 +2426,45 @@ } ] }, + { + "type": "dev.qingzhou.pushserver.model.dto.portal.TurnstileConfigRequest", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "getSecretKey", + "parameterTypes": [] + }, + { + "name": "getSiteKey", + "parameterTypes": [] + }, + { + "name": "isEnabled", + "parameterTypes": [] + }, + { + "name": "setEnabled", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "setSecretKey", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "setSiteKey", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, { "type": "dev.qingzhou.pushserver.model.entity.portal.PortalAppApiKey", "methods": [ @@ -12460,9 +12509,18 @@ { "glob": "sqlite-jdbc.properties" }, + { + "glob": "static/.well-known/appspecific/com.chrome.devtools.json" + }, + { + "glob": "static/assets/DashboardView-B2MNrj-2.js" + }, { "glob": "static/assets/DashboardView-BQ-3UbUb.js" }, + { + "glob": "static/assets/DashboardView-CDau1uW5.js" + }, { "glob": "static/assets/DashboardView-D6Hck1UZ.js" }, @@ -12499,9 +12557,18 @@ { "glob": "static/assets/index-BQHYf_DI.js" }, + { + "glob": "static/assets/index-ChEtmHeK.js" + }, + { + "glob": "static/assets/index-DfoQW2_6.js" + }, { "glob": "static/assets/index-FpXCWsKn.css" }, + { + "glob": "static/assets/index-OuPPjUqQ.css" + }, { "glob": "static/favicon.png" }, @@ -12511,6 +12578,12 @@ { "glob": "static/logo.png" }, + { + "glob": "static/system/content.css.map" + }, + { + "glob": "static/system/sidebar.css.map" + }, { "module": "java.base", "glob": "jdk/internal/icu/impl/data/icudt76b/nfkc.nrm" From 30548bb6786444a3c19ad0601b8b1232a7b46cb5 Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 11 Feb 2026 17:09:08 +0800 Subject: [PATCH 14/15] =?UTF-8?q?feat(native-image):=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=9B=B4=E5=A4=9A=E6=9E=B6=E6=9E=84=E4=B8=8E=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E8=A7=A6=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/native-image.yml | 113 ++++++++--------------------- 1 file changed, 32 insertions(+), 81 deletions(-) diff --git a/.github/workflows/native-image.yml b/.github/workflows/native-image.yml index 55d5e2b..dfb8b9d 100644 --- a/.github/workflows/native-image.yml +++ b/.github/workflows/native-image.yml @@ -3,6 +3,7 @@ name: native-image on: release: types: [published] + workflow_dispatch: permissions: contents: write @@ -17,23 +18,31 @@ jobs: matrix: include: - os: ubuntu-latest - artifact: linux + artifact: linux-amd64 + arch: amd64 + ext: "" + - os: ubuntu-24.04-arm + artifact: linux-arm64 + arch: arm64 ext: "" - os: macos-latest artifact: macos + arch: macos ext: "" - os: windows-latest artifact: windows + arch: windows ext: ".exe" steps: - name: Checkout uses: actions/checkout@v4 + - name: Set up Liberica NIK uses: graalvm/setup-graalvm@v1 with: java-version: '25' - distribution: 'liberica' # 这里指定使用 Liberica 发行版 + distribution: 'liberica' github-token: ${{ secrets.GITHUB_TOKEN }} cache: 'maven' @@ -68,10 +77,6 @@ jobs: mkdir -p push-server-core/src/main/resources/static cp -r push-server-web/dist/* push-server-core/src/main/resources/static/ - - name: Run tests - shell: bash - run: ${{ env.MVN_CMD }} -B test - - name: Build native image shell: bash run: ${{ env.MVN_CMD }} -B -pl push-server-core -am -Pnative -DskipTests native:compile @@ -79,89 +84,35 @@ jobs: - name: Rename binary shell: bash run: | - mkdir -p target + mkdir -p target-bin src="push-server-core/target/push-server-core${{ matrix.ext }}" - dest="target/push-server-${{ matrix.artifact }}${{ matrix.ext }}" + dest="target-bin/push-server-${{ matrix.artifact }}${{ matrix.ext }}" if [ -f "$src" ]; then mv "$src" "$dest" - fi - - - name: Upload release asset - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - files: target/push-server-${{ matrix.artifact }}${{ matrix.ext }} - - docker-build: - name: docker build (${{ matrix.arch }}) - runs-on: ${{ matrix.os }} - needs: build - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - arch: amd64 - - os: ubuntu-24.04-arm - arch: arm64 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Liberica NIK - uses: graalvm/setup-graalvm@v1 - with: - java-version: '25' - distribution: 'liberica' - github-token: ${{ secrets.GITHUB_TOKEN }} - cache: 'maven' - - - name: Select Maven command - shell: bash - run: | - if [ -f ./mvnw ]; then - chmod +x ./mvnw - echo "MVN_CMD=./mvnw" >> "$GITHUB_ENV" else - echo "MVN_CMD=mvn" >> "$GITHUB_ENV" + echo "Error: Binary not found at $src" + exit 1 fi - - name: Checkout Frontend - uses: actions/checkout@v4 - with: - repository: qingzhou-dev/push-server-web - path: push-server-web - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Build Frontend - shell: bash - run: | - cd push-server-web - npm install - npm run build - cd .. - mkdir -p push-server-core/src/main/resources/static - cp -r push-server-web/dist/* push-server-core/src/main/resources/static/ - - - name: Build native image - shell: bash - run: ${{ env.MVN_CMD }} -B -pl push-server-core -am -Pnative -DskipTests native:compile - - - name: Upload binary + - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: push-server-${{ matrix.arch }} - path: push-server-core/target/push-server-core + name: push-server-${{ matrix.artifact }} + path: target-bin/push-server-${{ matrix.artifact }}${{ matrix.ext }} if-no-files-found: error + - name: Upload release asset + if: github.event_name == 'release' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + files: target-bin/push-server-${{ matrix.artifact }}${{ matrix.ext }} + docker-push: name: docker push runs-on: ubuntu-latest - needs: docker-build + needs: build + if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' steps: - name: Checkout uses: actions/checkout@v4 @@ -169,20 +120,20 @@ jobs: - name: Download amd64 binary uses: actions/download-artifact@v4 with: - name: push-server-amd64 + name: push-server-linux-amd64 path: bin-amd64 - name: Download arm64 binary uses: actions/download-artifact@v4 with: - name: push-server-arm64 + name: push-server-linux-arm64 path: bin-arm64 - name: Prepare binaries run: | mkdir -p target - cp bin-amd64/push-server-core target/push-server-amd64 - cp bin-arm64/push-server-core target/push-server-arm64 + cp bin-amd64/push-server-linux-amd64 target/push-server-amd64 + cp bin-arm64/push-server-linux-arm64 target/push-server-arm64 ls -R target - name: Set up Docker Buildx @@ -220,4 +171,4 @@ jobs: push: true platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} From b63ffbb1e1fe1f030cdc525d74c53aca4b9d0743 Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 11 Feb 2026 17:09:35 +0800 Subject: [PATCH 15/15] =?UTF-8?q?chore(pom):=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E8=87=B3=200.1.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f059f4c..a49cee4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ dev.qingzhou push-server - 0.1.3 + 0.1.4 pom push-server push-server