#
tokens: 49744/50000 36/349 files (page 3/7)
lines: off (toggle) GitHub
raw markdown copy
This is page 3 of 7. Use http://codebase.md/higress-group/himarket?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .cursor
│   └── rules
│       ├── api-style.mdc
│       └── project-architecture.mdc
├── .gitignore
├── build.sh
├── deploy
│   ├── docker
│   │   ├── docker-compose.yml
│   │   └── Docker部署说明.md
│   └── helm
│       ├── Chart.yaml
│       ├── Helm部署说明.md
│       ├── templates
│       │   ├── _helpers.tpl
│       │   ├── himarket-admin-cm.yaml
│       │   ├── himarket-admin-deployment.yaml
│       │   ├── himarket-admin-service.yaml
│       │   ├── himarket-frontend-cm.yaml
│       │   ├── himarket-frontend-deployment.yaml
│       │   ├── himarket-frontend-service.yaml
│       │   ├── himarket-server-cm.yaml
│       │   ├── himarket-server-deployment.yaml
│       │   ├── himarket-server-service.yaml
│       │   ├── mysql.yaml
│       │   └── serviceaccount.yaml
│       └── values.yaml
├── LICENSE
├── NOTICE
├── pom.xml
├── portal-bootstrap
│   ├── Dockerfile
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   │   └── com
│       │   │       └── alibaba
│       │   │           └── apiopenplatform
│       │   │               ├── config
│       │   │               │   ├── AsyncConfig.java
│       │   │               │   ├── FilterConfig.java
│       │   │               │   ├── PageConfig.java
│       │   │               │   ├── RestTemplateConfig.java
│       │   │               │   ├── SecurityConfig.java
│       │   │               │   └── SwaggerConfig.java
│       │   │               ├── filter
│       │   │               │   └── PortalResolvingFilter.java
│       │   │               └── PortalApplication.java
│       │   └── resources
│       │       └── application.yaml
│       └── test
│           └── java
│               └── com
│                   └── alibaba
│                       └── apiopenplatform
│                           └── integration
│                               └── AdministratorAuthIntegrationTest.java
├── portal-dal
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── alibaba
│                       └── apiopenplatform
│                           ├── converter
│                           │   ├── AdpAIGatewayConfigConverter.java
│                           │   ├── APIGConfigConverter.java
│                           │   ├── APIGRefConfigConverter.java
│                           │   ├── ApiKeyConfigConverter.java
│                           │   ├── ConsumerAuthConfigConverter.java
│                           │   ├── GatewayConfigConverter.java
│                           │   ├── HigressConfigConverter.java
│                           │   ├── HigressRefConfigConverter.java
│                           │   ├── HmacConfigConverter.java
│                           │   ├── JsonConverter.java
│                           │   ├── JwtConfigConverter.java
│                           │   ├── NacosRefConfigConverter.java
│                           │   ├── PortalSettingConfigConverter.java
│                           │   ├── PortalUiConfigConverter.java
│                           │   └── ProductIconConverter.java
│                           ├── entity
│                           │   ├── Administrator.java
│                           │   ├── BaseEntity.java
│                           │   ├── Consumer.java
│                           │   ├── ConsumerCredential.java
│                           │   ├── ConsumerRef.java
│                           │   ├── Developer.java
│                           │   ├── DeveloperExternalIdentity.java
│                           │   ├── Gateway.java
│                           │   ├── NacosInstance.java
│                           │   ├── Portal.java
│                           │   ├── PortalDomain.java
│                           │   ├── Product.java
│                           │   ├── ProductPublication.java
│                           │   ├── ProductRef.java
│                           │   └── ProductSubscription.java
│                           ├── repository
│                           │   ├── AdministratorRepository.java
│                           │   ├── BaseRepository.java
│                           │   ├── ConsumerCredentialRepository.java
│                           │   ├── ConsumerRefRepository.java
│                           │   ├── ConsumerRepository.java
│                           │   ├── DeveloperExternalIdentityRepository.java
│                           │   ├── DeveloperRepository.java
│                           │   ├── GatewayRepository.java
│                           │   ├── NacosInstanceRepository.java
│                           │   ├── PortalDomainRepository.java
│                           │   ├── PortalRepository.java
│                           │   ├── ProductPublicationRepository.java
│                           │   ├── ProductRefRepository.java
│                           │   ├── ProductRepository.java
│                           │   └── SubscriptionRepository.java
│                           └── support
│                               ├── common
│                               │   ├── Encrypted.java
│                               │   ├── Encryptor.java
│                               │   └── User.java
│                               ├── consumer
│                               │   ├── AdpAIAuthConfig.java
│                               │   ├── APIGAuthConfig.java
│                               │   ├── ApiKeyConfig.java
│                               │   ├── ConsumerAuthConfig.java
│                               │   ├── HigressAuthConfig.java
│                               │   ├── HmacConfig.java
│                               │   └── JwtConfig.java
│                               ├── enums
│                               │   ├── APIGAPIType.java
│                               │   ├── ConsumerAuthType.java
│                               │   ├── ConsumerStatus.java
│                               │   ├── CredentialMode.java
│                               │   ├── DeveloperAuthType.java
│                               │   ├── DeveloperStatus.java
│                               │   ├── DomainType.java
│                               │   ├── GatewayType.java
│                               │   ├── GrantType.java
│                               │   ├── HigressAPIType.java
│                               │   ├── JwtAlgorithm.java
│                               │   ├── ProductIconType.java
│                               │   ├── ProductStatus.java
│                               │   ├── ProductType.java
│                               │   ├── ProtocolType.java
│                               │   ├── PublicKeyFormat.java
│                               │   ├── SourceType.java
│                               │   ├── SubscriptionStatus.java
│                               │   └── UserType.java
│                               ├── gateway
│                               │   ├── AdpAIGatewayConfig.java
│                               │   ├── APIGConfig.java
│                               │   ├── GatewayConfig.java
│                               │   └── HigressConfig.java
│                               ├── portal
│                               │   ├── AuthCodeConfig.java
│                               │   ├── IdentityMapping.java
│                               │   ├── JwtBearerConfig.java
│                               │   ├── OAuth2Config.java
│                               │   ├── OidcConfig.java
│                               │   ├── PortalSettingConfig.java
│                               │   ├── PortalUiConfig.java
│                               │   └── PublicKeyConfig.java
│                               └── product
│                                   ├── APIGRefConfig.java
│                                   ├── HigressRefConfig.java
│                                   ├── NacosRefConfig.java
│                                   └── ProductIcon.java
├── portal-server
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── com
│                   └── alibaba
│                       └── apiopenplatform
│                           ├── controller
│                           │   ├── AdministratorController.java
│                           │   ├── ConsumerController.java
│                           │   ├── DeveloperController.java
│                           │   ├── GatewayController.java
│                           │   ├── NacosController.java
│                           │   ├── OAuth2Controller.java
│                           │   ├── OidcController.java
│                           │   ├── PortalController.java
│                           │   └── ProductController.java
│                           ├── core
│                           │   ├── advice
│                           │   │   ├── ExceptionAdvice.java
│                           │   │   └── ResponseAdvice.java
│                           │   ├── annotation
│                           │   │   ├── AdminAuth.java
│                           │   │   ├── AdminOrDeveloperAuth.java
│                           │   │   └── DeveloperAuth.java
│                           │   ├── constant
│                           │   │   ├── CommonConstants.java
│                           │   │   ├── IdpConstants.java
│                           │   │   ├── JwtConstants.java
│                           │   │   └── Resources.java
│                           │   ├── event
│                           │   │   ├── DeveloperDeletingEvent.java
│                           │   │   ├── PortalDeletingEvent.java
│                           │   │   └── ProductDeletingEvent.java
│                           │   ├── exception
│                           │   │   ├── BusinessException.java
│                           │   │   └── ErrorCode.java
│                           │   ├── response
│                           │   │   └── Response.java
│                           │   ├── security
│                           │   │   ├── ContextHolder.java
│                           │   │   ├── DeveloperAuthenticationProvider.java
│                           │   │   └── JwtAuthenticationFilter.java
│                           │   └── utils
│                           │       ├── IdGenerator.java
│                           │       ├── PasswordHasher.java
│                           │       └── TokenUtil.java
│                           ├── dto
│                           │   ├── converter
│                           │   │   ├── InputConverter.java
│                           │   │   ├── NacosToGatewayToolsConverter.java
│                           │   │   └── OutputConverter.java
│                           │   ├── params
│                           │   │   ├── admin
│                           │   │   │   ├── AdminCreateParam.java
│                           │   │   │   ├── AdminLoginParam.java
│                           │   │   │   └── ResetPasswordParam.java
│                           │   │   ├── consumer
│                           │   │   │   ├── CreateConsumerParam.java
│                           │   │   │   ├── CreateCredentialParam.java
│                           │   │   │   ├── CreateSubscriptionParam.java
│                           │   │   │   ├── QueryConsumerParam.java
│                           │   │   │   ├── QuerySubscriptionParam.java
│                           │   │   │   └── UpdateCredentialParam.java
│                           │   │   ├── developer
│                           │   │   │   ├── CreateDeveloperParam.java
│                           │   │   │   ├── CreateExternalDeveloperParam.java
│                           │   │   │   ├── DeveloperLoginParam.java
│                           │   │   │   ├── QueryDeveloperParam.java
│                           │   │   │   ├── UnbindExternalIdentityParam.java
│                           │   │   │   ├── UpdateDeveloperParam.java
│                           │   │   │   └── UpdateDeveloperStatusParam.java
│                           │   │   ├── gateway
│                           │   │   │   ├── ImportGatewayParam.java
│                           │   │   │   ├── QueryAdpAIGatewayParam.java
│                           │   │   │   ├── QueryAPIGParam.java
│                           │   │   │   └── QueryGatewayParam.java
│                           │   │   ├── nacos
│                           │   │   │   ├── CreateNacosParam.java
│                           │   │   │   ├── QueryNacosNamespaceParam.java
│                           │   │   │   ├── QueryNacosParam.java
│                           │   │   │   └── UpdateNacosParam.java
│                           │   │   ├── portal
│                           │   │   │   ├── BindDomainParam.java
│                           │   │   │   ├── CreatePortalParam.java
│                           │   │   │   └── UpdatePortalParam.java
│                           │   │   └── product
│                           │   │       ├── CreateProductParam.java
│                           │   │       ├── CreateProductRefParam.java
│                           │   │       ├── PublishProductParam.java
│                           │   │       ├── QueryProductParam.java
│                           │   │       ├── QueryProductSubscriptionParam.java
│                           │   │       ├── UnPublishProductParam.java
│                           │   │       └── UpdateProductParam.java
│                           │   └── result
│                           │       ├── AdminResult.java
│                           │       ├── AdpGatewayInstanceResult.java
│                           │       ├── AdpMcpServerListResult.java
│                           │       ├── AdpMCPServerResult.java
│                           │       ├── APIConfigResult.java
│                           │       ├── APIGMCPServerResult.java
│                           │       ├── APIResult.java
│                           │       ├── AuthResult.java
│                           │       ├── ConsumerCredentialResult.java
│                           │       ├── ConsumerResult.java
│                           │       ├── DeveloperResult.java
│                           │       ├── GatewayMCPServerResult.java
│                           │       ├── GatewayResult.java
│                           │       ├── HigressMCPServerResult.java
│                           │       ├── IdpResult.java
│                           │       ├── IdpState.java
│                           │       ├── IdpTokenResult.java
│                           │       ├── MCPConfigResult.java
│                           │       ├── MCPServerResult.java
│                           │       ├── MseNacosResult.java
│                           │       ├── NacosMCPServerResult.java
│                           │       ├── NacosNamespaceResult.java
│                           │       ├── NacosResult.java
│                           │       ├── PageResult.java
│                           │       ├── PortalResult.java
│                           │       ├── ProductPublicationResult.java
│                           │       ├── ProductRefResult.java
│                           │       ├── ProductResult.java
│                           │       └── SubscriptionResult.java
│                           └── service
│                               ├── AdministratorService.java
│                               ├── AdpAIGatewayService.java
│                               ├── ConsumerService.java
│                               ├── DeveloperService.java
│                               ├── gateway
│                               │   ├── AdpAIGatewayOperator.java
│                               │   ├── AIGatewayOperator.java
│                               │   ├── APIGOperator.java
│                               │   ├── client
│                               │   │   ├── AdpAIGatewayClient.java
│                               │   │   ├── APIGClient.java
│                               │   │   ├── GatewayClient.java
│                               │   │   ├── HigressClient.java
│                               │   │   ├── PopGatewayClient.java
│                               │   │   └── SLSClient.java
│                               │   ├── factory
│                               │   │   └── HTTPClientFactory.java
│                               │   ├── GatewayOperator.java
│                               │   └── HigressOperator.java
│                               ├── GatewayService.java
│                               ├── IdpService.java
│                               ├── impl
│                               │   ├── AdministratorServiceImpl.java
│                               │   ├── ConsumerServiceImpl.java
│                               │   ├── DeveloperServiceImpl.java
│                               │   ├── GatewayServiceImpl.java
│                               │   ├── IdpServiceImpl.java
│                               │   ├── NacosServiceImpl.java
│                               │   ├── OAuth2ServiceImpl.java
│                               │   ├── OidcServiceImpl.java
│                               │   ├── PortalServiceImpl.java
│                               │   └── ProductServiceImpl.java
│                               ├── NacosService.java
│                               ├── OAuth2Service.java
│                               ├── OidcService.java
│                               ├── PortalService.java
│                               └── ProductService.java
├── portal-web
│   ├── api-portal-admin
│   │   ├── .env
│   │   ├── .gitignore
│   │   ├── bin
│   │   │   ├── replace_var.py
│   │   │   └── start.sh
│   │   ├── Dockerfile
│   │   ├── eslint.config.js
│   │   ├── index.html
│   │   ├── nginx.conf
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── proxy.conf
│   │   ├── public
│   │   │   ├── logo.png
│   │   │   └── vite.svg
│   │   ├── README.md
│   │   ├── src
│   │   │   ├── aliyunThemeToken.ts
│   │   │   ├── App.css
│   │   │   ├── App.tsx
│   │   │   ├── assets
│   │   │   │   └── react.svg
│   │   │   ├── components
│   │   │   │   ├── api-product
│   │   │   │   │   ├── ApiProductApiDocs.tsx
│   │   │   │   │   ├── ApiProductDashboard.tsx
│   │   │   │   │   ├── ApiProductFormModal.tsx
│   │   │   │   │   ├── ApiProductLinkApi.tsx
│   │   │   │   │   ├── ApiProductOverview.tsx
│   │   │   │   │   ├── ApiProductPolicy.tsx
│   │   │   │   │   ├── ApiProductPortal.tsx
│   │   │   │   │   ├── ApiProductUsageGuide.tsx
│   │   │   │   │   ├── SwaggerUIWrapper.css
│   │   │   │   │   └── SwaggerUIWrapper.tsx
│   │   │   │   ├── common
│   │   │   │   │   ├── AdvancedSearch.tsx
│   │   │   │   │   └── index.ts
│   │   │   │   ├── console
│   │   │   │   │   ├── GatewayTypeSelector.tsx
│   │   │   │   │   ├── ImportGatewayModal.tsx
│   │   │   │   │   ├── ImportHigressModal.tsx
│   │   │   │   │   ├── ImportMseNacosModal.tsx
│   │   │   │   │   └── NacosTypeSelector.tsx
│   │   │   │   ├── icons
│   │   │   │   │   └── McpServerIcon.tsx
│   │   │   │   ├── Layout.tsx
│   │   │   │   ├── LayoutWrapper.tsx
│   │   │   │   ├── portal
│   │   │   │   │   ├── PortalConsumers.tsx
│   │   │   │   │   ├── PortalDashboard.tsx
│   │   │   │   │   ├── PortalDevelopers.tsx
│   │   │   │   │   ├── PortalDomain.tsx
│   │   │   │   │   ├── PortalFormModal.tsx
│   │   │   │   │   ├── PortalOverview.tsx
│   │   │   │   │   ├── PortalPublishedApis.tsx
│   │   │   │   │   ├── PortalSecurity.tsx
│   │   │   │   │   ├── PortalSettings.tsx
│   │   │   │   │   ├── PublicKeyManager.tsx
│   │   │   │   │   └── ThirdPartyAuthManager.tsx
│   │   │   │   └── subscription
│   │   │   │       └── SubscriptionListModal.tsx
│   │   │   ├── contexts
│   │   │   │   └── LoadingContext.tsx
│   │   │   ├── index.css
│   │   │   ├── lib
│   │   │   │   ├── api.ts
│   │   │   │   ├── constant.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages
│   │   │   │   ├── ApiProductDetail.tsx
│   │   │   │   ├── ApiProducts.tsx
│   │   │   │   ├── Dashboard.tsx
│   │   │   │   ├── GatewayConsoles.tsx
│   │   │   │   ├── Login.tsx
│   │   │   │   ├── NacosConsoles.tsx
│   │   │   │   ├── PortalDetail.tsx
│   │   │   │   ├── Portals.tsx
│   │   │   │   └── Register.tsx
│   │   │   ├── routes
│   │   │   │   └── index.tsx
│   │   │   ├── types
│   │   │   │   ├── api-product.ts
│   │   │   │   ├── consumer.ts
│   │   │   │   ├── gateway.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── portal.ts
│   │   │   │   ├── shims-js-yaml.d.ts
│   │   │   │   └── subscription.ts
│   │   │   └── vite-env.d.ts
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   └── api-portal-frontend
│       ├── .env
│       ├── .gitignore
│       ├── .husky
│       │   └── pre-commit
│       ├── bin
│       │   ├── replace_var.py
│       │   └── start.sh
│       ├── Dockerfile
│       ├── eslint.config.js
│       ├── index.html
│       ├── nginx.conf
│       ├── package.json
│       ├── postcss.config.js
│       ├── proxy.conf
│       ├── public
│       │   ├── favicon.ico
│       │   ├── logo.png
│       │   ├── logo.svg
│       │   ├── MCP.png
│       │   ├── MCP.svg
│       │   └── vite.svg
│       ├── README.md
│       ├── src
│       │   ├── aliyunThemeToken.ts
│       │   ├── App.css
│       │   ├── App.tsx
│       │   ├── assets
│       │   │   ├── aliyun.png
│       │   │   ├── github.png
│       │   │   ├── google.png
│       │   │   └── react.svg
│       │   ├── components
│       │   │   ├── consumer
│       │   │   │   ├── ConsumerBasicInfo.tsx
│       │   │   │   ├── CredentialManager.tsx
│       │   │   │   ├── index.ts
│       │   │   │   └── SubscriptionManager.tsx
│       │   │   ├── Layout.tsx
│       │   │   ├── Navigation.tsx
│       │   │   ├── ProductHeader.tsx
│       │   │   ├── SwaggerUIWrapper.css
│       │   │   ├── SwaggerUIWrapper.tsx
│       │   │   └── UserInfo.tsx
│       │   ├── index.css
│       │   ├── lib
│       │   │   ├── api.ts
│       │   │   ├── statusUtils.ts
│       │   │   └── utils.ts
│       │   ├── main.tsx
│       │   ├── pages
│       │   │   ├── ApiDetail.tsx
│       │   │   ├── Apis.tsx
│       │   │   ├── Callback.tsx
│       │   │   ├── ConsumerDetail.tsx
│       │   │   ├── Consumers.tsx
│       │   │   ├── GettingStarted.tsx
│       │   │   ├── Home.tsx
│       │   │   ├── Login.tsx
│       │   │   ├── Mcp.tsx
│       │   │   ├── McpDetail.tsx
│       │   │   ├── OidcCallback.tsx
│       │   │   ├── Profile.tsx
│       │   │   ├── Register.tsx
│       │   │   └── Test.css
│       │   ├── router.tsx
│       │   ├── types
│       │   │   ├── consumer.ts
│       │   │   └── index.ts
│       │   └── vite-env.d.ts
│       ├── tailwind.config.js
│       ├── tsconfig.app.json
│       ├── tsconfig.json
│       ├── tsconfig.node.json
│       └── vite.config.ts
└── README.md
```

# Files

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/ProductService.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service;

import com.alibaba.apiopenplatform.core.event.PortalDeletingEvent;
import com.alibaba.apiopenplatform.dto.params.product.*;
import com.alibaba.apiopenplatform.dto.result.*;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Map;

public interface ProductService {

    /**
     * 创建API产品
     *
     * @param param
     * @return
     */
    ProductResult createProduct(CreateProductParam param);

    /**
     * 查询API产品
     *
     * @param productId
     * @return
     */
    ProductResult getProduct(String productId);

    /**
     * 查询API产品列表
     *
     * @param param
     * @param pageable
     * @return
     */
    PageResult<ProductResult> listProducts(QueryProductParam param, Pageable pageable);

    /**
     * 更新门户
     *
     * @param productId
     * @param param
     * @return
     */
    ProductResult updateProduct(String productId, UpdateProductParam param);

    /**
     * 发布API产品
     *
     * @param productId
     * @param portalId
     * @return
     */
    void publishProduct(String productId, String portalId);

    /**
     * 获取API产品的发布信息
     *
     * @param productId
     * @param pageable
     * @return
     */
    PageResult<ProductPublicationResult> getPublications(String productId, Pageable pageable);

    /**
     * 下线产品
     *
     * @param productId
     * @param portalId
     * @return
     */
    void unpublishProduct(String productId, String portalId);

    /**
     * 删除产品
     *
     * @param productId
     */
    void deleteProduct(String productId);

    /**
     * API产品引用API或MCP Server
     *
     * @param productId
     * @param param
     */
    void addProductRef(String productId, CreateProductRefParam param);

    /**
     * 查询API产品引用的资源
     *
     * @param productId
     * @return
     */
    ProductRefResult getProductRef(String productId);

    /**
     * 删除API产品的引用
     *
     * @param productId
     */
    void deleteProductRef(String productId);

    /**
     * 清理门户资源
     *
     * @param event
     */
    void handlePortalDeletion(PortalDeletingEvent event);

    Map<String, ProductResult> getProducts(List<String> productIds);

    /**
     * 获取API产品的Dashboard监控面板URL
     *
     * @param productId
     * @return Dashboard URL
     */
    String getProductDashboard(String productId);

    /**
     * 获取API产品的订阅信息
     *
     * @param productId
     * @param param
     * @param pageable
     * @return
     */
    PageResult<SubscriptionResult> listProductSubscriptions(String productId, QueryProductSubscriptionParam param, Pageable pageable);

    /**
     * 检查API产品是否存在
     *
     * @param productId
     * @return
     */
    void existsProduct(String productId);
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/pages/Test.css:
--------------------------------------------------------------------------------

```css
.test-container {
  padding: 2rem;
  max-width: 800px;
  margin: 0 auto;
  font-family: 'Arial', sans-serif;
}

.test-container h1 {
  text-align: center;
  color: #333;
  margin-bottom: 2rem;
}

.demo-section {
  margin-bottom: 3rem;
  padding: 1.5rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  background: #fafafa;
}

.demo-section h2 {
  color: #555;
  margin-bottom: 1rem;
  font-size: 1.2rem;
}

.text-flow-container {
  text-align: center;
  padding: 2rem 0;
}

.text-flow-container.large {
  padding: 3rem 0;
}

.text-flow {
  display: inline-block;
  margin: 0 1rem;
  /* font-weight: bold; */
  position: relative;
}

/* 基础流光效果 - 红色到白色 */
.text-flow-primary {
  background-image: linear-gradient(
    to right,
    #ff0000,
    #ffffff 12.5%,
    #ff0000 25%,
    #ffffff 37.5%,
    #ff0000 50%,
    #ff0000 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 400% 100%;
  animation: light 2s infinite linear;
}

/* 次要流光效果 - 黑色到白色 */
.text-flow-secondary {
  background-image: linear-gradient(
    to right,
    #000000,
    #000000 50%,
    #ffffff 62.5%,
    #000000 75%,
    #ffffff 87.5%,
    #000000 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 800% 100%;
  animation: light 2s infinite linear;
}

.text-flow-grey {
  background-image: linear-gradient(
    to right,
    #4b5563,
    #ffffff 12.5%,
    #4b5563 25%,
    #ffffff 37.5%,
    #4b5563 50%,
    #4b5563 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 400% 100%;
  animation: light 2s infinite linear;
}


/* 蓝色流光效果 */
.text-flow-blue {
  background-image: linear-gradient(
    to right,
    #0066cc,
    #ffffff 12.5%,
    #0066cc 25%,
    #ffffff 37.5%,
    #0066cc 50%,
    #0066cc 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 400% 100%;
  animation: light 2s infinite linear;
}

/* 绿色流光效果 */
.text-flow-green {
  background-image: linear-gradient(
    to right,
    #00cc00,
    #ffffff 12.5%,
    #00cc00 25%,
    #ffffff 37.5%,
    #00cc00 50%,
    #00cc00 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 400% 100%;
  animation: light 2s infinite linear;
}

/* 紫色流光效果 */
.text-flow-purple {
  background-image: linear-gradient(
    to right,
    #6600cc,
    #ffffff 12.5%,
    #6600cc 25%,
    #ffffff 37.5%,
    #6600cc 50%,
    #6600cc 100%
  );
  -webkit-text-fill-color: transparent;
  -webkit-background-clip: text;
  background-clip: text;
  background-size: 400% 100%;
  animation: light 2s infinite linear;
}

/* 大字体流光效果 */
.text-flow-container.large .text-flow {
  font-size: 3.5rem;
  margin: 0 1.5rem;
}

/* 慢速动画 */
.text-flow.slow {
  /* animation: light 4s infinite linear; */
}

/* 快速动画 */
.text-flow.fast {
  animation: light 1s infinite linear;
}

/* 流光动画关键帧 */
@keyframes light {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: -100% 0;
  }
}

/* 兼容性处理 */
@-webkit-keyframes light {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: -100% 0;
  }
}

/* 响应式设计 */
@media (max-width: 768px) {
  .test-container {
    padding: 1rem;
  }
  
  .text-flow {
    font-size: 1.5rem;
    margin: 0 0.5rem;
  }
  
  .text-flow-container.large .text-flow {
    font-size: 2.5rem;
    margin: 0 1rem;
  }
} 
```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/filter/PortalResolvingFilter.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.core.security.ContextHolder;
import com.alibaba.apiopenplatform.service.PortalService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;

@Slf4j
@RequiredArgsConstructor
public class PortalResolvingFilter extends OncePerRequestFilter {

    private final PortalService portalService;

    private final ContextHolder contextHolder;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain chain)
            throws ServletException, IOException {
        try {
            String origin = request.getHeader("Origin");
            String host = request.getHeader("Host");
            String xForwardedHost = request.getHeader("X-Forwarded-Host");
            String xRealIp = request.getHeader("X-Real-IP");
            String xForwardedFor = request.getHeader("X-Forwarded-For");

            String domain = null;
            if (origin != null) {
                try {
                    URI uri = new URI(origin);
                    domain = uri.getHost();
                } catch (Exception ignored) {
                }
            }

            log.info("域名解析调试 - Origin: {}, Host: {}, X-Forwarded-Host: {}, ServerName: {}, X-Real-IP: {}, X-Forwarded-For: {}",
                    origin, host, xForwardedHost, request.getServerName(), xRealIp, xForwardedFor);

            if (domain == null) {
                // 优先使用Host头,如果没有则使用ServerName
                if (host != null && !host.isEmpty()) {
                    domain = host.split(":")[0]; // 去掉端口号
                } else {
                    domain = request.getServerName();
                }
            }
            String portalId = portalService.resolvePortal(domain);

            if (StrUtil.isNotBlank(portalId)) {
                contextHolder.savePortal(portalId);
                log.info("Resolved portal for domain: {} with portalId: {}", domain, portalId);
            } else {
                log.info("No portal found for domain: {}", domain);
                String defaultPortalId = portalService.getDefaultPortal();
                if (StrUtil.isNotBlank(defaultPortalId)) {
                    contextHolder.savePortal(defaultPortalId);
                    log.info("Use default portal: {}", defaultPortalId);
                }
            }

            chain.doFilter(request, response);
        } finally {
            contextHolder.clearPortal();
        }
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/GatewayService.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service;

import com.alibaba.apiopenplatform.dto.params.gateway.ImportGatewayParam;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryAPIGParam;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryGatewayParam;
import com.alibaba.apiopenplatform.dto.result.GatewayMCPServerResult;
import com.alibaba.apiopenplatform.dto.result.*;
import com.alibaba.apiopenplatform.entity.Consumer;
import com.alibaba.apiopenplatform.entity.ConsumerCredential;
import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
import org.springframework.data.domain.Pageable;

public interface GatewayService {

    /**
     * 获取APIG Gateway列表
     *
     * @param param
     * @param page
     * @param size
     * @return
     */
    PageResult<GatewayResult> fetchGateways(QueryAPIGParam param, int page, int size);

    /**
     * 导入Gateway
     *
     * @param param
     */
    void importGateway(ImportGatewayParam param);

    GatewayResult getGateway(String gatewayId);

    /**
     * 获取导入的Gateway列表
     *
     * @param param
     * @param pageable
     * @return
     */
    PageResult<GatewayResult> listGateways(QueryGatewayParam param, Pageable pageable);

    /**
     * 删除Gateway
     *
     * @param gatewayId
     */
    void deleteGateway(String gatewayId);

    /**
     * 拉取网关API列表
     *
     * @param gatewayId
     * @param apiType
     * @param page
     * @param size
     * @return
     */
    PageResult<APIResult> fetchAPIs(String gatewayId, String apiType, int page, int size);

    PageResult<APIResult> fetchHTTPAPIs(String gatewayId, int page, int size);

    PageResult<APIResult> fetchRESTAPIs(String gatewayId, int page, int size);

    PageResult<APIResult> fetchRoutes(String gatewayId, int page, int size);

    PageResult<GatewayMCPServerResult> fetchMcpServers(String gatewayId, int page, int size);

    String fetchAPIConfig(String gatewayId, Object config);

    String fetchMcpConfig(String gatewayId, Object conf);

    String createConsumer(Consumer consumer, ConsumerCredential credential, GatewayConfig config);

    void updateConsumer(String gwConsumerId, ConsumerCredential credential, GatewayConfig config);

    void deleteConsumer(String gwConsumerId, GatewayConfig config);

    /**
     * 检查消费者是否存在于网关中
     * @param gwConsumerId 网关消费者ID
     * @param config 网关配置
     * @return 是否存在
     */
    boolean isConsumerExists(String gwConsumerId, GatewayConfig config);

    ConsumerAuthConfig authorizeConsumer(String gatewayId, String gwConsumerId, ProductRefResult productRef);

    void revokeConsumerAuthorization(String gatewayId, String gwConsumerId, ConsumerAuthConfig config);

    GatewayConfig getGatewayConfig(String gatewayId);

    /**
     * 获取仪表板URL
     *
     * @return 仪表板URL
     */
    String getDashboard(String gatewayId, String type);
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/ConsumerService.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service;

import com.alibaba.apiopenplatform.dto.params.consumer.QueryConsumerParam;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateConsumerParam;
import com.alibaba.apiopenplatform.dto.result.ConsumerResult;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.dto.result.ConsumerCredentialResult;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateCredentialParam;
import com.alibaba.apiopenplatform.dto.params.consumer.UpdateCredentialParam;
import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateSubscriptionParam;
import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
import org.springframework.data.domain.Pageable;

public interface ConsumerService {

    /**
     * 创建Consumer
     *
     * @param param
     * @return
     */
    ConsumerResult createConsumer(CreateConsumerParam param);

    /**
     * 获取Consumer列表
     *
     * @param param
     * @param pageable
     * @return
     */
    PageResult<ConsumerResult> listConsumers(QueryConsumerParam param, Pageable pageable);

    /**
     * 查询Consumer
     *
     * @param consumerId
     * @return
     */
    ConsumerResult getConsumer(String consumerId);

    /**
     * 删除Consumer
     *
     * @param consumerId
     */
    void deleteConsumer(String consumerId);

    /**
     * 创建Consumer凭证
     *
     * @param consumerId
     * @param param
     */
    void createCredential(String consumerId, CreateCredentialParam param);

    /**
     * 获取Consumer凭证
     *
     * @param consumerId
     * @return
     */
    ConsumerCredentialResult getCredential(String consumerId);

    /**
     * 更新Consumer凭证
     *
     * @param consumerId
     * @param param
     */
    void updateCredential(String consumerId, UpdateCredentialParam param);

    /**
     * 删除Consumer凭证
     *
     * @param consumerId Consumer ID
     */
    void deleteCredential(String consumerId);

    /**
     * 订阅API产品
     *
     * @param consumerId
     * @param param
     * @return
     */
    SubscriptionResult subscribeProduct(String consumerId, CreateSubscriptionParam param);

    /**
     * 取消订阅
     *
     * @param consumerId
     * @param productId
     */
    void unsubscribeProduct(String consumerId, String productId);

    /**
     * 获取Consumer的订阅列表
     *
     * @param consumerId
     * @param param
     * @param pageable
     * @return
     */
    PageResult<SubscriptionResult> listSubscriptions(String consumerId, QuerySubscriptionParam param, Pageable pageable);

    /**
     * 取消订阅API产品
     *
     * @param consumerId
     * @param productId
     */
    void deleteSubscription(String consumerId, String productId);

    /**
     * 审批订阅API产品
     *
     * @param consumerId
     * @param productId
     */
    SubscriptionResult approveSubscription(String consumerId, String productId);
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/types/index.ts:
--------------------------------------------------------------------------------

```typescript

// 与 Admin 端保持一致的 API 产品配置接口
export interface ApiProductConfig {
  spec: string;
  meta: {
    source: string;
    type: string;
  }
}

export interface ApiProductMcpConfig {
  mcpServerName: string;
  tools: string;
  meta: {
    source: string;
    mcpServerName: string;
    mcpServerConfig: any;
    fromType: string;
    protocol?: string;
  }
  mcpServerConfig: {
    path: string;
    domains: {
      domain: string;
      protocol: string;
    }[];
    rawConfig?: unknown;
  }
}

export interface ApiProduct {
  productId: string;
  name: string;
  description: string;
  type: 'REST_API' | 'MCP_SERVER';
  category: string;
  status: 'PENDING' | 'READY' | 'PUBLISHED' | string;
  createAt: string;
  createdAt?: string; // 兼容字段
  enableConsumerAuth?: boolean;
  autoApprove?: boolean;
  apiConfig?: ApiProductConfig;
  mcpConfig?: ApiProductMcpConfig;
  document?: string;
  icon?: ProductIcon | null;
  // 向后兼容
  apiSpec?: string;
}

export const ProductType = {
  REST_API: 'REST_API',
  MCP_SERVER: 'MCP_SERVER',
} as const;
export type ProductType = typeof ProductType[keyof typeof ProductType];

// 产品状态枚举
export const ProductStatus = {
  ENABLE: 'ENABLE',
  DISABLE: 'DISABLE',
} as const;
export type ProductStatus = typeof ProductStatus[keyof typeof ProductStatus];

// 产品分类
export const ProductCategory = {
  OFFICIAL: 'official',
  COMMUNITY: 'community',
  CUSTOM: 'custom',
} as const;
export type ProductCategory = typeof ProductCategory[keyof typeof ProductCategory];

// 基础产品接口
export interface BaseProduct {
  productId: string;
  name: string;
  description: string;
  status: ProductStatus;
  enableConsumerAuth: boolean | null;
  autoApprove?: boolean;
  type: ProductType;
  document: string | null;
  icon: ProductIcon | null;
  category: ProductCategory;
  productType: ProductType;
  productName: string;
  mcpConfig: any;
  updatedAt: string;
  lastUpdated: string;
}

// REST API 产品
export interface RestApiProduct extends BaseProduct {
  apiSpec: string | null;
  mcpSpec: null;
}

// MCP Server 产品
// @ts-ignore
export interface McpServerProduct extends BaseProduct {
  apiSpec: null;
  mcpSpec?: McpServerConfig; // 保持向后兼容
  mcpConfig?: McpConfig; // 新的nacos格式
  enabled?: boolean;
}

// 联合类型
export type Product = RestApiProduct | McpServerProduct;

// 产品图标类型(与 Admin 端保持一致)
export interface ProductIcon {
  type: 'URL' | 'BASE64';
  value: string;
}

// API 响应结构
export interface ApiResponse<T> {
  code: string;
  message: string | null;
  data: T;
}

// 分页响应结构
export interface PaginatedResponse<T> {
  content: T[];
  totalElements: number;
  totalPages: number;
  size: number;
  number: number;
  first: boolean;
  last: boolean;
}

// MCP 配置解析后的结构 (旧格式,保持向后兼容)
export interface McpServerConfig {
  mcpRouteId?: string;
  mcpServerName?: string;
  fromType?: string;
  fromGatewayType?: string;
  domains?: Array<{
    domain: string;
    protocol: string;
  }>;
  mcpServerConfig?: string; // YAML配置字符串
  enabled?: boolean;
  server?: {
    name: string;
    config: Record<string, unknown>;
    allowTools: string[];
  };
  tools?: Array<{
    name: string;
    description: string;
    args: Array<{
      name: string;
      description: string;
      type: string;
      required: boolean;
      position: string;
      default?: string;
      enum?: string[];
    }>;
    requestTemplate: {
      url: string;
      method: string;
      headers: Array<{
        key: string;
        value: string;
      }>;
    };
    responseTemplate: {
      body: string;
    };
  }>;
}

// 新的nacos格式MCP配置
export interface McpConfig {
  mcpServerName: string;
  mcpServerConfig: {
    path: string;
    domains: Array<{
      domain: string;
      protocol: string;
    }>;
    rawConfig?: string;
  };
  tools: string; // YAML格式的tools配置字符串
  meta: {
    source: string;
    fromType: string;
    protocol?: string;
  };
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/security/ContextHolder.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.core.security;

import cn.hutool.core.util.EnumUtil;
import com.alibaba.apiopenplatform.core.constant.CommonConstants;
import com.alibaba.apiopenplatform.support.enums.UserType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

@Component
public class ContextHolder {

    private final ThreadLocal<String> portalContext = new ThreadLocal<>();

    public String getPortal() {
        return portalContext.get();
    }

    public void savePortal(String portalId) {
        portalContext.set(portalId);
    }

    public void clearPortal() {
        portalContext.remove();
    }

    /**
     * 获取当前认证用户ID
     *
     * @return
     */
    public String getUser() {
        Authentication authentication = getAuthenticationFromContext();
        Object principal = authentication.getPrincipal();
        if (principal instanceof String) {
            return (String) principal;
        }
        throw new AuthenticationCredentialsNotFoundException("User ID not found in authentication");
    }

    /**
     * 获取当前认证用户类型
     *
     * @return 用户类型
     * @throws AuthenticationException 如果用户未认证或类型无效
     */
    private UserType getCurrentUserType() {
        Authentication authentication = getAuthenticationFromContext();
        return authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .filter(authority -> authority.startsWith(CommonConstants.ROLE_PREFIX))
                .map(authority -> authority.substring(5))
                .map(role -> EnumUtil.likeValueOf(UserType.class, role))
                .findFirst()
                .orElseThrow(() -> new AuthenticationCredentialsNotFoundException("User type not found in authentication"));
    }

    public boolean isAdministrator() {
        try {
            return getCurrentUserType() == UserType.ADMIN;
        } catch (AuthenticationException e) {
            return false;
        }
    }

    public boolean isDeveloper() {
        try {
            return getCurrentUserType() == UserType.DEVELOPER;
        } catch (AuthenticationException e) {
            return false;
        }
    }

    /**
     * 获取当前认证信息
     *
     * @return
     */
    private Authentication getAuthenticationFromContext() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated() ||
                authentication instanceof AnonymousAuthenticationToken) {
            throw new AuthenticationCredentialsNotFoundException("No authenticated user found");
        }
        return authentication;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/DeveloperService.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service;

import com.alibaba.apiopenplatform.core.event.PortalDeletingEvent;
import com.alibaba.apiopenplatform.dto.params.developer.CreateDeveloperParam;
import com.alibaba.apiopenplatform.dto.params.developer.CreateExternalDeveloperParam;
import com.alibaba.apiopenplatform.dto.params.developer.QueryDeveloperParam;
import com.alibaba.apiopenplatform.dto.params.developer.UpdateDeveloperParam;
import com.alibaba.apiopenplatform.dto.result.AuthResult;
import com.alibaba.apiopenplatform.dto.result.DeveloperResult;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.support.enums.DeveloperStatus;
import org.springframework.data.domain.Pageable;

import javax.servlet.http.HttpServletRequest;

public interface DeveloperService {

    /**
     * 开发者注册
     *
     * @param param
     * @return
     */
    AuthResult registerDeveloper(CreateDeveloperParam param);

    /**
     * 创建开发者
     *
     * @param param
     * @return
     */
    DeveloperResult createDeveloper(CreateDeveloperParam param);

    /**
     * 开发者登录
     *
     * @param username
     * @param password
     * @return
     */
    AuthResult login(String username, String password);

    /**
     * 校验Developer
     *
     * @param developerId
     */
    void existsDeveloper(String developerId);

    /**
     * 获取外部开发者详情
     *
     * @param provider
     * @param subject
     * @return
     */
    DeveloperResult getExternalDeveloper(String provider, String subject);

    /**
     * 外部账号创建开发者
     *
     * @param param
     * @return
     */
    DeveloperResult createExternalDeveloper(CreateExternalDeveloperParam param);

    /**
     * 删除开发者账号(删除账号及所有外部身份)
     *
     * @param developerId
     */
    void deleteDeveloper(String developerId);

    /**
     * 查询开发者详情
     *
     * @param developerId
     * @return
     */
    DeveloperResult getDeveloper(String developerId);

    /**
     * 查询门户下的开发者列表
     *
     * @param param
     * @param pageable
     * @return
     */
    PageResult<DeveloperResult> listDevelopers(QueryDeveloperParam param, Pageable pageable);

    /**
     * 设置开发者状态
     *
     * @param developerId
     * @param status
     * @return
     */
    void setDeveloperStatus(String developerId, DeveloperStatus status);

    /**
     * 开发者修改密码
     *
     * @param developerId
     * @param oldPassword
     * @param newPassword
     * @return
     */
    boolean resetPassword(String developerId, String oldPassword, String newPassword);

    /**
     * 开发者更新个人信息
     *
     * @param param
     * @return
     */
    boolean updateProfile(UpdateDeveloperParam param);

    /**
     * 清理门户资源
     *
     * @param event
     */
    void handlePortalDeletion(PortalDeletingEvent event);

    /**
     * 开发者登出
     *
     * @param request HTTP请求
     */
    void logout(HttpServletRequest request);

    /**
     * 获取当前登录开发者信息
     *
     * @return 开发者信息
     */
    DeveloperResult getCurrentDeveloperInfo();

    /**
     * 当前开发者修改密码
     *
     * @param oldPassword 旧密码
     * @param newPassword 新密码
     * @return 是否成功
     */
    boolean changeCurrentDeveloperPassword(String oldPassword, String newPassword);
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/controller/PortalController.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
import com.alibaba.apiopenplatform.dto.params.portal.*;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.dto.result.PortalResult;
import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
import com.alibaba.apiopenplatform.service.PortalService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/portals")
@Slf4j
@Validated
@Tag(name = "门户管理")
@AdminAuth
@RequiredArgsConstructor
public class PortalController {

    private final PortalService portalService;

    @Operation(summary = "创建门户")
    @PostMapping
    public PortalResult createPortal(@Valid @RequestBody CreatePortalParam param) {
        return portalService.createPortal(param);
    }

    @Operation(summary = "获取门户详情")
    @GetMapping("/{portalId}")
    public PortalResult getPortal(@PathVariable String portalId) {
        return portalService.getPortal(portalId);
    }

    @Operation(summary = "获取门户列表")
    @GetMapping
    public PageResult<PortalResult> listPortals(Pageable pageable) {
        return portalService.listPortals(pageable);
    }

    @Operation(summary = "更新门户信息")
    @PutMapping("/{portalId}")
    public PortalResult updatePortal(@PathVariable String portalId, @Valid @RequestBody UpdatePortalParam param) {
        return portalService.updatePortal(portalId, param);
    }

    @Operation(summary = "删除门户")
    @DeleteMapping("/{portalId}")
    public void deletePortal(@PathVariable String portalId) {
        portalService.deletePortal(portalId);
    }

    @Operation(summary = "绑定域名")
    @PostMapping("/{portalId}/domains")
    public PortalResult bindDomain(@PathVariable String portalId, @Valid @RequestBody BindDomainParam param) {
        return portalService.bindDomain(portalId, param);
    }

    @Operation(summary = "解绑域名")
    @DeleteMapping("/{portalId}/domains/{domain}")
    public PortalResult unbindDomain(@PathVariable String portalId, @PathVariable String domain) {
        return portalService.unbindDomain(portalId, domain);
    }

    @Operation(summary = "获取门户上的API产品订阅列表")
    @GetMapping("/{portalId}/subscriptions")
    public PageResult<SubscriptionResult> listSubscriptions(@PathVariable String portalId,
                                                            QuerySubscriptionParam param,
                                                            Pageable pageable) {
        return portalService.listSubscriptions(portalId, param, pageable);
    }

    @Operation(summary = "获取门户Dashboard监控面板URL")
    @GetMapping("/{portalId}/dashboard")
    public String getDashboard(@PathVariable String portalId,
                               @RequestParam(required = false, defaultValue = "Portal") String type) {
        return portalService.getDashboard(portalId);
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/assets/react.svg:
--------------------------------------------------------------------------------

```
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/assets/react.svg:
--------------------------------------------------------------------------------

```
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/impl/AdministratorServiceImpl.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.core.constant.Resources;
import com.alibaba.apiopenplatform.core.security.ContextHolder;
import com.alibaba.apiopenplatform.core.utils.TokenUtil;
import com.alibaba.apiopenplatform.dto.result.AdminResult;
import com.alibaba.apiopenplatform.dto.result.AuthResult;
import com.alibaba.apiopenplatform.entity.Administrator;
import com.alibaba.apiopenplatform.repository.AdministratorRepository;
import com.alibaba.apiopenplatform.service.AdministratorService;
import com.alibaba.apiopenplatform.core.utils.PasswordHasher;
import com.alibaba.apiopenplatform.core.utils.IdGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;

@Service
@RequiredArgsConstructor
@Transactional
public class AdministratorServiceImpl implements AdministratorService {

    private final AdministratorRepository administratorRepository;

    private final ContextHolder contextHolder;

    @Override
    public AuthResult login(String username, String password) {
        Administrator admin = administratorRepository.findByUsername(username)
                .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, Resources.ADMINISTRATOR, username));

        if (!PasswordHasher.verify(password, admin.getPasswordHash())) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "用户名或密码错误");
        }

        String token = TokenUtil.generateAdminToken(admin.getAdminId());
        return AuthResult.of(token, TokenUtil.getTokenExpiresIn());
    }

    @Override
    public boolean needInit() {
        return administratorRepository.count() == 0;
    }

    @Override
    public AdminResult initAdmin(String username, String password) {
        Administrator admin = Administrator.builder()
                .adminId(generateAdminId())
                .username(username)
                .passwordHash(PasswordHasher.hash(password))
                .build();
        administratorRepository.save(admin);
        return new AdminResult().convertFrom(admin);
    }

    @Override
    public AdminResult getAdministrator() {
        Administrator administrator = findAdministrator(contextHolder.getUser());
        return new AdminResult().convertFrom(administrator);
    }

    @Override
    @Transactional
    public void resetPassword(String oldPassword, String newPassword) {
        Administrator admin = findAdministrator(contextHolder.getUser());

        if (!PasswordHasher.verify(oldPassword, admin.getPasswordHash())) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "用户名或密码错误");
        }

        admin.setPasswordHash(PasswordHasher.hash(newPassword));
        administratorRepository.save(admin);
    }

    private String generateAdminId() {
        return IdGenerator.genAdministratorId();
    }

    private Administrator findAdministrator(String adminId) {
        return administratorRepository.findByAdminId(adminId)
                .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, Resources.ADMINISTRATOR, adminId));
    }
} 
```

--------------------------------------------------------------------------------
/deploy/helm/templates/mysql.yaml:
--------------------------------------------------------------------------------

```yaml
{{- if .Values.mysql.enabled }}
{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "mysql-secret") }}
{{- $rootPassword := "" }}
{{- $userPassword := "" }}
{{- if $existingSecret }}
  {{- $rootPassword = (index $existingSecret.data "MYSQL_ROOT_PASSWORD" | b64dec) }}
  {{- $userPassword = (index $existingSecret.data "MYSQL_PASSWORD" | b64dec) }}
{{- else }}
  {{- if .Values.mysql.auth.rootPassword }}
    {{- $rootPassword = .Values.mysql.auth.rootPassword }}
  {{- else }}
    {{- $rootPassword = randAlphaNum 16 }}
  {{- end }}
  {{- if .Values.mysql.auth.password }}
    {{- $userPassword = .Values.mysql.auth.password }}
  {{- else }}
    {{- $userPassword = randAlphaNum 16 }}
  {{- end }}
{{- end }}
---
# MySQL Secret: 存储敏感的数据库凭据(自动生成随机密码)
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
type: Opaque
stringData:
  MYSQL_ROOT_PASSWORD: {{ $rootPassword | quote }}
  MYSQL_DATABASE: {{ .Values.mysql.auth.database | quote }}
  MYSQL_USER: {{ .Values.mysql.auth.username | quote }}
  MYSQL_PASSWORD: {{ $userPassword | quote }}

---
# HiMarket Server Secret: 应用专用敏感配置(使用相同的密码)
apiVersion: v1
kind: Secret
metadata:
  name: himarket-server-secret
  labels:
    app: himarket-server
type: Opaque
stringData:
  # 使用相同的 MySQL 密码变量,确保一致性
  DB_HOST: "mysql-headless-svc"
  DB_PORT: "3306"
  DB_NAME: {{ .Values.mysql.auth.database | quote }}
  DB_USERNAME: {{ .Values.mysql.auth.username | quote }}
  DB_PASSWORD: {{ $userPassword | quote }}

---
# MySQL Headless Service: 为 StatefulSet 提供稳定的网络域
apiVersion: v1
kind: Service
metadata:
  name: mysql-headless-svc
spec:
  ports:
    - port: 3306
      name: mysql
  clusterIP: None
  selector:
    app: mysql

---
# MySQL External Service: 暴露数据库给外部访问(可选)
{{- if .Values.mysql.service.external.enabled }}
apiVersion: v1
kind: Service
metadata:
  name: mysql-external-svc
spec:
  type: {{ .Values.mysql.service.external.type }}
  ports:
    - port: 3306
      targetPort: 3306
      protocol: TCP
  selector:
    app: mysql
{{- end }}

---
# MySQL StatefulSet: 部署 MySQL 应用
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  replicas: {{ .Values.mysql.replicaCount }}
  selector:
    matchLabels:
      app: mysql
  serviceName: "mysql-headless-svc"
  template:
    metadata:
      labels:
        app: mysql
    spec:
      serviceAccountName: {{ include "himarket.serviceAccountName" . }}
      containers:
        - name: mysql
          image: "{{ .Values.hub }}/{{ .Values.mysql.image.repository }}:{{ .Values.mysql.image.tag }}"
          imagePullPolicy: {{ .Values.mysql.image.pullPolicy }}
          ports:
            - containerPort: 3306
              name: mysql
          envFrom:
            - secretRef:
                name: mysql-secret
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
          {{- with .Values.mysql.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          # 健康检查
          livenessProbe:
            exec:
              command:
                - mysqladmin
                - ping
                - -h
                - localhost
                - -u{{ .Values.mysql.auth.username }}
                - -p{{ $userPassword }}
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
          readinessProbe:
            exec:
              command:
                - mysql
                - -h
                - localhost
                - -u{{ .Values.mysql.auth.username }}
                - -p{{ $userPassword }}
                - -e
                - "SELECT 1"
            initialDelaySeconds: 1
            periodSeconds: 1
            timeoutSeconds: 5

  volumeClaimTemplates:
    - metadata:
        name: mysql-data
      spec:
        accessModes: 
          - {{ .Values.mysql.persistence.accessMode }}
        resources:
          requests:
            storage: {{ .Values.mysql.persistence.size }}
        {{- if .Values.mysql.persistence.storageClass }}
        {{- if (eq "-" .Values.mysql.persistence.storageClass) }}
        storageClassName: ""
        {{- else }}
        storageClassName: {{ .Values.mysql.persistence.storageClass | quote }}
        {{- end }}
        {{- end }}
{{- end }}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/controller/NacosController.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
import com.alibaba.apiopenplatform.dto.params.nacos.CreateNacosParam;
import com.alibaba.apiopenplatform.dto.params.nacos.QueryNacosParam;
import com.alibaba.apiopenplatform.dto.params.nacos.UpdateNacosParam;
import com.alibaba.apiopenplatform.dto.result.MseNacosResult;
import com.alibaba.apiopenplatform.dto.result.NacosMCPServerResult;
import com.alibaba.apiopenplatform.dto.result.NacosNamespaceResult;
import com.alibaba.apiopenplatform.dto.result.NacosResult;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.service.NacosService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Tag(name = "Nacos资源管理", description = "Nacos实例管理与能力市场统一控制器")
@RestController
@RequestMapping("/nacos")
@RequiredArgsConstructor
@AdminAuth
public class NacosController {

    private final NacosService nacosService;

    @Operation(summary = "获取Nacos实例列表", description = "分页获取Nacos实例列表")
    @GetMapping
    public PageResult<NacosResult> listNacosInstances(Pageable pageable) {
        return nacosService.listNacosInstances(pageable);
    }

    @Operation(summary = "从阿里云MSE获取Nacos集群列表")
    @GetMapping("/mse")
    public PageResult<MseNacosResult> fetchNacos(@Valid QueryNacosParam param,
                                              Pageable pageable) {
        return nacosService.fetchNacos(param, pageable);
    }

    @Operation(summary = "获取Nacos实例详情", description = "根据ID获取Nacos实例详细信息")
    @GetMapping("/{nacosId}")
    public NacosResult getNacosInstance(@PathVariable String nacosId) {
        return nacosService.getNacosInstance(nacosId);
    }

    @Operation(summary = "创建Nacos实例", description = "创建新的Nacos实例")
    @PostMapping
    public void createNacosInstance(@RequestBody @Valid CreateNacosParam param) {
        nacosService.createNacosInstance(param);
    }

    @Operation(summary = "更新Nacos实例", description = "更新指定Nacos实例信息")
    @PutMapping("/{nacosId}")
    public void updateNacosInstance(@PathVariable String nacosId, @RequestBody @Valid UpdateNacosParam param) {
        nacosService.updateNacosInstance(nacosId, param);
    }

    @Operation(summary = "删除Nacos实例", description = "删除指定的Nacos实例")
    @DeleteMapping("/{nacosId}")
    public void deleteNacosInstance(@PathVariable String nacosId) {
        nacosService.deleteNacosInstance(nacosId);
    }

    @Operation(summary = "获取Nacos中的MCP Server列表", description = "获取指定Nacos实例中的MCP Server列表,可按命名空间过滤")
    @GetMapping("/{nacosId}/mcp-servers")
    public PageResult<NacosMCPServerResult> fetchMcpServers(@PathVariable String nacosId,
                                                            @RequestParam(value = "namespaceId", required = false) String namespaceId,
                                                            Pageable pageable) throws Exception {
        return nacosService.fetchMcpServers(nacosId, namespaceId, pageable);
    }

    @Operation(summary = "获取指定Nacos实例的命名空间列表")
    @GetMapping("/{nacosId}/namespaces")
    public PageResult<NacosNamespaceResult> fetchNamespaces(@PathVariable String nacosId,
                                                            Pageable pageable) throws Exception {
        return nacosService.fetchNamespaces(nacosId, pageable);
    }

} 
```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/pages/ConsumerDetail.tsx:
--------------------------------------------------------------------------------

```typescript
import { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { Layout } from "../components/Layout";
import { Alert, Tabs } from "antd";
import { ArrowLeftOutlined } from "@ant-design/icons";
import api from "../lib/api";
import { ConsumerBasicInfo, CredentialManager, SubscriptionManager } from "../components/consumer";
import type { Consumer, Subscription } from "../types/consumer";
import type { ApiResponse } from "../types";

function ConsumerDetailPage() {
  const { consumerId } = useParams();
  const navigate = useNavigate();
  const [subscriptionsLoading, setSubscriptionsLoading] = useState(false);
  const [error, setError] = useState('');
  const [consumer, setConsumer] = useState<Consumer>();
  const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
  const [activeTab, setActiveTab] = useState('basic');

  useEffect(() => {
    if (!consumerId) return;
    
    const fetchConsumerDetail = async () => {
      try {
        const response: ApiResponse<Consumer> = await api.get(`/consumers/${consumerId}`);
        if (response?.code === "SUCCESS" && response?.data) {
          setConsumer(response.data);
        }
      } catch (error) {
        console.error('获取消费者详情失败:', error);
        setError('加载失败,请稍后重试');
      }
    };

    const fetchSubscriptions = async () => {
      setSubscriptionsLoading(true);
      try {
        const response: ApiResponse<{content: Subscription[], totalElements: number}> = await api.get(`/consumers/${consumerId}/subscriptions`);
        if (response?.code === "SUCCESS" && response?.data) {
          // 从分页数据中提取实际的订阅数组
          const subscriptionsData = response.data.content || [];
          setSubscriptions(subscriptionsData);
        }
      } catch (error) {
        console.error('获取订阅列表失败:', error);
      } finally {
        setSubscriptionsLoading(false);
      }
    };
    
    const loadData = async () => {
      try {
        await Promise.all([
          fetchConsumerDetail(),
          fetchSubscriptions()
        ]);
      } finally {
        // 不设置loading状态,避免闪烁
      }
    };
    
    loadData();
  }, [consumerId]);

  if (error) {
    return (
      <Layout>
        <Alert
          message="加载失败"
          description={error}
          type="error"
          showIcon
          className="my-8" />
      </Layout>
    );
  }

  return (
    <Layout>
      {consumer ? (
        <>
          {/* 消费者头部 - 返回按钮 + 消费者名称 */}
          <div className="mb-2">
            <div className="flex items-center gap-2">
              <ArrowLeftOutlined 
                className="text-gray-500 hover:text-gray-700 cursor-pointer"
                style={{ fontSize: '20px', fontWeight: 'normal' }}
                onClick={() => navigate('/consumers')}
              />
              <span className="text-2xl font-normal text-gray-500">
                {consumer.name}
              </span>
            </div>
          </div>
          
          <Tabs activeKey={activeTab} onChange={setActiveTab}>
            <Tabs.TabPane tab="基本信息" key="basic">
              <ConsumerBasicInfo consumer={consumer} />
              <div className="mt-6">
                <CredentialManager 
                  consumerId={consumerId!}
                />
              </div>
            </Tabs.TabPane>

            <Tabs.TabPane tab="订阅列表" key="authorization">
              <SubscriptionManager 
                consumerId={consumerId!}
                subscriptions={subscriptions}
                onSubscriptionsChange={async () => {
                  // 重新获取订阅列表
                  if (consumerId) {
                    setSubscriptionsLoading(true);
                    try {
                      const response: ApiResponse<{content: Subscription[], totalElements: number}> = await api.get(`/consumers/${consumerId}/subscriptions`);
                      if (response?.code === "SUCCESS" && response?.data) {
                        // 从分页数据中提取实际的订阅数组
                        const subscriptionsData = response.data.content || [];
                        setSubscriptions(subscriptionsData);
                      }
                    } catch (error) {
                      console.error('获取订阅列表失败:', error);
                    } finally {
                      setSubscriptionsLoading(false);
                    }
                  }
                }}
                loading={subscriptionsLoading}
              />
            </Tabs.TabPane>
          </Tabs>
        </>
      ) : (
        <div className="flex items-center justify-center h-64">
          <div className="text-gray-500">加载中...</div>
        </div>
      )}
    </Layout>
  );
}

export default ConsumerDetailPage;

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/security/JwtAuthenticationFilter.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.core.security;

import com.alibaba.apiopenplatform.core.constant.CommonConstants;
import com.alibaba.apiopenplatform.core.utils.TokenUtil;
import com.alibaba.apiopenplatform.support.common.User;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    // 白名单路径
    private static final String[] WHITELIST_PATHS = {
            "/admins/init",
            "/admins/need-init", 
            "/admins/login",
            "/developers",
            "/developers/login",
            "/developers/authorize",
            "/developers/callback",
            "/developers/providers",
            "/developers/oidc/authorize",
            "/developers/oidc/callback",
            "/developers/oidc/providers",
            "/developers/oauth2/token",
            "/portal/swagger-ui.html",
            "/portal/swagger-ui/**",
            "/portal/v3/api-docs/**",
            "/favicon.ico",
            "/error"
    };

    @Override
    protected void doFilterInternal(@NotNull HttpServletRequest request,
                                    @NotNull HttpServletResponse response,
                                    @NotNull FilterChain chain)
            throws IOException, ServletException {

        // 检查是否是白名单路径
        if (isWhitelistPath(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }

        try {
            String token = TokenUtil.getTokenFromRequest(request);
            if (token != null) {
                // 检查token是否被撤销
                if (TokenUtil.isTokenRevoked(token)) {
                    log.debug("Token已被撤销: {}", token);
                    SecurityContextHolder.clearContext();
                } else {
                    try {
                        authenticateRequest(token);
                    } catch (Exception e) {
                        log.debug("Token认证失败: {}", e.getMessage());
                        SecurityContextHolder.clearContext();
                    }
                }
            }
        } catch (Exception e) {
            log.debug("Token处理异常: {}", e.getMessage());
            SecurityContextHolder.clearContext();
        }
        chain.doFilter(request, response);
    }

    private boolean isWhitelistPath(String requestURI) {
        for (String whitelistPath : WHITELIST_PATHS) {
            if (whitelistPath.endsWith("/**")) {
                // 处理通配符路径
                String basePath = whitelistPath.substring(0, whitelistPath.length() - 2);
                if (requestURI.startsWith(basePath)) {
                    return true;
                }
            } else if (requestURI.equals(whitelistPath)) {
                return true;
            }
        }
        return false;
    }

    private void authenticateRequest(String token) {
        User user = TokenUtil.parseUser(token);
        // 设置认证信息
        String role = CommonConstants.ROLE_PREFIX + user.getUserType().name();
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                user.getUserId(),
                null,
                Collections.singletonList(new SimpleGrantedAuthority(role))
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
} 
```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/pages/Login.tsx:
--------------------------------------------------------------------------------

```typescript
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import api from "../lib/api";
import { authApi } from '@/lib/api'
import { Form, Input, Button, Alert } from "antd";

const Login: React.FC = () => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const [isRegister, setIsRegister] = useState<boolean | null>(null); // null 表示正在加载
  const navigate = useNavigate();

  // 页面加载时检查权限
  useEffect(() => {
    const checkAuth = async () => {
      try {
        const response = await authApi.getNeedInit(); // 替换为你的权限接口
        setIsRegister(response.data === true); // 根据接口返回值决定是否显示注册表单
      } catch (err) {
        setIsRegister(false); // 默认显示登录表单
      }
    };

    checkAuth();
  }, []);

  // 登录表单提交
  const handleLogin = async (values: { username: string; password: string }) => {
    setLoading(true);
    setError("");
    try {
      const response = await api.post("/admins/login", {
        username: values.username,
        password: values.password,
      });
      const accessToken = response.data.access_token;
      localStorage.setItem('access_token', accessToken);
      localStorage.setItem('userInfo', JSON.stringify(response.data));
      navigate('/portals');
    } catch {
      setError("账号或密码错误");
    } finally {
      setLoading(false);
    }
  };

  // 注册表单提交
  const handleRegister = async (values: { username: string; password: string; confirmPassword: string }) => {
    setLoading(true);
    setError("");
    if (values.password !== values.confirmPassword) {
      setError("两次输入的密码不一致");
      setLoading(false);
      return;
    }
    try {
      const response = await api.post("/admins/init", {
        username: values.username,
        password: values.password,
      });
      if (response.data.adminId) {
        setIsRegister(false); // 初始化成功后切换到登录状态
      }
    } catch {
      setError("初始化失败,请重试");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-white">
      <div className="bg-white p-8 rounded-xl shadow-2xl w-full max-w-md flex flex-col items-center border border-gray-100">
        {/* Logo */}
        <div className="mb-4">
          <img src="/logo.png" alt="Logo" className="w-16 h-16 mx-auto mb-4" />
        </div>
        <h2 className="text-2xl font-bold mb-6 text-gray-900 text-center">
          {isRegister ? "注册Admin账号" : "登录HiMarket-后台"}
        </h2>

        {/* 登录表单 */}
        {!isRegister && (
          <Form
            className="w-full flex flex-col gap-4"
            layout="vertical"
            onFinish={handleLogin}
          >
            <Form.Item
              name="username"
              rules={[{ required: true, message: "请输入账号" }]}
            >
              <Input placeholder="账号" size="large" />
            </Form.Item>
            <Form.Item
              name="password"
              rules={[{ required: true, message: "请输入密码" }]}
            >
              <Input.Password placeholder="密码" size="large" />
            </Form.Item>
            {error && <Alert message={error} type="error" showIcon className="mb-2" />}
            <Form.Item>
              <Button
                type="primary"
                htmlType="submit"
                className="w-full"
                loading={loading}
                size="large"
              >
                登录
              </Button>
            </Form.Item>
          </Form>
        )}

        {/* 注册表单 */}
        {isRegister && (
          <Form
            className="w-full flex flex-col gap-4"
            layout="vertical"
            onFinish={handleRegister}
          >
            <Form.Item
              name="username"
              rules={[{ required: true, message: "请输入账号" }]}
            >
              <Input placeholder="账号" size="large" />
            </Form.Item>
            <Form.Item
              name="password"
              rules={[{ required: true, message: "请输入密码" }]}
            >
              <Input.Password placeholder="密码" size="large" />
            </Form.Item>
            <Form.Item
              name="confirmPassword"
              rules={[{ required: true, message: "请确认密码" }]}
            >
              <Input.Password placeholder="确认密码" size="large" />
            </Form.Item>
            {error && <Alert message={error} type="error" showIcon className="mb-2" />}
            <Form.Item>
              <Button
                type="primary"
                htmlType="submit"
                className="w-full"
                loading={loading}
                size="large"
              >
                初始化
              </Button>
            </Form.Item>
          </Form>
        )}
      </div>
    </div>
  );
};

export default Login;

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/console/ImportMseNacosModal.tsx:
--------------------------------------------------------------------------------

```typescript
import { useState } from 'react'
import { Button, Table, Modal, Form, Input, message } from 'antd'
import { nacosApi } from '@/lib/api'

interface MseNacosItem {
  instanceId: string
  name: string
  serverIntranetEndpoint?: string
  serverInternetEndpoint?: string
  version?: string
}

interface ImportMseNacosModalProps {
  visible: boolean
  onCancel: () => void
  // 将选中的 MSE Nacos 信息带入创建表单
  onPrefill: (values: {
    nacosName?: string
    serverUrl?: string
  serverInternetEndpoint?: string
  serverIntranetEndpoint?: string
    username?: string
    password?: string
    accessKey?: string
    secretKey?: string
    description?: string
  nacosId?: string
  }) => void
}

export default function ImportMseNacosModal({ visible, onCancel, onPrefill }: ImportMseNacosModalProps) {
  const [importForm] = Form.useForm()

  const [loading, setLoading] = useState(false)
  const [list, setList] = useState<MseNacosItem[]>([])
  const [selected, setSelected] = useState<MseNacosItem | null>(null)
  const [auth, setAuth] = useState({
    regionId: '',
    accessKey: '',
    secretKey: ''
  })
  const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })

  const fetchMseNacos = async (values: any, page = 0, size = 10) => {
    setLoading(true)
    try {
      const res = await nacosApi.getMseNacos({ ...values, page, size })
      setList(res.data?.content || [])
      setPagination({ current: page + 1, pageSize: size, total: res.data?.totalElements || 0 })
    } catch (e: any) {
      // message.error(e?.response?.data?.message || '获取 MSE Nacos 列表失败')
    } finally {
      setLoading(false)
    }
  }

  const handleImport = async () => {
    if (!selected) {
      message.warning('请选择一个 Nacos 实例')
      return
    }
    // 将关键信息带出到创建表单,供用户补充
    onPrefill({
      nacosName: selected.name,
      serverUrl: selected.serverInternetEndpoint || selected.serverIntranetEndpoint,
  serverInternetEndpoint: selected.serverInternetEndpoint,
  serverIntranetEndpoint: selected.serverIntranetEndpoint,
  accessKey: auth.accessKey,
  secretKey: auth.secretKey,
  nacosId: selected.instanceId,
    })
    handleCancel()
  }

  const handleCancel = () => {
    setSelected(null)
    setList([])
    setPagination({ current: 1, pageSize: 10, total: 0 })
    importForm.resetFields()
    onCancel()
  }

  return (
    <Modal title="导入 MSE Nacos 实例" open={visible} onCancel={handleCancel} footer={null} width={800}>
      <Form form={importForm} layout="vertical" preserve={false}>
        {list.length === 0 && (
          <div className="mb-4">
            <h3 className="text-lg font-medium mb-3">认证信息</h3>
            <Form.Item label="Region" name="regionId" rules={[{ required: true, message: '请输入region' }]}>
              <Input />
            </Form.Item>
            <Form.Item label="Access Key" name="accessKey" rules={[{ required: true, message: '请输入accessKey' }]}>
              <Input />
            </Form.Item>
            <Form.Item label="Secret Key" name="secretKey" rules={[{ required: true, message: '请输入secretKey' }]}>
              <Input.Password />
            </Form.Item>
            <Button 
              type="primary" 
              onClick={() => {
                importForm.validateFields().then((values) => {
                  setAuth(values)
                  fetchMseNacos(values)
                })
              }}
              loading={loading}
            >
              获取实例列表
            </Button>
          </div>
        )}

        {list.length > 0 && (
          <div className="mb-4">
            <h3 className="text-lg font-medium mb-3">选择 Nacos 实例</h3>
            <Table
              rowKey="instanceId"
              columns={[
                { title: '实例ID', dataIndex: 'instanceId' },
                { title: '名称', dataIndex: 'name' },
                { title: '版本', dataIndex: 'version' },
              ]}
              dataSource={list}
              rowSelection={{
                type: 'radio',
                selectedRowKeys: selected ? [selected.instanceId] : [],
                onChange: (_selectedRowKeys, selectedRows) => setSelected(selectedRows[0]),
              }}
              pagination={{
                current: pagination.current,
                pageSize: pagination.pageSize,
                total: pagination.total,
                onChange: (page, pageSize) => fetchMseNacos(auth, page - 1, pageSize),
                showSizeChanger: true,
                showQuickJumper: true,
                showTotal: (total) => `共 ${total} 条`,
              }}
              size="small"
            />
          </div>
        )}

        {selected && (
          <div className="text-right">
            <Button type="primary" onClick={handleImport}>
              导入
            </Button>
          </div>
        )}
      </Form>
    </Modal>
  )
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/controller/GatewayController.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
import com.alibaba.apiopenplatform.dto.params.gateway.ImportGatewayParam;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryAPIGParam;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryAdpAIGatewayParam;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryGatewayParam;
import com.alibaba.apiopenplatform.dto.result.GatewayMCPServerResult;
import com.alibaba.apiopenplatform.dto.result.*;
import com.alibaba.apiopenplatform.service.GatewayService;
import com.alibaba.apiopenplatform.service.AdpAIGatewayService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Tag(name = "网关资源管理")
@RestController
@RequestMapping("/gateways")
@RequiredArgsConstructor
@AdminAuth
public class GatewayController {

    private final GatewayService gatewayService;
    private final AdpAIGatewayService adpAIGatewayService;

    @Operation(summary = "获取APIG Gateway列表")
    @GetMapping("/apig")
    public PageResult<GatewayResult> fetchGateways(@Valid QueryAPIGParam param,
                                                   @RequestParam(defaultValue = "1") int page,
                                                   @RequestParam(defaultValue = "500") int size) {
        return gatewayService.fetchGateways(param, page, size);
    }

    @Operation(summary = "获取ADP AI Gateway列表")
    @PostMapping("/adp")
    public PageResult<GatewayResult> fetchAdpGateways(@RequestBody @Valid QueryAdpAIGatewayParam param,
                                                      @RequestParam(defaultValue = "1") int page,
                                                      @RequestParam(defaultValue = "500") int size) {
        return adpAIGatewayService.fetchGateways(param, page, size);
    }

    @Operation(summary = "获取导入的Gateway列表")
    @GetMapping
    public PageResult<GatewayResult> listGateways(QueryGatewayParam param, Pageable pageable) {
        return gatewayService.listGateways(param, pageable);
    }

    @Operation(summary = "导入Gateway")
    @PostMapping
    public void importGateway(@RequestBody @Valid ImportGatewayParam param) {
        gatewayService.importGateway(param);
    }

    @Operation(summary = "删除Gateway")
    @DeleteMapping("/{gatewayId}")
    public void deleteGateway(@PathVariable String gatewayId) {
        gatewayService.deleteGateway(gatewayId);
    }

    @Operation(summary = "获取REST API列表")
    @GetMapping("/{gatewayId}/rest-apis")
    public PageResult<APIResult> fetchRESTAPIs(@PathVariable String gatewayId,
                                               @RequestParam(defaultValue = "1") int page,
                                               @RequestParam(defaultValue = "500") int size) {
        return gatewayService.fetchRESTAPIs(gatewayId, page, size);
    }

//    @Operation(summary = "获取API列表")
//    @GetMapping("/{gatewayId}/apis")
//    public PageResult<APIResult> fetchAPIs(@PathVariable String gatewayId,
//                                           @RequestParam String apiType,
//                                           Pageable pageable) {
//        return gatewayService.fetchAPIs(gatewayId, apiType, pageable);
//    }

    @Operation(summary = "获取MCP Server列表")
    @GetMapping("/{gatewayId}/mcp-servers")
    public PageResult<GatewayMCPServerResult> fetchMcpServers(@PathVariable String gatewayId,
                                                              @RequestParam(defaultValue = "1") int page,
                                                              @RequestParam(defaultValue = "500") int size) {
        return gatewayService.fetchMcpServers(gatewayId, page, size);
    }

    @Operation(summary = "获取仪表板URL")
    @GetMapping("/{gatewayId}/dashboard")
    public String getDashboard(@PathVariable String gatewayId,
                               @RequestParam(required = false, defaultValue = "API") String type) {
        return gatewayService.getDashboard(gatewayId, type);
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/gateway/GatewayOperator.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.service.gateway;

import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;
import com.alibaba.apiopenplatform.dto.result.GatewayMCPServerResult;
import com.alibaba.apiopenplatform.dto.result.*;
import com.alibaba.apiopenplatform.entity.*;
import com.alibaba.apiopenplatform.service.gateway.client.APIGClient;
import com.alibaba.apiopenplatform.service.gateway.client.GatewayClient;
import com.alibaba.apiopenplatform.service.gateway.client.HigressClient;
import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public abstract class GatewayOperator<T> {

    private final Map<String, GatewayClient> clientCache = new ConcurrentHashMap<>();

    abstract public PageResult<APIResult> fetchHTTPAPIs(Gateway gateway, int page, int size);

    abstract public PageResult<APIResult> fetchRESTAPIs(Gateway gateway, int page, int size);

    abstract public PageResult<? extends GatewayMCPServerResult> fetchMcpServers(Gateway gateway, int page, int size);

    abstract public String fetchAPIConfig(Gateway gateway, Object config);

    abstract public String fetchMcpConfig(Gateway gateway, Object conf);

    abstract public PageResult<GatewayResult> fetchGateways(Object param, int page, int size);

    abstract public String createConsumer(Consumer consumer, ConsumerCredential credential, GatewayConfig config);

    abstract public void updateConsumer(String consumerId, ConsumerCredential credential, GatewayConfig config);

    abstract public void deleteConsumer(String consumerId, GatewayConfig config);

    /**
     * 检查消费者是否存在于网关中
     * @param consumerId 消费者ID
     * @param config 网关配置
     * @return 是否存在
     */
    abstract public boolean isConsumerExists(String consumerId, GatewayConfig config);

    abstract public ConsumerAuthConfig authorizeConsumer(Gateway gateway, String consumerId, Object refConfig);

    abstract public void revokeConsumerAuthorization(Gateway gateway, String consumerId, ConsumerAuthConfig authConfig);

    abstract public APIResult fetchAPI(Gateway gateway, String apiId);

    abstract public GatewayType getGatewayType();

    /**
     * 获取网关控制台仪表盘链接
     * @param gateway 网关实体
     * @return 仪表盘访问链接
     */
    abstract public String getDashboard(Gateway gateway,String type);

    @SuppressWarnings("unchecked")
    protected T getClient(Gateway gateway) {
        String clientKey = gateway.getGatewayType().isAPIG() ?
                gateway.getApigConfig().buildUniqueKey() : gateway.getHigressConfig().buildUniqueKey();
        return (T) clientCache.computeIfAbsent(
                clientKey,
                key -> createClient(gateway)
        );
    }

//    @SuppressWarnings("unchecked")
//    protected T getClient(Gateway gateway) {
//        String clientKey = gateway.getGatewayType().isAPIG() ?
//                gateway.getApigConfig().buildUniqueKey() : gateway.getHigressConfig().buildUniqueKey();
//        return (T) clientCache.computeIfAbsent(
//                clientKey,
//                key -> createClient(gateway)
//        );
//    }

    /**
     * 创建网关客户端
     */
    private GatewayClient createClient(Gateway gateway) {
        switch (gateway.getGatewayType()) {
            case APIG_API:
            case APIG_AI:
                return new APIGClient(gateway.getApigConfig());
            case HIGRESS:
                return new HigressClient(gateway.getHigressConfig());
            default:
                throw new BusinessException(ErrorCode.INTERNAL_ERROR,
                        "No factory found for gateway type: " + gateway.getGatewayType());
        }
    }

    /**
     * 移除网关客户端
     */
    public void removeClient(String instanceId) {
        GatewayClient client = clientCache.remove(instanceId);
        try {
            client.close();
        } catch (Exception e) {
            log.error("Error closing client for instance: {}", instanceId, e);
        }
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/controller/ProductController.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
import com.alibaba.apiopenplatform.core.annotation.AdminOrDeveloperAuth;
import com.alibaba.apiopenplatform.dto.params.product.*;
import com.alibaba.apiopenplatform.dto.params.product.CreateProductRefParam;
import com.alibaba.apiopenplatform.dto.result.*;
import com.alibaba.apiopenplatform.service.ProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Tag(name = "API产品管理", description = "提供API产品的创建、更新、删除、查询、订阅等管理功能")
@RestController
@RequestMapping("/products")
@Slf4j
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @Operation(summary = "创建API产品")
    @PostMapping
    @AdminAuth
    public ProductResult createProduct(@RequestBody @Valid CreateProductParam param) {
        return productService.createProduct(param);
    }

    @Operation(summary = "获取API产品列表")
    @GetMapping
    public PageResult<ProductResult> listProducts(QueryProductParam param,
                                                  Pageable pageable) {
        return productService.listProducts(param, pageable);
    }

    @Operation(summary = "获取API产品详情")
    @GetMapping("/{productId}")
    public ProductResult getProduct(@PathVariable String productId) {
        return productService.getProduct(productId);
    }

    @Operation(summary = "更新API产品")
    @PutMapping("/{productId}")
    @AdminAuth
    public ProductResult updateProduct(@PathVariable String productId, @RequestBody @Valid UpdateProductParam param) {
        return productService.updateProduct(productId, param);
    }

    @Operation(summary = "发布API产品")
    @PostMapping("/{productId}/publications/{portalId}")
    @AdminAuth
    public void publishProduct(@PathVariable String productId, @PathVariable String portalId) {
        productService.publishProduct(productId, portalId);
    }

    @Operation(summary = "获取API产品的发布信息")
    @GetMapping("/{productId}/publications")
    @AdminAuth
    public PageResult<ProductPublicationResult> getPublications(@PathVariable String productId, Pageable pageable) {
        return productService.getPublications(productId, pageable);
    }

    @Operation(summary = "下线API产品")
    @DeleteMapping("/{productId}/publications/{portalId}")
    @AdminAuth
    public void unpublishProduct(@PathVariable String productId, @PathVariable String portalId) {
        productService.unpublishProduct(productId, portalId);
    }

    @Operation(summary = "删除API产品")
    @DeleteMapping("/{productId}")
    @AdminAuth
    public void deleteProduct(@PathVariable String productId) {
        productService.deleteProduct(productId);
    }

    @Operation(summary = "API产品关联API或MCP Server")
    @PostMapping("/{productId}/ref")
    @AdminAuth
    public void addProductRef(@PathVariable String productId, @RequestBody @Valid CreateProductRefParam param) throws Exception {
        productService.addProductRef(productId, param);
    }

    @Operation(summary = "获取API产品关联的API或MCP Server")
    @GetMapping("/{productId}/ref")
    public ProductRefResult getProductRef(@PathVariable String productId) {
        return productService.getProductRef(productId);
    }

    @Operation(summary = "删除API产品关联的API或MCP Server")
    @DeleteMapping("/{productId}/ref")
    @AdminAuth
    public void deleteProductRef(@PathVariable String productId) {
        productService.deleteProductRef(productId);
    }

    @Operation(summary = "获取API产品的Dashboard监控面板URL")
    @GetMapping("/{productId}/dashboard")
    public String getProductDashboard(@PathVariable String productId) {
        return productService.getProductDashboard(productId);
    }

    @Operation(summary = "获取产品的订阅列表")
    @GetMapping("/{productId}/subscriptions")
    @AdminOrDeveloperAuth
    public PageResult<SubscriptionResult> listProductSubscriptions(
            @PathVariable String productId,
            QueryProductSubscriptionParam param,
            Pageable pageable) {
        return productService.listProductSubscriptions(productId, param, pageable);
    }
}

```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/config/SecurityConfig.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.config;

import com.alibaba.apiopenplatform.core.security.JwtAuthenticationFilter;
import com.alibaba.apiopenplatform.core.utils.TokenUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.*;

import com.alibaba.apiopenplatform.core.security.DeveloperAuthenticationProvider;
import org.springframework.http.HttpMethod;

/**
 * Spring Security安全配置,集成JWT认证与接口权限控制,支持管理员和开发者多用户体系
 *
 */
@Configuration
@RequiredArgsConstructor
@Slf4j
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    private final DeveloperAuthenticationProvider developerAuthenticationProvider;

    // Auth相关
    private static final String[] AUTH_WHITELIST = {
            "/admins/init",
            "/admins/need-init",
            "/admins/login",
            "/developers",
            "/developers/login",
            "/developers/authorize",
            "/developers/callback",
            "/developers/providers",
            "/developers/oidc/authorize",
            "/developers/oidc/callback",
            "/developers/oidc/providers",
            "/developers/oauth2/token"
    };

    // Swagger API文档相关
    private static final String[] SWAGGER_WHITELIST = {
            "/portal/swagger-ui.html",
            "/portal/swagger-ui/**",
            "/portal/v3/api-docs/**"
    };

    // 系统路径白名单
    private static final String[] SYSTEM_WHITELIST = {
            "/favicon.ico",
            "/error"
    };

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors(Customizer.withDefaults())
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .authorizeRequests()
                // OPTIONS请求放行
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                // 认证相关接口放行
                .antMatchers(AUTH_WHITELIST).permitAll()
                // Swagger相关接口放行
                .antMatchers(SWAGGER_WHITELIST).permitAll()
                // 系统路径放行
                .antMatchers(SYSTEM_WHITELIST).permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .authenticationProvider(developerAuthenticationProvider);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfig.setAllowedMethods(Collections.singletonList("*"));
        corsConfig.setAllowedHeaders(Collections.singletonList("*"));
        corsConfig.setAllowCredentials(true);
        corsConfig.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);
        return source;
    }
}
```

--------------------------------------------------------------------------------
/portal-server/pom.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.alibaba.himarket</groupId>
        <artifactId>himarket</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>portal-server</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba.himarket</groupId>
            <artifactId>portal-dal</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <artifactId>checker-qual</artifactId>
                    <groupId>org.checkerframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-mail</artifactId>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>org.jacoco.agent</artifactId>
                    <groupId>org.jacoco</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>alibabacloud-apig20240327</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>annotations</artifactId>
                    <groupId>org.jetbrains</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-maintainer-client</artifactId>
            <version>3.0.2</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>mse20190531</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>dom4j</artifactId>
                    <groupId>org.dom4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>credentials-java</artifactId>
                    <groupId>com.aliyun</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
          <groupId>com.aliyun</groupId>
          <artifactId>alibabacloud-sls20201230</artifactId>
          <version>4.0.11</version>
        </dependency>
        
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.bouncycastle</groupId>
                    <artifactId>bcprov-jdk16</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <!-- SnakeYAML for JSON to YAML conversion -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>2.0</version>
        </dependency>
    </dependencies>

</project>
```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/Layout.tsx:
--------------------------------------------------------------------------------

```typescript
import React, { useState, useEffect } from 'react'
import { Outlet, Link, useLocation, useNavigate } from 'react-router-dom'
import { GlobalOutlined, AppstoreOutlined, DesktopOutlined, UserOutlined, MenuOutlined, SettingOutlined } from '@ant-design/icons'
import { Button } from 'antd'
import { isAuthenticated, removeToken } from '../lib/utils'

interface NavigationItem {
  name: string
  cn: string
  href: string
  icon: React.ComponentType<any>
  children?: NavigationItem[]
}

const Layout: React.FC = () => {
  const location = useLocation()
  const navigate = useNavigate()
  const [sidebarCollapsed, setSidebarCollapsed] = useState<boolean>(false)
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false)

  useEffect(() => {
    // 检查 cookie 中的 token 来判断登录状态
    const checkAuthStatus = () => {
      const hasToken = isAuthenticated()
      setIsLoggedIn(hasToken)
    }
    
    checkAuthStatus()
    // 监听 storage 变化(当其他标签页登录/登出时)
    window.addEventListener('storage', checkAuthStatus)
    
    return () => {
      window.removeEventListener('storage', checkAuthStatus)
    }
  }, [])

  useEffect(() => {
    // 进入详情页自动折叠侧边栏
    if (location.pathname.startsWith('/portals/detail') || location.pathname.startsWith('/api-products/detail')) {
      setSidebarCollapsed(true)
    } else {
      setSidebarCollapsed(false)
    }
  }, [location.pathname])

  const navigation: NavigationItem[] = [
    { name: 'Portal', cn: '门户', href: '/portals', icon: GlobalOutlined },
    { name: 'API Products', cn: 'API产品', href: '/api-products', icon: AppstoreOutlined },
    { 
      name: '实例管理', 
      cn: '实例管理', 
      href: '/consoles', 
      icon: SettingOutlined,
      children: [
        { name: 'Nacos实例', cn: 'Nacos实例', href: '/consoles/nacos', icon: DesktopOutlined },
        { name: '网关实例', cn: '网关实例', href: '/consoles/gateway', icon: DesktopOutlined },
      ]
    },
  ]

  const toggleSidebar = () => {
    setSidebarCollapsed(!sidebarCollapsed)
  }

  const handleLogout = () => {
    removeToken()
    setIsLoggedIn(false)
    navigate('/login')
  }

  const isMenuActive = (item: NavigationItem): boolean => {
    if (location.pathname === item.href) return true
    if (item.children) {
      return item.children.some(child => location.pathname === child.href)
    }
    return false
  }

  const renderMenuItem = (item: NavigationItem, level: number = 0) => {
    const Icon = item.icon
    const isActive = isMenuActive(item)
    const hasChildren = item.children && item.children.length > 0

    return (
      <div key={item.name}>
          <Link
            to={item.href}
            className={`flex items-center mt-2 px-3 py-3 rounded-lg transition-colors duration-150 ${
              level > 0 ? 'ml-4' : ''
            } ${
              isActive && !hasChildren
                ? 'bg-gray-100 text-black font-semibold'
                : 'text-gray-500 hover:text-black hover:bg-gray-50'
            }`}
            title={sidebarCollapsed ? item.name : ''}
          >
            <Icon className="mr-3 h-5 w-5 flex-shrink-0" />
            {!sidebarCollapsed && (
              <div className="flex flex-col flex-1">
                <span className="text-base leading-none">{item.name}</span>
              </div>
            )}
          </Link>
        {!sidebarCollapsed && hasChildren && (
          <div className="ml-2">
            {item.children!.map(child => renderMenuItem(child, level + 1))}
          </div>
        )}
      </div>
    )
  }

  return (
    <div className="min-h-screen bg-background">
      {/* 顶部导航栏 */}
      <header className="w-full h-16 flex items-center justify-between px-8 bg-white border-b shadow-sm">
        <div className="flex items-center space-x-2">
        <div className="bg-white">
              <Button
                type="text"
                icon={<MenuOutlined />}
                onClick={toggleSidebar}
                className="hover:bg-gray-100"
              />
            </div>
          <span className="text-2xl font-bold">HiMarket</span>
        </div>
        {/* 顶部右侧用户信息或登录按钮 */}
        {isLoggedIn ? (
          <div className="flex items-center space-x-2">
            <UserOutlined className="mr-2 text-lg" />
            <span>admin</span>
            <button
              onClick={handleLogout}
              className="ml-2 px-2 py-1 rounded bg-gray-200 hover:bg-gray-300"
            >
              退出
            </button>
          </div>
        ) : (
          <button onClick={() => navigate('/login')} className="flex items-center px-4 py-2 rounded bg-black text-white hover:bg-gray-800">
            <UserOutlined className="mr-2" /> 登录
          </button>
        )}
      </header>
      <div className="flex">
        {/* 侧边栏 */}
        <aside className={`bg-white border-r min-h-screen pt-8 transition-all duration-300 ${
          sidebarCollapsed ? 'w-16' : 'w-64'
        }`}>
          <nav className="flex flex-col space-y-2 px-4">
            {navigation.map(item => renderMenuItem(item))}
          </nav>
        </aside>

        {/* 主内容区域 */}
        <div className="flex-1 min-h-screen overflow-hidden">
          <main className="p-8 w-full max-w-full overflow-x-hidden">
            <Outlet />
          </main>
        </div>
      </div>
    </div>
  )
}

export default Layout

```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/components/SwaggerUIWrapper.tsx:
--------------------------------------------------------------------------------

```typescript
import React from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import './SwaggerUIWrapper.css';
import * as yaml from 'js-yaml';
import { message } from 'antd';

interface SwaggerUIWrapperProps {
  apiSpec: string;
}

export const SwaggerUIWrapper: React.FC<SwaggerUIWrapperProps> = ({ apiSpec }) => {
  // 直接解析原始规范,不进行重新构建
  let swaggerSpec: any;
  
  try {
    // 尝试解析YAML格式
    try {
      swaggerSpec = yaml.load(apiSpec);
    } catch {
      // 如果YAML解析失败,尝试JSON格式
      swaggerSpec = JSON.parse(apiSpec);
    }

    if (!swaggerSpec || !swaggerSpec.paths) {
      throw new Error('Invalid OpenAPI specification');
    }

    // 为没有tags的操作添加默认标签,避免显示"default"
    Object.keys(swaggerSpec.paths).forEach(path => {
      const pathItem = swaggerSpec.paths[path];
      Object.keys(pathItem).forEach(method => {
        const operation = pathItem[method];
        if (operation && typeof operation === 'object' && !operation.tags) {
          operation.tags = ['接口列表'];
        }
      });
    });
  } catch (error) {
    console.error('OpenAPI规范解析失败:', error);
    return (
      <div className="text-center text-gray-500 py-8 bg-gray-50 rounded-lg">
        <p>无法解析OpenAPI规范</p>
        <div className="text-sm text-gray-400 mt-2">
          请检查API配置格式是否正确
        </div>
        <div className="text-xs text-gray-400 mt-1">
          错误详情: {error instanceof Error ? error.message : String(error)}
        </div>
      </div>
    );
  }

  return (
    <div className="swagger-ui-wrapper">
      <SwaggerUI
        spec={swaggerSpec}
        docExpansion="list"
        displayRequestDuration={true}
        tryItOutEnabled={true}
        filter={false}
        defaultModelsExpandDepth={0}
        defaultModelExpandDepth={0}
        displayOperationId={true}
        supportedSubmitMethods={['get', 'post', 'put', 'delete', 'patch', 'head', 'options']}
        deepLinking={false}
        requestInterceptor={(request: any) => {
          console.log('Request:', request);
          return request;
        }}
        responseInterceptor={(response: any) => {
          console.log('Response:', response);
          return response;
        }}
        onComplete={() => {
          console.log('Swagger UI loaded');
          // 添加服务器复制功能 - 使用requestAnimationFrame优化性能
          const addCopyButton = () => {
            const serversContainer = document.querySelector('.swagger-ui .servers');
            if (serversContainer && !serversContainer.querySelector('.copy-server-btn')) {
              const copyBtn = document.createElement('button');
              copyBtn.className = 'copy-server-btn';
              copyBtn.innerHTML = `
                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
                </svg>
              `;
              copyBtn.title = '复制服务器地址';
              copyBtn.style.cssText = `
                position: absolute;
                right: 12px;
                top: 50%;
                transform: translateY(-50%);
                background: transparent;
                border: none;
                border-radius: 4px;
                padding: 6px 8px;
                cursor: pointer;
                color: #666;
                transition: all 0.2s;
                z-index: 10;
                display: flex;
                align-items: center;
                justify-content: center;
              `;
              
              // 添加hover效果
              copyBtn.addEventListener('mouseenter', () => {
                copyBtn.style.background = '#f5f5f5';
                copyBtn.style.color = '#1890ff';
              });
              
              copyBtn.addEventListener('mouseleave', () => {
                copyBtn.style.background = 'transparent';
                copyBtn.style.color = '#666';
              });
              
              copyBtn.addEventListener('click', () => {
                const serverSelect = serversContainer.querySelector('select') as HTMLSelectElement;
                if (serverSelect && serverSelect.value) {
                  navigator.clipboard.writeText(serverSelect.value)
                    .then(() => {
                      message.success('服务器地址已复制到剪贴板', 1);
                    })
                    .catch(() => {
                      // 降级到传统复制方法
                      const textArea = document.createElement('textarea');
                      textArea.value = serverSelect.value;
                      document.body.appendChild(textArea);
                      textArea.select();
                      document.execCommand('copy');
                      document.body.removeChild(textArea);
                      message.success('服务器地址已复制到剪贴板', 1);
                    });
                }
              });


              serversContainer.appendChild(copyBtn);
              
              // 调整服务器选择框的padding
              const serverSelect = serversContainer.querySelector('select') as HTMLSelectElement;
              if (serverSelect) {
                serverSelect.style.paddingRight = '50px';
              }
            }
          };
          
          // 立即尝试添加按钮,如果失败则在下一帧重试
          addCopyButton();
          if (!document.querySelector('.swagger-ui .servers .copy-server-btn')) {
            requestAnimationFrame(addCopyButton);
          }
        }}
      />
    </div>
  );
};

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/api-product/SwaggerUIWrapper.tsx:
--------------------------------------------------------------------------------

```typescript
import React from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import './SwaggerUIWrapper.css';
import * as yaml from 'js-yaml';
import { message } from 'antd';
import { copyToClipboard } from '@/lib/utils';

interface SwaggerUIWrapperProps {
  apiSpec: string;
}

export const SwaggerUIWrapper: React.FC<SwaggerUIWrapperProps> = ({ apiSpec }) => {
  // 直接解析原始规范,不进行重新构建
  let swaggerSpec: any;
  
  try {
    // 尝试解析YAML格式
    try {
      swaggerSpec = yaml.load(apiSpec);
    } catch {
      // 如果YAML解析失败,尝试JSON格式
      swaggerSpec = JSON.parse(apiSpec);
    }

    if (!swaggerSpec || !swaggerSpec.paths) {
      throw new Error('Invalid OpenAPI specification');
    }

    // 为没有tags的操作添加默认标签,避免显示"default"
    Object.keys(swaggerSpec.paths).forEach(path => {
      const pathItem = swaggerSpec.paths[path];
      Object.keys(pathItem).forEach(method => {
        const operation = pathItem[method];
        if (operation && typeof operation === 'object' && !operation.tags) {
          operation.tags = ['接口列表'];
        }
      });
    });
  } catch (error) {
    console.error('OpenAPI规范解析失败:', error);
    return (
      <div className="text-center text-gray-500 py-8 bg-gray-50 rounded-lg">
        <p>无法解析OpenAPI规范</p>
        <div className="text-sm text-gray-400 mt-2">
          请检查API配置格式是否正确
        </div>
        <div className="text-xs text-gray-400 mt-1">
          错误详情: {error instanceof Error ? error.message : String(error)}
        </div>
      </div>
    );
  }

  return (
    <div className="swagger-ui-wrapper">
      <SwaggerUI
        spec={swaggerSpec}
        docExpansion="list"
        displayRequestDuration={true}
        tryItOutEnabled={true}
        filter={false}
        showRequestHeaders={true}
        showCommonExtensions={true}
        defaultModelsExpandDepth={0}
        defaultModelExpandDepth={0}
        displayOperationId={true}
        enableCORS={true}
        supportedSubmitMethods={['get', 'post', 'put', 'delete', 'patch', 'head', 'options']}
        deepLinking={false}
        showMutatedRequest={true}
        requestInterceptor={(request: any) => {
          console.log('Request:', request);
          return request;
        }}
        responseInterceptor={(response: any) => {
          console.log('Response:', response);
          return response;
        }}
        onComplete={() => {
          console.log('Swagger UI loaded');
          // 添加服务器复制功能
          setTimeout(() => {
            const serversContainer = document.querySelector('.swagger-ui .servers');
            if (serversContainer && !serversContainer.querySelector('.copy-server-btn')) {
              const copyBtn = document.createElement('button');
              copyBtn.className = 'copy-server-btn';
              copyBtn.innerHTML = `
                <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
                  <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
                </svg>
              `;
              copyBtn.title = '复制服务器地址';
              copyBtn.style.cssText = `
                position: absolute;
                right: 12px;
                top: 50%;
                transform: translateY(-50%);
                background: transparent;
                border: none;
                border-radius: 4px;
                padding: 6px 8px;
                cursor: pointer;
                color: #666;
                transition: all 0.2s;
                z-index: 10;
                display: flex;
                align-items: center;
                justify-content: center;
              `;
              
              copyBtn.addEventListener('click', async () => {
                const serverSelect = serversContainer.querySelector('select') as HTMLSelectElement;
                if (serverSelect && serverSelect.value) {
                  try {
                    await copyToClipboard(serverSelect.value);
                    message.success('服务器地址已复制到剪贴板', 1);
                  } catch {
                    message.error('复制失败,请手动复制');
                  }
                }
              });

              // 添加hover效果
              copyBtn.addEventListener('mouseenter', () => {
                copyBtn.style.background = '#f5f5f5';
                copyBtn.style.color = '#1890ff';
              });
              
              copyBtn.addEventListener('mouseleave', () => {
                copyBtn.style.background = 'transparent';
                copyBtn.style.color = '#666';
              });

              serversContainer.appendChild(copyBtn);
              
              // 调整服务器选择框的padding
              const serverSelect = serversContainer.querySelector('select') as HTMLSelectElement;
              if (serverSelect) {
                serverSelect.style.paddingRight = '50px';
              }
            }
          }, 1000);
        }}
        syntaxHighlight={{
          activated: true,
          theme: 'agate'
        }}
        requestSnippetsEnabled={true}
        requestSnippets={{
          generators: {
            'curl_bash': {
              title: 'cURL (bash)',
              syntax: 'bash'
            },
            'curl_powershell': {
              title: 'cURL (PowerShell)',
              syntax: 'powershell'
            },
            'curl_cmd': {
              title: 'cURL (CMD)',
              syntax: 'bash'
            }
          }
        }}
      />
    </div>
  );
};

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/pages/Dashboard.tsx:
--------------------------------------------------------------------------------

```typescript
import { Card, Row, Col, Statistic, Progress, Table } from 'antd'
import { 
  EyeOutlined, 
  UserOutlined, 
  ApiOutlined,
  GlobalOutlined,
  ArrowUpOutlined,
  ArrowDownOutlined
} from '@ant-design/icons'

const mockRecentActivity = [
  {
    key: '1',
    action: 'Portal访问',
    description: 'Company Portal被访问了1250次',
    time: '2小时前',
    user: '[email protected]'
  },
  {
    key: '2',
    action: 'API调用',
    description: 'Payment API被调用了8765次',
    time: '4小时前',
    user: '[email protected]'
  },
  {
    key: '3',
    action: '新用户注册',
    description: '新开发者注册了账户',
    time: '6小时前',
    user: '[email protected]'
  }
]

export default function Dashboard() {
  const activityColumns = [
    {
      title: '操作',
      dataIndex: 'action',
      key: 'action',
    },
    {
      title: '描述',
      dataIndex: 'description',
      key: 'description',
    },
    {
      title: '用户',
      dataIndex: 'user',
      key: 'user',
    },
    {
      title: '时间',
      dataIndex: 'time',
      key: 'time',
    },
  ]

  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-3xl font-bold tracking-tight">仪表板</h1>
        <p className="text-gray-500 mt-2">
          欢迎使用HiMarket管理系统
        </p>
      </div>

      {/* 统计卡片 */}
      <Row gutter={[16, 16]}>
        <Col xs={24} sm={12} lg={6}>
          <Card>
            <Statistic
              title="Portal访问量"
              value={1250}
              prefix={<EyeOutlined />}
              valueStyle={{ color: '#3f8600' }}
              suffix={<ArrowUpOutlined style={{ fontSize: '14px' }} />}
            />
          </Card>
        </Col>
        <Col xs={24} sm={12} lg={6}>
          <Card>
            <Statistic
              title="注册用户"
              value={45}
              prefix={<UserOutlined />}
              valueStyle={{ color: '#1890ff' }}
              suffix={<ArrowUpOutlined style={{ fontSize: '14px' }} />}
            />
          </Card>
        </Col>
        <Col xs={24} sm={12} lg={6}>
          <Card>
            <Statistic
              title="API调用"
              value={8765}
              prefix={<ApiOutlined />}
              valueStyle={{ color: '#722ed1' }}
              suffix={<ArrowUpOutlined style={{ fontSize: '14px' }} />}
            />
          </Card>
        </Col>
        <Col xs={24} sm={12} lg={6}>
          <Card>
            <Statistic
              title="活跃Portal"
              value={3}
              prefix={<GlobalOutlined />}
              valueStyle={{ color: '#fa8c16' }}
              suffix={<ArrowDownOutlined style={{ fontSize: '14px' }} />}
            />
          </Card>
        </Col>
      </Row>

      {/* 详细信息 */}
      <Row gutter={[16, 16]}>
        <Col xs={24} lg={12}>
          <Card title="系统状态" className="h-full">
            <div className="space-y-4">
              <div>
                <div className="flex justify-between mb-2">
                  <span>系统负载</span>
                  <span className="text-blue-600">75%</span>
                </div>
                <Progress percent={75} strokeColor="#1890ff" />
              </div>
              <div>
                <div className="flex justify-between mb-2">
                  <span>API响应时间</span>
                  <span className="text-green-600">245ms</span>
                </div>
                <Progress percent={85} strokeColor="#52c41a" />
              </div>
              <div>
                <div className="flex justify-between mb-2">
                  <span>错误率</span>
                  <span className="text-red-600">0.12%</span>
                </div>
                <Progress percent={1.2} strokeColor="#ff4d4f" />
              </div>
            </div>
          </Card>
        </Col>
        <Col xs={24} lg={12}>
          <Card title="快速操作" className="h-full">
            <div className="space-y-4">
              <div className="grid grid-cols-2 gap-4">
                <div className="text-center p-4 border rounded-lg hover:bg-gray-50 cursor-pointer">
                  <GlobalOutlined className="text-2xl text-blue-500 mb-2" />
                  <div className="font-medium">创建Portal</div>
                  <div className="text-sm text-gray-500">新建开发者门户</div>
                </div>
                <div className="text-center p-4 border rounded-lg hover:bg-gray-50 cursor-pointer">
                  <ApiOutlined className="text-2xl text-green-500 mb-2" />
                  <div className="font-medium">发布API</div>
                  <div className="text-sm text-gray-500">发布新的API产品</div>
                </div>
                <div className="text-center p-4 border rounded-lg hover:bg-gray-50 cursor-pointer">
                  <UserOutlined className="text-2xl text-purple-500 mb-2" />
                  <div className="font-medium">管理用户</div>
                  <div className="text-sm text-gray-500">管理开发者账户</div>
                </div>
                <div className="text-center p-4 border rounded-lg hover:bg-gray-50 cursor-pointer">
                  <EyeOutlined className="text-2xl text-orange-500 mb-2" />
                  <div className="font-medium">查看统计</div>
                  <div className="text-sm text-gray-500">查看使用统计</div>
                </div>
              </div>
            </div>
          </Card>
        </Col>
      </Row>

      {/* 最近活动 */}
      <Card title="最近活动">
        <Table 
          columns={activityColumns} 
          dataSource={mockRecentActivity}
          rowKey="key"
          pagination={false}
          size="small"
        />
      </Card>
    </div>
  )
} 
```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/portal/PortalDomain.tsx:
--------------------------------------------------------------------------------

```typescript
import {Card, Button, Table, Modal, Form, Input, Select, message, Space} from 'antd'
import {PlusOutlined, ExclamationCircleOutlined} from '@ant-design/icons'
import {useState} from 'react'
import {Portal} from '@/types'
import {portalApi} from '@/lib/api'

interface PortalDomainProps {
    portal: Portal
    onRefresh?: () => void
}

export function PortalDomain({portal, onRefresh}: PortalDomainProps) {
    const [domainModalVisible, setDomainModalVisible] = useState(false)
    const [domainForm] = Form.useForm()
    const [domainLoading, setDomainLoading] = useState(false)

    const handleAddDomain = () => {
        setDomainModalVisible(true)
    }

    const handleDomainModalOk = async () => {
        try {
            setDomainLoading(true)
            const values = await domainForm.validateFields()
            
            await portalApi.bindDomain(portal.portalId, {
                domain: values.domain,
                type: 'CUSTOM',
                protocol: values.protocol
            })
            
            message.success('域名绑定成功')
            setDomainModalVisible(false)
            domainForm.resetFields()
            onRefresh?.()
        } catch (error) {
            message.error('绑定域名失败')
        } finally {
            setDomainLoading(false)
        }
    }

    const handleDomainModalCancel = () => {
        setDomainModalVisible(false)
        domainForm.resetFields()
    }

    const handleDeleteDomain = async (domain: string) => {
        Modal.confirm({
            title: '确认解绑',
            icon: <ExclamationCircleOutlined/>,
            content: `确定要解绑域名 "${domain}" 吗?此操作不可恢复。`,
            okText: '确认解绑',
            okType: 'danger',
            cancelText: '取消',
            async onOk() {
                try {
                    await portalApi.unbindDomain(portal.portalId, domain)
                    message.success('域名解绑成功')
                    onRefresh?.()
                } catch (error) {
                    message.error('解绑域名失败')
                }
            },
        })
    }

    const domainColumns = [
        {
            title: '域名',
            dataIndex: 'domain',
            key: 'domain',
        },
        {
            title: '协议',
            dataIndex: 'protocol',
            key: 'protocol',
            render: (protocol: string) => protocol?.toUpperCase() || 'HTTP'
        },
        {
            title: '类型',
            dataIndex: 'type',
            key: 'type',
            render: (type: string) => type === 'CUSTOM' ? '自定义域名' : '系统域名'
        },
        {
            title: '操作',
            key: 'action',
            render: (_: any, record: any) => (
                <Space>
                    {record.type === 'CUSTOM' ? (
                        <Button 
                            type="link" 
                            danger 
                            size="small"
                            onClick={() => handleDeleteDomain(record.domain)}
                        >
                            解绑
                        </Button>
                    ) : (
                        <span className="text-gray-400 text-sm">-</span>
                    )}
                </Space>
            ),
        },
    ]

    return (
        <div className="p-6 space-y-6">
            <div className="flex justify-between items-center">
                <div>
                    <h1 className="text-2xl font-bold mb-2">域名列表</h1>
                    <p className="text-gray-600">管理Portal的域名配置</p>
                </div>
                <Space>
                    <Button type="primary" icon={<PlusOutlined/>} onClick={handleAddDomain}>
                        绑定域名
                    </Button>
                </Space>
            </div>

            <Card>
                <div className="space-y-6">
                    {/* 域名列表内容 */}
                    <div>
                        <Table
                            columns={domainColumns}
                            dataSource={portal.portalDomainConfig || []}
                            rowKey="domain"
                            pagination={false}
                            size="small"
                            locale={{
                                emptyText: '暂无绑定域名'
                            }}
                        />
                    </div>
                </div>
            </Card>

            {/* 域名绑定模态框 */}
            <Modal
                title="绑定域名"
                open={domainModalVisible}
                onOk={handleDomainModalOk}
                onCancel={handleDomainModalCancel}
                confirmLoading={domainLoading}
                destroyOnClose
            >
                <Form form={domainForm} layout="vertical" initialValues={{ protocol: 'HTTP' }}>
                    <Form.Item
                        name="domain"
                        label="域名"
                        rules={[{ required: true, message: '请输入要绑定的域名' }]}
                    >
                        <Input placeholder="例如:example.com" />
                    </Form.Item>
                    
                    <Form.Item
                        name="protocol"
                        label="协议"
                        rules={[{ required: true, message: '请选择协议' }]}
                    >
                        <Select placeholder="请选择协议">
                            <Select.Option value="HTTPS">HTTPS</Select.Option>
                            <Select.Option value="HTTP">HTTP</Select.Option>
                        </Select>
                    </Form.Item>
                </Form>
            </Modal>
        </div>
    )
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/pages/Login.tsx:
--------------------------------------------------------------------------------

```typescript
import React, { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { Form, Input, Button, Card, Divider, message } from "antd";
import { UserOutlined, LockOutlined } from "@ant-design/icons";
import api, { getOidcProviders, type IdpResult } from "../lib/api";
import aliyunIcon from "../assets/aliyun.png";
import githubIcon from "../assets/github.png";
import googleIcon from "../assets/google.png";
import { AxiosError } from "axios";


const oidcIcons: Record<string, React.ReactNode> = {
  google: <img src={googleIcon} alt="Google" className="w-5 h-5 mr-2" />,
  github: <img src={githubIcon} alt="GitHub" className="w-6 h-6 mr-2" />,
  aliyun: <img src={aliyunIcon} alt="Aliyun" className="w-6 h-6 mr-2" />,
};

const Login: React.FC = () => {
  const [providers, setProviders] = useState<IdpResult[]>([]);
  const [loading, setLoading] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    // 使用OidcController的接口获取OIDC提供商
    getOidcProviders()
      .then((response: any) => {
        console.log('OIDC providers response:', response);
        
        // 处理不同的响应格式
        let providersData: IdpResult[];
        if (Array.isArray(response)) {
          providersData = response;
        } else if (response && Array.isArray(response.data)) {
          providersData = response.data;
        } else if (response && response.data) {
          console.warn('Unexpected response format:', response);
          providersData = [];
        } else {
          providersData = [];
        }
        
        console.log('Processed providers data:', providersData);
        setProviders(providersData);
      })
      .catch((error) => {
        console.error('Failed to fetch OIDC providers:', error);
        setProviders([]);
      });
  }, []);

  // 账号密码登录
  const handlePasswordLogin = async (values: { username: string; password: string }) => {
    setLoading(true);
    try {
      const res = await api.post("/developers/login", {
        username: values.username,
        password: values.password,
      });
      // 登录成功后跳转到首页并携带access_token
      if (res && res.data && res.data.access_token) {
        message.success('登录成功!', 1);
        localStorage.setItem('access_token', res.data.access_token)
        navigate('/')
      } else {
        message.error("登录失败,未获取到access_token");
      }
    } catch (error) {
      if (error instanceof AxiosError) {
        message.error(error.response?.data.message || "登录失败,请检查账号密码是否正确");
      } else {
        message.error("登录失败");
      }
    } finally {
      setLoading(false);
    }
  };

  // 跳转到 OIDC 授权 - 对接OidcController
  const handleOidcLogin = (provider: string) => {
    // 获取API前缀配置
    const apiPrefix = api.defaults.baseURL || '/api/v1';
    
    // 构建授权URL - 对接 /developers/oidc/authorize
    const authUrl = new URL(`${window.location.origin}${apiPrefix}/developers/oidc/authorize`);
    authUrl.searchParams.set('provider', provider);
    
    console.log('Redirecting to OIDC authorization:', authUrl.toString());
    
    // 跳转到OIDC授权服务器
    window.location.href = authUrl.toString();
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-gray-50">
      <Card className="w-full max-w-md shadow-lg">
        {/* Logo */}
        <div className="text-center mb-6">
          <img src="/logo.png" alt="Logo" className="w-16 h-16 mx-auto mb-4" />
          <h2 className="text-2xl font-bold text-gray-900">登录HiMarket-前台</h2>
        </div>

        {/* 账号密码登录表单 */}
        <Form
          name="login"
          onFinish={handlePasswordLogin}
          autoComplete="off"
          layout="vertical"
          size="large"
        >
          <Form.Item
            name="username"
            rules={[
              { required: true, message: '请输入账号' }
            ]}
          >
            <Input
              prefix={<UserOutlined />}
              placeholder="账号"
              autoComplete="username"
            />
          </Form.Item>

          <Form.Item
            name="password"
            rules={[
              { required: true, message: '请输入密码' }
            ]}
          >
            <Input.Password
              prefix={<LockOutlined />}
              placeholder="密码"
              autoComplete="current-password"
            />
          </Form.Item>

          <Form.Item>
            <Button
              type="primary"
              htmlType="submit"
              loading={loading}
              className="w-full"
              size="large"
            >
              {loading ? "登录中..." : "登录"}
            </Button>
          </Form.Item>
        </Form>

        {/* 分隔线 */}
        <Divider plain>或</Divider>

        {/* OIDC 登录按钮 */}
        <div className="flex flex-col gap-3">
          {!Array.isArray(providers) || providers.length === 0 ? (
            <div className="text-gray-400 text-center">暂无可用第三方登录</div>
          ) : (
            providers.map((provider) => (
              <Button
                key={provider.provider}
                onClick={() => handleOidcLogin(provider.provider)}
                className="w-full flex items-center justify-center"
                size="large"
                icon={oidcIcons[provider.provider.toLowerCase()] || <span>🆔</span>}
              >
                使用{provider.name || provider.provider}登录
              </Button>
            ))
          )}
        </div>

        {/* 底部提示 */}
        <div className="mt-6 text-center text-gray-500">
          没有账号?
          <Link to="/register" className="text-blue-500 hover:underline ml-1">
            注册
          </Link>
        </div>
      </Card>
    </div>
  );
};

export default Login;

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/portal/PortalConsumers.tsx:
--------------------------------------------------------------------------------

```typescript
import { Card, Table, Badge, Button, Space, Avatar, Tag, Input } from 'antd'
import { SearchOutlined, UserAddOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
import { useState } from 'react'
import { Portal, DeveloperStats } from '@/types'
import { formatDateTime } from '@/lib/utils'

interface PortalConsumersProps {
  portal: Portal
}

const mockConsumers: DeveloperStats[] = [
  {
    id: "1",
    name: "企业A",
    email: "[email protected]",
    status: "active",
    plan: "premium",
    joinedAt: "2025-01-01T10:00:00Z",
    lastActive: "2025-01-08T15:30:00Z",
    apiCalls: 15420,
    subscriptions: 3
  },
  {
    id: "2",
    name: "企业B",
    email: "[email protected]",
    status: "active",
    plan: "standard",
    joinedAt: "2025-01-02T11:00:00Z",
    lastActive: "2025-01-08T14:20:00Z",
    apiCalls: 8765,
    subscriptions: 2
  },
  {
    id: "3",
    name: "企业C",
    email: "[email protected]",
    status: "inactive",
    plan: "basic",
    joinedAt: "2025-01-03T12:00:00Z",
    lastActive: "2025-01-05T09:15:00Z",
    apiCalls: 1200,
    subscriptions: 1
  }
]

export function PortalConsumers({ portal }: PortalConsumersProps) {
  const [consumers, setConsumers] = useState<DeveloperStats[]>(mockConsumers)
  const [searchText, setSearchText] = useState('')

  const filteredConsumers = consumers.filter(consumer =>
    consumer.name.toLowerCase().includes(searchText.toLowerCase()) ||
    consumer.email.toLowerCase().includes(searchText.toLowerCase())
  )

  const getPlanColor = (plan: string) => {
    switch (plan) {
      case 'premium':
        return 'gold'
      case 'standard':
        return 'blue'
      case 'basic':
        return 'green'
      default:
        return 'default'
    }
  }

  const getPlanText = (plan: string) => {
    switch (plan) {
      case 'premium':
        return '高级版'
      case 'standard':
        return '标准版'
      case 'basic':
        return '基础版'
      default:
        return plan
    }
  }

  const columns = [
    {
      title: '消费者',
      dataIndex: 'name',
      key: 'name',
      render: (name: string, record: DeveloperStats) => (
        <div className="flex items-center space-x-3">
          <Avatar className="bg-green-500">
            {name.charAt(0).toUpperCase()}
          </Avatar>
          <div>
            <div className="font-medium">{name}</div>
            <div className="text-sm text-gray-500">{record.email}</div>
          </div>
        </div>
      ),
    },
    {
      title: '状态',
      dataIndex: 'status',
      key: 'status',
      render: (status: string) => (
        <Badge status={status === 'active' ? 'success' : 'default'} text={status === 'active' ? '活跃' : '非活跃'} />
      )
    },
    {
      title: '套餐',
      dataIndex: 'plan',
      key: 'plan',
      render: (plan: string) => (
        <Tag color={getPlanColor(plan)}>
          {getPlanText(plan)}
        </Tag>
      )
    },
    {
      title: 'API调用',
      dataIndex: 'apiCalls',
      key: 'apiCalls',
      render: (calls: number) => calls.toLocaleString()
    },
    {
      title: '订阅数',
      dataIndex: 'subscriptions',
      key: 'subscriptions',
      render: (subscriptions: number) => subscriptions.toLocaleString()
    },
    {
      title: '加入时间',
      dataIndex: 'joinedAt',
      key: 'joinedAt',
      render: (date: string) => formatDateTime(date)
    },
    {
      title: '最后活跃',
      dataIndex: 'lastActive',
      key: 'lastActive',
      render: (date: string) => formatDateTime(date)
    },
    {
      title: '操作',
      key: 'action',
      render: (_: any, record: DeveloperStats) => (
        <Space size="middle">
          <Button type="link" icon={<EditOutlined />}>
            编辑
          </Button>
          <Button type="link" danger icon={<DeleteOutlined />}>
            删除
          </Button>
        </Space>
      ),
    },
  ]

  return (
    <div className="p-6 space-y-6">
      <div className="flex justify-between items-center">
        <div>
          <h1 className="text-2xl font-bold mb-2">消费者</h1>
          <p className="text-gray-600">管理Portal的消费者用户</p>
        </div>
        <Button type="primary" icon={<UserAddOutlined />}>
          添加消费者
        </Button>
      </div>

      <Card>
        <div className="mb-4">
          <Input
            placeholder="搜索消费者..."
            prefix={<SearchOutlined />}
            value={searchText}
            onChange={(e) => setSearchText(e.target.value)}
            style={{ width: 300 }}
          />
        </div>
        <Table 
          columns={columns} 
          dataSource={filteredConsumers}
          rowKey="id"
          pagination={false}
        />
      </Card>

      {/* <Card title="消费者统计">
        <div className="grid grid-cols-4 gap-4">
          <div className="text-center">
            <div className="text-2xl font-bold text-blue-600">{consumers.length}</div>
            <div className="text-sm text-gray-500">总消费者</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-green-600">
              {consumers.filter(c => c.status === 'active').length}
            </div>
            <div className="text-sm text-gray-500">活跃消费者</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-purple-600">
              {consumers.reduce((sum, c) => sum + c.apiCalls, 0).toLocaleString()}
            </div>
            <div className="text-sm text-gray-500">总API调用</div>
          </div>
          <div className="text-center">
            <div className="text-2xl font-bold text-orange-600">
              {consumers.reduce((sum, c) => sum + c.subscriptions, 0)}
            </div>
            <div className="text-sm text-gray-500">总订阅数</div>
          </div>
        </div>
      </Card> */}
    </div>
  )
} 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/controller/ConsumerController.java:
--------------------------------------------------------------------------------

```java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
import com.alibaba.apiopenplatform.core.annotation.DeveloperAuth;
import com.alibaba.apiopenplatform.core.annotation.AdminOrDeveloperAuth;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateCredentialParam;
import com.alibaba.apiopenplatform.dto.params.consumer.QueryConsumerParam;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateConsumerParam;
import com.alibaba.apiopenplatform.dto.params.consumer.UpdateCredentialParam;
import com.alibaba.apiopenplatform.dto.params.consumer.CreateSubscriptionParam;
import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
import com.alibaba.apiopenplatform.dto.result.ConsumerCredentialResult;
import com.alibaba.apiopenplatform.dto.result.ConsumerResult;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
import com.alibaba.apiopenplatform.service.ConsumerService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@Tag(name = "Consumer管理", description = "提供Consumer注册、审批、产品订阅等管理功能")
@RestController
@RequestMapping("/consumers")
@RequiredArgsConstructor
@Validated
public class ConsumerController {

    private final ConsumerService consumerService;

    @Operation(summary = "获取Consumer列表")
    @GetMapping
    public PageResult<ConsumerResult> listConsumers(QueryConsumerParam param,
                                                    Pageable pageable) {
        return consumerService.listConsumers(param, pageable);
    }

    @Operation(summary = "获取Consumer")
    @GetMapping("/{consumerId}")
    public ConsumerResult getConsumer(@PathVariable String consumerId) {
        return consumerService.getConsumer(consumerId);
    }

    @Operation(summary = "注册Consumer")
    @PostMapping
    @DeveloperAuth
    public ConsumerResult createConsumer(@RequestBody @Valid CreateConsumerParam param) {
        return consumerService.createConsumer(param);
    }

    @Operation(summary = "删除Consumer")
    @DeleteMapping("/{consumerId}")
    public void deleteDevConsumer(@PathVariable String consumerId) {
        consumerService.deleteConsumer(consumerId);
    }

    @Operation(summary = "生成Consumer凭证")
    @PostMapping("/{consumerId}/credentials")
    @DeveloperAuth
    public void createCredential(@PathVariable String consumerId,
                                 @RequestBody @Valid CreateCredentialParam param) {
        consumerService.createCredential(consumerId, param);
    }

    @Operation(summary = "获取Consumer凭证信息")
    @GetMapping("/{consumerId}/credentials")
    @DeveloperAuth
    public ConsumerCredentialResult getCredential(@PathVariable String consumerId) {
        return consumerService.getCredential(consumerId);
    }

    @Operation(summary = "更新Consumer凭证")
    @PutMapping("/{consumerId}/credentials")
    @DeveloperAuth
    public void updateCredential(@PathVariable String consumerId,
                                 @RequestBody @Valid UpdateCredentialParam param) {
        consumerService.updateCredential(consumerId, param);
    }

    @Operation(summary = "删除Consumer凭证")
    @DeleteMapping("/{consumerId}/credentials")
    @DeveloperAuth
    public void deleteCredential(@PathVariable String consumerId) {
        consumerService.deleteCredential(consumerId);
    }

    @Operation(summary = "订阅API产品")
    @PostMapping("/{consumerId}/subscriptions")
    @DeveloperAuth
    public SubscriptionResult subscribeProduct(@PathVariable String consumerId,
                                               @RequestBody @Valid CreateSubscriptionParam param) {
        return consumerService.subscribeProduct(consumerId, param);
    }

    @Operation(summary = "获取Consumer的订阅列表")
    @GetMapping("/{consumerId}/subscriptions")
    @AdminOrDeveloperAuth
    public PageResult<SubscriptionResult> listSubscriptions(@PathVariable String consumerId,
                                                            QuerySubscriptionParam param,
                                                            Pageable pageable) {
        return consumerService.listSubscriptions(consumerId, param, pageable);
    }

    @Operation(summary = "取消订阅")
    @DeleteMapping("/{consumerId}/subscriptions/{productId}")
    public void deleteSubscription(@PathVariable String consumerId, @PathVariable String productId) {
        consumerService.unsubscribeProduct(consumerId, productId);
    }

    @Operation(summary = "审批订阅申请")
    @PatchMapping("/{consumerId}/subscriptions/{productId}")
    @AdminAuth
    public SubscriptionResult approveSubscription(@PathVariable String consumerId, @PathVariable String productId) {
        return consumerService.approveSubscription(consumerId, productId);
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/common/AdvancedSearch.tsx:
--------------------------------------------------------------------------------

```typescript
import React, { useState, useEffect } from 'react';
import { Select, Input, Button, Tag, Space } from 'antd';
import { SearchOutlined, CloseOutlined } from '@ant-design/icons';
// import './AdvancedSearch.css';

const { Option } = Select;

export interface SearchParam {
  label: string;
  name: string;
  placeholder: string;
  type?: 'input' | 'select';
  optionList?: Array<{ label: string; value: string }>;
}

interface AdvancedSearchProps {
  searchParamsList: SearchParam[];
  onSearch: (searchName: string, searchValue: string) => void;
  onClear?: () => void;
  className?: string;
}

export const AdvancedSearch: React.FC<AdvancedSearchProps> = ({
  searchParamsList,
  onSearch,
  onClear,
  className = ''
}) => {
  const [activeSearchName, setActiveSearchName] = useState<string>('');
  const [activeSearchValue, setActiveSearchValue] = useState<string>('');
  const [tagList, setTagList] = useState<Array<SearchParam & { value: string }>>([]);
  const [isInitialized, setIsInitialized] = useState<boolean>(false);

  useEffect(() => {
    // 防止初始化时自动触发搜索
    if (isInitialized && activeSearchName) {
      setActiveSearchValue(''); // 清空输入框
      setTagList([]); // 清空关联标签
      onSearch(activeSearchName, '');
    }
  }, [activeSearchName, isInitialized]); // 移除 onSearch 避免无限循环

  useEffect(() => {
    if (searchParamsList.length > 0) {
      setActiveSearchName(searchParamsList[0].name);
      setIsInitialized(true); // 标记为已初始化
    }
  }, [searchParamsList]);

  const handleSearch = () => {
    if (activeSearchValue.trim()) {
      // 添加到标签列表
      const currentParam = searchParamsList.find(item => item.name === activeSearchName);
      if (currentParam) {
        const newTag = {
          ...currentParam,
          value: activeSearchValue
        };
        setTagList(prev => {
          const filtered = prev.filter(tag => tag.name !== activeSearchName);
          return [...filtered, newTag];
        });
      }
      
      onSearch(activeSearchName, activeSearchValue);
      setActiveSearchValue('');
    }
  };

  const handleClearOne = (tagName: string) => {
    setTagList(prev => prev.filter(tag => tag.name !== tagName));
    onSearch(tagName, '');
  };

  const handleClearAll = () => {
    setTagList([]);
    if (onClear) {
      onClear();
    }
  };

  const handleSelectOne = (tagName: string) => {
    const tag = tagList.find(t => t.name === tagName);
    if (tag) {
      setActiveSearchName(tagName);
      setActiveSearchValue(tag.value);
    }
  };

  const getCurrentParam = () => {
    return searchParamsList.find(item => item.name === activeSearchName);
  };

  const currentParam = getCurrentParam();

  return (
    <div className={`flex flex-col gap-4 ${className}`}>
      {/* 搜索控件 */}
      <div className="flex items-center">
        {/* 左侧:搜索字段选择器 */}
        <Select
          value={activeSearchName}
          onChange={setActiveSearchName}
          style={{ 
            width: 120,
            borderTopRightRadius: 0,
            borderBottomRightRadius: 0,
            borderRight: 'none'
          }}
          className="h-10"
          size="large"
        >
          {searchParamsList.map(item => (
            <Option key={item.name} value={item.name}>
              {item.label}
            </Option>
          ))}
        </Select>

        {/* 中间:搜索值输入框 */}
        {currentParam?.type === 'select' ? (
          <Select
            placeholder={currentParam.placeholder}
            value={activeSearchValue}
            onChange={(value) => {
              setActiveSearchValue(value);
              // 自动触发搜索
              if (value) {
                onSearch(activeSearchName, value);
              }
            }}
            style={{ 
              width: 400,
              borderTopLeftRadius: 0,
              borderBottomLeftRadius: 0
            }}
            allowClear
            onClear={() => {
              setActiveSearchValue('');
              onClear?.();
            }}
            className="h-10"
            size="large"
          >
            {currentParam.optionList?.map(item => (
              <Option key={item.value} value={item.value}>
                {item.label}
              </Option>
            ))}
          </Select>
        ) : (
          <Input
            placeholder={currentParam?.placeholder}
            value={activeSearchValue}
            onChange={(e) => setActiveSearchValue(e.target.value)}
            style={{ 
              width: 400,
              borderTopLeftRadius: 0,
              borderBottomLeftRadius: 0
            }}
            onPressEnter={handleSearch}
            allowClear
            onClear={() => setActiveSearchValue('')}
            size="large"
            className="h-10"
            suffix={
              <Button
                type="text"
                icon={<SearchOutlined />}
                onClick={handleSearch}
                size="small"
                className="h-8 w-8 flex items-center justify-center"
              />
            }
          />
        )}
      </div>

      {/* 搜索标签 */}
      {tagList.length > 0 && (
        <div className="mt-4">
          <div className="flex items-center gap-2 mb-2">
            <span className="text-sm text-gray-500">已选择的筛选条件:</span>
            <Button
              type="link"
              size="small"
              onClick={handleClearAll}
              className="text-gray-400 hover:text-gray-600"
            >
              清除全部
            </Button>
          </div>
          <Space wrap>
            {tagList.map(tag => (
              <Tag
                key={tag.name}
                closable
                onClose={() => handleClearOne(tag.name)}
                onClick={() => handleSelectOne(tag.name)}
                className="cursor-pointer"
                color={tag.name === activeSearchName ? 'blue' : 'default'}
              >
                {tag.label}:{tag.value}
              </Tag>
            ))}
          </Space>
        </div>
      )}
    </div>
  );
};

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/subscription/SubscriptionListModal.tsx:
--------------------------------------------------------------------------------

```typescript
import { Modal, Table, Badge, message, Button, Popconfirm } from 'antd';
import { useEffect, useState } from 'react';
import { Subscription } from '@/types/subscription';
import { portalApi } from '@/lib/api';
import { formatDateTime } from '@/lib/utils';

interface SubscriptionListModalProps {
  visible: boolean;
  consumerId: string;
  consumerName: string;
  onCancel: () => void;
}

export function SubscriptionListModal({
  visible,
  consumerId,
  consumerName,
  onCancel
}: SubscriptionListModalProps) {
  const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
  const [loading, setLoading] = useState(false);
  const [actionLoading, setActionLoading] = useState<string | null>(null);
  const [pagination, setPagination] = useState({
    current: 1,
    pageSize: 10,
    total: 0,
    showSizeChanger: true,
    showQuickJumper: true,
    showTotal: (total: number) => `共 ${total} 条`
  });

  useEffect(() => {
    if (visible && consumerId) {
      fetchSubscriptions();
    }
  }, [visible, consumerId, pagination.current, pagination.pageSize]);

  const fetchSubscriptions = () => {
    setLoading(true);
    portalApi.getConsumerSubscriptions(consumerId, {
      page: pagination.current - 1, // 后端从0开始
      size: pagination.pageSize
    }).then((res) => {
      setSubscriptions(res.data.content || []);
      setPagination(prev => ({
        ...prev,
        total: res.data.totalElements || 0
      }));
    }).catch((err) => {
      message.error('获取订阅列表失败');
    }).finally(() => {
      setLoading(false);
    });
  };

  const handleTableChange = (paginationInfo: any) => {
    setPagination(prev => ({
      ...prev,
      current: paginationInfo.current,
      pageSize: paginationInfo.pageSize
    }));
  };

  const handleApproveSubscription = async (subscription: Subscription) => {
    setActionLoading(`${subscription.consumerId}-${subscription.productId}-approve`);
    try {
      await portalApi.approveSubscription(subscription.consumerId, subscription.productId);
      message.success('审批通过成功');
      fetchSubscriptions(); // 重新获取数据
    } catch (error: any) {
      const errorMessage = error.response?.data?.message || error.message || '审批失败';
      message.error(`审批失败: ${errorMessage}`);
    } finally {
      setActionLoading(null);
    }
  };

  const handleDeleteSubscription = async (subscription: Subscription) => {
    setActionLoading(`${subscription.consumerId}-${subscription.productId}-delete`);
    try {
      await portalApi.deleteSubscription(subscription.consumerId, subscription.productId);
      message.success('删除订阅成功');
      fetchSubscriptions(); // 重新获取数据
    } catch (error: any) {
      const errorMessage = error.response?.data?.message || error.message || '删除订阅失败';
      message.error(`删除订阅失败: ${errorMessage}`);
    } finally {
      setActionLoading(null);
    }
  };

  const columns = [
    {
      title: '产品名称',
      dataIndex: 'productName',
      key: 'productName',
      render: (productName: string) => (
        <div>
          <div className="font-medium">{productName || '未知产品'}</div>
        </div>
      )
    },
    {
      title: '产品类型',
      dataIndex: 'productType',
      key: 'productType',
      render: (productType: string) => (
        <Badge 
          color={productType === 'REST_API' ? 'blue' : 'purple'} 
          text={productType === 'REST_API' ? 'REST API' : 'MCP Server'} 
        />
      )
    },
    {
      title: '订阅状态',
      dataIndex: 'status',
      key: 'status',
      render: (status: string) => (
        <Badge 
          status={status === 'APPROVED' ? 'success' : 'processing'} 
          text={status === 'APPROVED' ? '已通过' : '待审批'} 
        />
      )
    },
    {
      title: '订阅时间',
      dataIndex: 'createAt',
      key: 'createAt',
      render: (date: string) => formatDateTime(date)
    },
    {
      title: '更新时间',
      dataIndex: 'updatedAt',
      key: 'updatedAt',
      render: (date: string) => formatDateTime(date)
    },
    {
      title: '操作',
      key: 'action',
      width: 120,
      render: (_: any, record: Subscription) => {
        const loadingKey = `${record.consumerId}-${record.productId}`;
        const isApproving = actionLoading === `${loadingKey}-approve`;
        const isDeleting = actionLoading === `${loadingKey}-delete`;
        
        if (record.status === 'PENDING') {
          return (
            <Button
              type="primary"
              size="small"
              loading={isApproving}
              onClick={() => handleApproveSubscription(record)}
            >
              审批通过
            </Button>
          );
        } else if (record.status === 'APPROVED') {
          return (
            <Popconfirm
              title="确定要删除这个订阅吗?"
              description="删除后将无法恢复"
              onConfirm={() => handleDeleteSubscription(record)}
              okText="确定"
              cancelText="取消"
            >
              <Button
                type="default"
                size="small"
                danger
                loading={isDeleting}
              >
                删除订阅
              </Button>
            </Popconfirm>
          );
        }
        return null;
      }
    }
  ];

  const pendingCount = subscriptions.filter(s => s.status === 'PENDING').length;
  const approvedCount = subscriptions.filter(s => s.status === 'APPROVED').length;

  return (
    <Modal
      title={
        <div>
          <div className="text-lg font-semibold">订阅列表 - {consumerName}</div>
          <div className="text-sm text-gray-500 mt-1">
            待审批: <Badge count={pendingCount} style={{ backgroundColor: '#faad14' }} /> | 
            已通过: <Badge count={approvedCount} style={{ backgroundColor: '#52c41a' }} />
          </div>
        </div>
      }
      open={visible}
      onCancel={onCancel}
      footer={null}
      width={1000}
      destroyOnClose
    >
      <Table
        columns={columns}
        dataSource={subscriptions}
        rowKey="subscriptionId"
        loading={loading}
        pagination={pagination}
        onChange={handleTableChange}
        locale={{
          emptyText: '暂无订阅记录'
        }}
      />
    </Modal>
  );
}




```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/api-product/ApiProductOverview.tsx:
--------------------------------------------------------------------------------

```typescript
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Card, Row, Col, Statistic, Button, message } from 'antd'
import { 
  ApiOutlined, 
  GlobalOutlined,
  TeamOutlined,
  EditOutlined,
  CheckCircleFilled,
  MinusCircleFilled,
  CopyOutlined,
  ExclamationCircleFilled,
  ClockCircleFilled
} from '@ant-design/icons'
import type { ApiProduct } from '@/types/api-product'
import { getServiceName, formatDateTime, copyToClipboard } from '@/lib/utils'
import { apiProductApi } from '@/lib/api'


interface ApiProductOverviewProps {
  apiProduct: ApiProduct
  linkedService: any | null
  onEdit: () => void
}

export function ApiProductOverview({ apiProduct, linkedService, onEdit }: ApiProductOverviewProps) {

  const [portalCount, setPortalCount] = useState(0)
  const [subscriberCount] = useState(0)

  const navigate = useNavigate()

  useEffect(() => {
    if (apiProduct.productId) {
      fetchPublishedPortals()
    }
  }, [apiProduct.productId])

  const fetchPublishedPortals = async () => {
    try {
      const res = await apiProductApi.getApiProductPublications(apiProduct.productId)
      setPortalCount(res.data.content?.length || 0)
    } catch (error) {
    } finally {
    }
  }


  return (
    <div className="p-6 space-y-6">
      <div>
        <h1 className="text-2xl font-bold mb-2">概览</h1>
        <p className="text-gray-600">API产品概览</p>
      </div>

      {/* 基本信息 */}
      <Card 
        title="基本信息"
        extra={
          <Button 
            type="primary" 
            icon={<EditOutlined />} 
            onClick={onEdit}
          >
            编辑
          </Button>
        }
      >
        <div>
            <div className="grid grid-cols-6 gap-8 items-center pt-0 pb-2">
             <span className="text-xs text-gray-600">产品名称:</span>
             <span className="col-span-2 text-xs text-gray-900">{apiProduct.name}</span>
             <span className="text-xs text-gray-600">产品ID:</span>
              <div className="col-span-2 flex items-center gap-2">
                <span className="text-xs text-gray-700">{apiProduct.productId}</span>
                <Button 
                  type="text" 
                  size="small"
                  icon={<CopyOutlined />}
                  onClick={async () => {
                    try {
                      await copyToClipboard(apiProduct.productId);
                      message.success('产品ID已复制');
                    } catch {
                      message.error('复制失败,请手动复制');
                    }
                  }}
                  className="h-auto p-1 min-w-0"
                />
              </div>
            </div>
            
            <div className="grid grid-cols-6 gap-8 items-center pt-2 pb-2">
             <span className="text-xs text-gray-600">类型:</span>
              <span className="col-span-2 text-xs text-gray-900">
                {apiProduct.type === 'REST_API' ? 'REST API' : 'MCP Server'}
              </span>
             <span className="text-xs text-gray-600">状态:</span>
              <div className="col-span-2 flex items-center">
                {apiProduct.status === "PENDING" ? (
                  <ExclamationCircleFilled className="text-yellow-500 mr-2" style={{fontSize: '10px'}} />
                ) : apiProduct.status === "READY" ? (
                  <ClockCircleFilled className="text-blue-500 mr-2" style={{fontSize: '10px'}} />
                ) : (
                  <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '10px'}} />
                )}
                <span className="text-xs text-gray-900">
                  {apiProduct.status === "PENDING" ? "待配置" : apiProduct.status === "READY" ? "待发布" : "已发布"}
                </span>
              </div>
            </div>
            
            <div className="grid grid-cols-6 gap-8 items-center pt-2 pb-2">
              <span className="text-xs text-gray-600">自动审批订阅:</span>
              <div className="col-span-2 flex items-center">
                {apiProduct.autoApprove === true ? (
                  <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '10px'}} />
                ) : (
                  <MinusCircleFilled className="text-gray-400 mr-2" style={{fontSize: '10px'}} />
                )}
                <span className="text-xs text-gray-900">
                 {apiProduct.autoApprove === true ? '已开启' : '已关闭'}
                </span>
              </div>
              <span className="text-xs text-gray-600">创建时间:</span>
              <span className="col-span-2 text-xs text-gray-700">{formatDateTime(apiProduct.createAt)}</span>
            </div>

            {apiProduct.description && (
              <div className="grid grid-cols-6 gap-8 pt-2 pb-2">
               <span className="text-xs text-gray-600">描述:</span>
                <span className="col-span-5 text-xs text-gray-700 leading-relaxed">
                  {apiProduct.description}
                </span>
              </div>
            )}

        </div>
      </Card>

      {/* 统计数据 */}
      <Row gutter={[16, 16]}>
        <Col xs={24} sm={12} lg={8}>
          <Card 
            className="cursor-pointer hover:shadow-md transition-shadow"
            onClick={() => {
              navigate(`/api-products/detail?productId=${apiProduct.productId}&tab=portal`)
            }}
          >
            <Statistic
              title="发布的门户"
              value={portalCount}
              prefix={<GlobalOutlined className="text-blue-500" />}
              valueStyle={{ color: '#1677ff', fontSize: '24px' }}
            />
          </Card>
        </Col>
        <Col xs={24} sm={12} lg={8}>
          <Card 
            className="cursor-pointer hover:shadow-md transition-shadow"
            onClick={() => {
              navigate(`/api-products/detail?productId=${apiProduct.productId}&tab=link-api`)
            }}
          >
            <Statistic
              title="关联API"
              value={getServiceName(linkedService) || '未关联'}
              prefix={<ApiOutlined className="text-blue-500" />}
              valueStyle={{ color: '#1677ff', fontSize: '24px' }}
            />
          </Card>
        </Col>
        <Col xs={24} sm={12} lg={8}>
          <Card className="hover:shadow-md transition-shadow">
            <Statistic
              title="订阅用户"
              value={subscriberCount}
              prefix={<TeamOutlined className="text-blue-500" />}
              valueStyle={{ color: '#1677ff', fontSize: '24px' }}
            />
          </Card>
        </Col>
      </Row>

    </div>
  )
} 
```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/components/portal/PortalSecurity.tsx:
--------------------------------------------------------------------------------

```typescript
import {Card, Form, Switch, Divider, message} from 'antd'
import {useMemo} from 'react'
import {Portal, ThirdPartyAuthConfig, AuthenticationType, OidcConfig, OAuth2Config} from '@/types'
import {portalApi} from '@/lib/api'
import {ThirdPartyAuthManager} from './ThirdPartyAuthManager'

interface PortalSecurityProps {
    portal: Portal
    onRefresh?: () => void
}

export function PortalSecurity({portal, onRefresh}: PortalSecurityProps) {
    const [form] = Form.useForm()

    const handleSave = async () => {
        try {
            const values = await form.validateFields()
            
            await portalApi.updatePortal(portal.portalId, {
                name: portal.name,
                description: portal.description,
                portalSettingConfig: {
                    ...portal.portalSettingConfig,
                    builtinAuthEnabled: values.builtinAuthEnabled,
                    oidcAuthEnabled: values.oidcAuthEnabled,
                    autoApproveDevelopers: values.autoApproveDevelopers,
                    autoApproveSubscriptions: values.autoApproveSubscriptions,
                    frontendRedirectUrl: values.frontendRedirectUrl,
                },
                portalDomainConfig: portal.portalDomainConfig,
                portalUiConfig: portal.portalUiConfig,
            })

            message.success('安全设置保存成功')
            onRefresh?.()
        } catch (error) {
            message.error('保存安全设置失败')
        }
    }

    const handleSettingUpdate = () => {
        // 立即更新配置
        handleSave()
    }

    // 第三方认证配置保存函数
    const handleSaveThirdPartyAuth = async (configs: ThirdPartyAuthConfig[]) => {
        try {
            // 分离OIDC和OAuth2配置,去掉type字段
            const oidcConfigs = configs
                .filter(config => config.type === AuthenticationType.OIDC)
                .map(config => {
                    const { type, ...oidcConfig } = config as (OidcConfig & { type: AuthenticationType.OIDC })
                    return oidcConfig
                })

            const oauth2Configs = configs
                .filter(config => config.type === AuthenticationType.OAUTH2)
                .map(config => {
                    const { type, ...oauth2Config } = config as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
                    return oauth2Config
                })
            
            const updateData = {
                ...portal,
                portalSettingConfig: {
                    ...portal.portalSettingConfig,
                    // 直接保存分离的配置数组
                    oidcConfigs: oidcConfigs,
                    oauth2Configs: oauth2Configs
                }
            }
            
            await portalApi.updatePortal(portal.portalId, updateData)
            
            onRefresh?.()
        } catch (error) {
            throw error
        }
    }

    // 合并OIDC和OAuth2配置用于统一显示
    const thirdPartyAuthConfigs = useMemo((): ThirdPartyAuthConfig[] => {
        const configs: ThirdPartyAuthConfig[] = []
        
        // 添加OIDC配置
        if (portal.portalSettingConfig?.oidcConfigs) {
            portal.portalSettingConfig.oidcConfigs.forEach(oidcConfig => {
                configs.push({
                    ...oidcConfig,
                    type: AuthenticationType.OIDC
                })
            })
        }
        
        // 添加OAuth2配置
        if (portal.portalSettingConfig?.oauth2Configs) {
            portal.portalSettingConfig.oauth2Configs.forEach(oauth2Config => {
                configs.push({
                    ...oauth2Config,
                    type: AuthenticationType.OAUTH2
                })
            })
        }
        
        return configs
    }, [portal.portalSettingConfig?.oidcConfigs, portal.portalSettingConfig?.oauth2Configs])


    return (
        <div className="p-6 space-y-6">
            <div>
                <h1 className="text-2xl font-bold mb-2">Portal安全配置</h1>
                <p className="text-gray-600">配置Portal的认证与审批方式</p>
            </div>

            <Form
                form={form}
                layout="vertical"
                initialValues={{
                    builtinAuthEnabled: portal.portalSettingConfig?.builtinAuthEnabled,
                    oidcAuthEnabled: portal.portalSettingConfig?.oidcAuthEnabled,
                    autoApproveDevelopers: portal.portalSettingConfig?.autoApproveDevelopers,
                    autoApproveSubscriptions: portal.portalSettingConfig?.autoApproveSubscriptions,
                    frontendRedirectUrl: portal.portalSettingConfig?.frontendRedirectUrl,
                }}
            >
                <Card>
                    <div className="space-y-6">
                        {/* 基本安全配置标题 */}
                        <h3 className="text-lg font-medium">基本安全配置</h3>
                        
                        {/* 基本安全设置内容 */}
                        <div className="grid grid-cols-2 gap-6">
                            <Form.Item
                                name="builtinAuthEnabled"
                                label="账号密码登录"
                                valuePropName="checked"
                            >
                                <Switch
                                    onChange={() => handleSettingUpdate()}
                                />
                            </Form.Item>
                            <Form.Item
                                name="autoApproveDevelopers"
                                label="开发者自动审批"
                                valuePropName="checked"
                            >
                                <Switch
                                    onChange={() => handleSettingUpdate()}
                                />
                            </Form.Item>
                            <Form.Item
                                name="autoApproveSubscriptions"
                                label="订阅自动审批"
                                valuePropName="checked"
                            >
                                <Switch
                                    onChange={() => handleSettingUpdate()}
                                />
                            </Form.Item>
                        </div>

                        <Divider />
                        
                        {/* 第三方认证管理器 - 内部已有标题,不需要重复添加 */}
                        <ThirdPartyAuthManager
                            configs={thirdPartyAuthConfigs}
                            onSave={handleSaveThirdPartyAuth}
                        />
                    </div>
                </Card>
            </Form>
        </div>
    )
}

```

--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------

```
<?xml version="1.0" encoding="UTF-8"?>
<!--
  ~ Licensed to the Apache Software Foundation (ASF) under one
  ~ or more contributor license agreements.  See the NOTICE file
  ~ distributed with this work for additional information
  ~ regarding copyright ownership.  The ASF licenses this file
  ~ to you under the Apache License, Version 2.0 (the
  ~ "License"); you may not use this file except in compliance
  ~ with the License.  You may obtain a copy of the License at
  ~
  ~   http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing,
  ~ software distributed under the License is distributed on an
  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  ~ KIND, either express or implied.  See the License for the
  ~ specific language governing permissions and limitations
  ~ under the License.
  -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.alibaba.himarket</groupId>
    <artifactId>himarket</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>himarket</name>
    <description>HiMarket AI OPEN Platform</description>
    <url>https://github.com/higress-group/himarket</url>

    <licenses>
        <license>
            <name>Apache License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <modules>
        <module>portal-dal</module>
        <module>portal-server</module>
        <module>portal-bootstrap</module>
    </modules>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.18</spring-boot.version>
        <mybatis.version>2.3.1</mybatis.version>
        <mariadb.version>3.4.1</mariadb.version>
        <hutool.version>5.8.32</hutool.version>
        <bouncycastle.version>1.78</bouncycastle.version>
        <springdoc.version>1.7.0</springdoc.version>
        <apigsdk.version>4.0.10</apigsdk.version>
        <msesdk.version>7.21.0</msesdk.version>
        <aliyunsdk.version>4.4.6</aliyunsdk.version>
        <okhttp.version>4.12.0</okhttp.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>

    <!-- Dependency Management -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot Dependencies -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- MariaDB Driver -->
            <dependency>
                <groupId>org.mariadb.jdbc</groupId>
                <artifactId>mariadb-java-client</artifactId>
                <version>${mariadb.version}</version>
            </dependency>

            <!-- Hutool -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>

            <!-- Spring Boot Starter Security -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
                <version>${spring-boot.version}</version>
            </dependency>

            <!-- Spring Boot Starter OAuth2 Client -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-oauth2-client</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-ui</artifactId>
                <version>${springdoc.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>alibabacloud-apig20240327</artifactId>
                <version>${apigsdk.version}</version>
            </dependency>

            <!-- 阿里云 MSE SDK -->
            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>mse20190531</artifactId>
                <version>${msesdk.version}</version>
            </dependency>

            <dependency>
                <groupId>com.aliyun</groupId>
                <artifactId>aliyun-java-sdk-core</artifactId>
                <version>${aliyunsdk.version}</version>
            </dependency>

            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>${okhttp.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-maintainer-client</artifactId>
                <version>3.0.2</version>
            </dependency>

            <!-- Bouncy Castle Provider -->
            <dependency>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-jdk15to18</artifactId>
                <version>${bouncycastle.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <!-- Build Configuration -->
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
```
Page 3/7FirstPrevNextLast