#
tokens: 49689/50000 85/349 files (page 2/7)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 7. Use http://codebase.md/higress-group/himarket?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/dto/params/gateway/QueryAPIGParam.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.dto.params.gateway;

import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class QueryAPIGParam implements InputConverter<APIGConfig> {

    @NotBlank(message = "网关region不能为空")
    private String region;

    @NotNull(message = "网关类型不能为空")
    private GatewayType gatewayType;

    @NotBlank(message = "accessKey不能为空")
    private String accessKey;

    @NotBlank(message = "secretKey不能为空")
    private String secretKey;
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/developer/CreateDeveloperParam.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.dto.params.developer;

import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.Developer;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Data
@NoArgsConstructor
public class CreateDeveloperParam implements InputConverter<Developer> {

    @NotBlank(message = "用户名不能为空")
    @Size(max = 64, message = "用户名长度不能超过64个字符")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 32, message = "密码长度应为6-32位")
    private String password;

    @Size(max = 256, message = "头像url长度不能超过256个字符")
    private String avatarUrl;
} 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/BaseEntity.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.entity;

import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Data
public class BaseEntity implements Serializable {

    @CreatedDate
    @Column(name = "created_at", updatable = false, columnDefinition = "datetime(3)")
    private LocalDateTime createAt;

    @LastModifiedDate
    @Column(name = "updated_at", columnDefinition = "datetime(3)")
    private LocalDateTime updatedAt;
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ProductPublicationRepository.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.repository;

import com.alibaba.apiopenplatform.entity.ProductPublication;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Optional;

public interface ProductPublicationRepository extends BaseRepository<ProductPublication, Long> {

    Page<ProductPublication> findByPortalId(String portalId, Pageable pageable);

    Optional<ProductPublication> findByPortalIdAndProductId(String portalId, String productId);

    Page<ProductPublication> findByProductId(String productId, Pageable pageable);

    void deleteByProductId(String productId);

    void deleteAllByPortalId(String portalId);

    boolean existsByProductId(String productId);
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ProductRefRepository.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.repository;

import com.alibaba.apiopenplatform.entity.ProductRef;
import com.alibaba.apiopenplatform.support.enums.SourceType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

/**
 * API Reference Repository
 */
@Repository
public interface ProductRefRepository extends JpaRepository<ProductRef, Long>, JpaSpecificationExecutor<ProductRef> {

    Optional<ProductRef> findByProductId(String productId);

    Optional<ProductRef> findFirstByProductId(String productId);

    boolean existsByGatewayId(String gatewayId);
}

```

--------------------------------------------------------------------------------
/portal-dal/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-dal</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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.14.0-rc1</version>
        </dependency>
    </dependencies>

</project>
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/AuthResult.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.dto.result;

import cn.hutool.core.annotation.Alias;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class AuthResult {

    @Alias("access_token")
    @JsonProperty("access_token")
    private String accessToken;

    @Alias("token_type")
    @JsonProperty("token_type")
    @Builder.Default
    private String tokenType = "Bearer";

    @Alias("expires_in")
    @JsonProperty("expires_in")
    private Long expiresIn;

    public static AuthResult of(String accessToken, Long expiresIn) {
        return AuthResult.builder()
                .accessToken(accessToken)
                .expiresIn(expiresIn)
                .build();
    }
} 
```

--------------------------------------------------------------------------------
/deploy/helm/templates/himarket-admin-deployment.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: himarket-admin
  labels:
    app: himarket-admin
spec:
  replicas: {{ .Values.admin.replicaCount }}
  selector:
    matchLabels:
      app: himarket-admin
  template:
    metadata:
      labels:
        app: himarket-admin
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "himarket.serviceAccountName" . }}
      containers:
        - name: admin
          image: "{{ .Values.hub }}/{{ .Values.admin.image.repository }}:{{ .Values.admin.image.tag}}"
          imagePullPolicy: {{ .Values.admin.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.admin.serverPort }}
              protocol: TCP
          envFrom:
            - configMapRef:
                name: himarket-admin
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          {{- with .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml . | nindent 12 }}
          {{- end }}
      {{- with .Values.volumes }}
      volumes:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/APIGMCPServerResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import com.aliyun.sdk.service.apig20240327.models.HttpRoute;
import lombok.Data;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
@Data
public class APIGMCPServerResult extends GatewayMCPServerResult implements OutputConverter<APIGMCPServerResult, HttpRoute> {

    private String apiId;

    private String mcpServerId;

    private String mcpRouteId;

    @Override
    public APIGMCPServerResult convertFrom(HttpRoute httpRoute) {
        APIGMCPServerResult r = OutputConverter.super.convertFrom(httpRoute);
        r.setMcpServerName(httpRoute.getName());
        r.setMcpRouteId(httpRoute.getRouteId());
        return r;
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "portal-admin",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint .",
    "preview": "vite preview",
    "start": "node server.js",
    "serve": "npm run build && npm start",
    "serve-live": "npx live-server dist --port=3000 --entry-file=index.html"
  },
  "dependencies": {
    "@babel/runtime": "^8.0.0-alpha.17",
    "antd": "^5.15.7",
    "axios": "^1.7.9",
    "clsx": "^2.1.1",
    "helmet": "^7.1.0",
    "js-yaml": "^4.1.0",
    "monaco-editor": "^0.52.2",
    "postcss": "^8.5.6",
    "react": "^18.0.0",
    "react-dom": "^18.0.0",
    "react-markdown": "^10.1.0",
    "react-markdown-editor-lite": "^1.3.4",
    "react-monaco-editor": "^0.59.0",
    "react-router-dom": "^6.28.0",
    "remark-gfm": "^4.0.1",
    "swagger-ui-react": "^5.29.0",
    "tailwind-merge": "^3.3.1",
    "terser": "^5.43.1"
  },
  "resolutions": {
    "@babel/runtime": "^8.0.0-alpha.17"
  },
  "devDependencies": {
    "@eslint/js": "^9.30.1",
    "@types/js-yaml": "^4.0.9",
    "@types/node": "^24.3.0",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.3",
    "@vitejs/plugin-react": "^4.7.0",
    "autoprefixer": "^10.4.21",
    "compression": "^1.8.0",
    "cors": "^2.8.5",
    "eslint": "^9.30.1",
    "eslint-plugin-react-hooks": "^5.2.0",
    "eslint-plugin-react-refresh": "^0.4.20",
    "express": "^4.21.2",
    "globals": "^16.3.0",
    "path": "^0.12.7",
    "postcss": "^8.5.6",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.6.3",
    "url": "^0.11.4",
    "vite": "^4.5.14"
  }
}

```

--------------------------------------------------------------------------------
/deploy/helm/templates/himarket-frontend-deployment.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: himarket-frontend
  labels:
    app: himarket-frontend
spec:
  replicas: {{ .Values.frontend.replicaCount }}
  selector:
    matchLabels:
      app: himarket-frontend
  template:
    metadata:
      labels:
        app: himarket-frontend
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "himarket.serviceAccountName" . }}
      containers:
        - name: frontend
          image: "{{ .Values.hub }}/{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag}}"
          imagePullPolicy: {{ .Values.frontend.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.frontend.serverPort }}
              protocol: TCP
          envFrom:
            - configMapRef:
                name: himarket-frontend
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          {{- with .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml . | nindent 12 }}
          {{- end }}
      {{- with .Values.volumes }}
      volumes:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/consumer/CreateCredentialParam.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.dto.params.consumer;

import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.ConsumerCredential;
import com.alibaba.apiopenplatform.support.consumer.ApiKeyConfig;
import com.alibaba.apiopenplatform.support.consumer.HmacConfig;
import com.alibaba.apiopenplatform.support.consumer.JwtConfig;
import lombok.Data;

import javax.validation.constraints.AssertTrue;

@Data
public class CreateCredentialParam implements InputConverter<ConsumerCredential> {

    private ApiKeyConfig apiKeyConfig;

    private HmacConfig hmacConfig;

    private JwtConfig jwtConfig;

    @AssertTrue(message = "凭证信息不能为空")
    private boolean isValid() {
        return apiKeyConfig != null || hmacConfig != null || jwtConfig != null;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/nacos/CreateNacosParam.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.dto.params.nacos;

import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.NacosInstance;

import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
 * 创建Nacos实例参数
 *
 */
@Data
public class CreateNacosParam implements InputConverter<NacosInstance> {

    @NotBlank(message = "Nacos名称不能为空")
    private String nacosName;

    @NotBlank(message = "服务器地址不能为空")
    private String serverUrl;

    /**
     * 可选:客户端指定的 nacosId,若为空则由服务端生成
     */
    private String nacosId;

    // namespace removed from create param as it's no longer stored on instance

    private String username;

    private String password;

    private String accessKey;

    private String secretKey;

    private String description;
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/support/enums/GatewayType.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.support.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum GatewayType {

    /**
     * 云原生API网关
     */
    APIG_API("API"),

    /**
     * AI网关
     */
    APIG_AI("AI"),

    /**
     * ADP AI网关
     */
    ADP_AI_GATEWAY("ADP_AI_GATEWAY"),

    /**
     * Higress
     */
    HIGRESS("Higress"),

    ;

    private final String type;

    public boolean isHigress() {
        return this == HIGRESS;
    }

    public boolean isAPIG() {
        return this == APIG_API || this == APIG_AI || this == ADP_AI_GATEWAY;
    }

    public boolean isAIGateway() {
        return this == APIG_AI || this == ADP_AI_GATEWAY;
    }

    public boolean isAdpAIGateway() {
        return this == ADP_AI_GATEWAY;
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/BaseRepository.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.repository;

import com.alibaba.apiopenplatform.entity.ProductPublication;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.lang.NonNull;

import java.util.Collection;
import java.util.List;

/**
 * 基础数据访问接口,提供通用的数据库操作方法
 *
 * @param <D> 实体类型(Domain/Entity)
 * @param <I> 主键类型(ID)
 */
@NoRepositoryBean
public interface BaseRepository<D, I> extends JpaRepository<D, I>, JpaSpecificationExecutor<D> {

    /**
     * 根据ID集合批量查询实体列表
     *
     * @param ids
     * @param sort
     * @return
     */
    List<D> findAllByIdIn(@NonNull Collection<I> ids, @NonNull Sort sort);
}


```

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

```java
package com.alibaba.apiopenplatform.controller;

import com.alibaba.apiopenplatform.dto.result.AuthResult;
import com.alibaba.apiopenplatform.dto.result.IdpResult;
import com.alibaba.apiopenplatform.service.OidcService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/developers/oidc")
@RequiredArgsConstructor
public class OidcController {

    private final OidcService oidcService;

    @GetMapping("/authorize")
    public void authorize(@RequestParam String provider,
                          @RequestParam(defaultValue = "/api/v1") String apiPrefix,
                          HttpServletRequest request,
                          HttpServletResponse response) throws IOException {
        String authUrl = oidcService.buildAuthorizationUrl(provider, apiPrefix, request);

        log.info("Redirecting to OIDC authorization URL: {}", authUrl);
        response.sendRedirect(authUrl);
    }

    @GetMapping("/callback")
    public AuthResult callback(@RequestParam String code,
                               @RequestParam String state,
                               HttpServletRequest request,
                               HttpServletResponse response) {
        return oidcService.handleCallback(code, state, request, response);
    }

    @GetMapping("/providers")
    public List<IdpResult> getProviders() {
        return oidcService.getAvailableProviders();
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Administrator.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.entity;

import javax.persistence.*;
import java.util.Date;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;

/**
 * 管理员实体类,映射管理员账号信息
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "administrator", uniqueConstraints = {
        @UniqueConstraint(columnNames = {"adminId"}),
        @UniqueConstraint(columnNames = {"username"})
})
public class Administrator extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String adminId;

    @Column(nullable = false, unique = true, length = 64)
    private String username;

    @Column(nullable = false)
    private String passwordHash;

} 
```

--------------------------------------------------------------------------------
/portal-bootstrap/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-bootstrap</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-server</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- OkHttp for RestTemplate -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.18</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/config/AsyncConfig.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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean("taskExecutor")
    public Executor getTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("TaskExecutor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/DeveloperRepository.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.repository;

import com.alibaba.apiopenplatform.entity.Developer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.List;

public interface DeveloperRepository extends BaseRepository<Developer, Long> {

    Optional<Developer> findByDeveloperId(String developerId);

    Optional<Developer> findByUsername(String username);

    List<Developer> findByPortalId(String portalId);

    Optional<Developer> findByPortalIdAndUsername(String portalId, String username);

    Optional<Developer> findByPortalIdAndEmail(String portalId, String email);

    Optional<Developer> findByDeveloperIdAndPortalId(String developerId, String portalId);

    Page<Developer> findByPortalId(String portalId, Pageable pageable);
} 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/MseNacosResult.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.dto.result;

import com.aliyun.mse20190531.models.ListClustersResponseBody.ListClustersResponseBodyData;

import lombok.Data;

@Data
public class MseNacosResult {
    private String instanceId;

    private String name;

    private String serverIntranetEndpoint;

    private String serverInternetEndpoint;

    private String version;

    public static MseNacosResult fromListClustersResponseBodyData(ListClustersResponseBodyData cluster) {
        MseNacosResult nacosResult = new MseNacosResult();
        nacosResult.setName(cluster.getClusterAliasName());
        nacosResult.setVersion(cluster.getVersionCode());
        nacosResult.setInstanceId(cluster.getInstanceId());
        nacosResult.setServerIntranetEndpoint(cluster.getIntranetDomain());
        nacosResult.setServerInternetEndpoint(cluster.getInternetDomain());
        return nacosResult;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/ProductRefResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import com.alibaba.apiopenplatform.entity.ProductRef;
import com.alibaba.apiopenplatform.support.enums.SourceType;
import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
import com.alibaba.apiopenplatform.support.product.HigressRefConfig;
import com.alibaba.apiopenplatform.support.product.NacosRefConfig;
import lombok.Data;

@Data
public class ProductRefResult implements OutputConverter<ProductRefResult, ProductRef> {

    private String productId;

    private SourceType sourceType;

    private String gatewayId;

    private APIGRefConfig apigRefConfig;

    // 新增:ADP AI 网关引用配置(与 APIGRefConfig 结构一致)
    private APIGRefConfig adpAIGatewayRefConfig;

    private HigressRefConfig higressRefConfig;

    private String nacosId;

    private NacosRefConfig nacosRefConfig;
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ProductRepository.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.repository;

import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import com.alibaba.apiopenplatform.entity.Product;

@Repository
public interface ProductRepository extends BaseRepository<Product, Long> {

    Optional<Product> findByProductId(String productId);

    Optional<Product> findByProductIdAndAdminId(String productId, String adminId);

    Optional<Product> findByNameAndAdminId(String name, String adminId);

    Page<Product> findByProductIdIn(Collection<String> productIds, Pageable pageable);

    List<Product> findByProductIdIn(Collection<String> productIds);

    Page<Product> findByAdminId(String adminId, Pageable pageable);

    Page<Product> findByCategory(String category, Pageable pageable);
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ConsumerRepository.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.repository;

import com.alibaba.apiopenplatform.entity.Consumer;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

public interface ConsumerRepository extends BaseRepository<Consumer, Long> {

    Optional<Consumer> findByConsumerId(String consumerId);

    Optional<Consumer> findByDeveloperIdAndConsumerId(String developerId, String consumerId);

    Optional<Consumer> findByDeveloperIdAndName(String developerId, String name);

    Page<Consumer> findByDeveloperId(String developerId, Pageable pageable);

    Page<Consumer> findByPortalId(String portalId, Pageable pageable);

    List<Consumer> findAllByDeveloperId(String developerId);

    void deleteAllByDeveloperId(String developerId);

    List<Consumer> findByConsumerIdIn(Collection<String> consumerIds);
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/product/UpdateProductParam.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.dto.params.product;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.Product;
import com.alibaba.apiopenplatform.support.enums.ProductType;
import com.alibaba.apiopenplatform.support.product.ProductIcon;
import lombok.Data;

import javax.validation.constraints.AssertTrue;

@Data
public class UpdateProductParam implements InputConverter<Product> {

    private String name;

    private String description;

    private ProductType type;

    private Boolean enableConsumerAuth;

    private String document;

    private ProductIcon icon;

    private String category;

    private Boolean autoApprove;

    @AssertTrue(message = "Icon大小不能超过16KB")
    public boolean checkIcon() {
        if (icon == null || StrUtil.isBlank(icon.getValue())) {
            return true;
        }
        return icon.getValue().length() < 16 * 1024;
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/types/api-product.ts:
--------------------------------------------------------------------------------

```typescript
export interface ApiProductConfig {
  spec: string;
  meta: {
    source: string;
    type: string;
  }
}

// 产品图标类型
export interface ProductIcon {
  type: 'URL' | 'BASE64';
  value: 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;
  }
}

// API 配置相关类型
export interface RestAPIItem {
  apiId: string;
  apiName: string;
}

export interface HigressMCPItem {
  mcpServerName: string;
  fromGatewayType: 'HIGRESS';
}

export interface NacosMCPItem {
  mcpServerName: string;
  fromGatewayType: 'NACOS';
  namespaceId: string;
}

export interface APIGAIMCPItem {
  mcpServerName: string;
  fromGatewayType: 'APIG_AI' | 'ADP_AI_GATEWAY';
  mcpRouteId: string;
  mcpServerId?: string;
  apiId?: string;
  type?: string;
}

export type ApiItem = RestAPIItem | HigressMCPItem | APIGAIMCPItem | NacosMCPItem;

// 关联服务配置
export interface LinkedService {
  productId: string;
  gatewayId?: string;
  nacosId?: string;
  sourceType: 'GATEWAY' | 'NACOS';
  apigRefConfig?: RestAPIItem | APIGAIMCPItem;
  higressRefConfig?: HigressMCPItem;
  nacosRefConfig?: NacosMCPItem;
  adpAIGatewayRefConfig?: APIGAIMCPItem;
}

export interface ApiProduct {
  productId: string;
  name: string;
  description: string;
  type: 'REST_API' | 'MCP_SERVER';
  category: string;
  status: 'PENDING' | 'READY' | 'PUBLISHED' | string;
  createAt: string;
  enableConsumerAuth?: boolean;
  autoApprove?: boolean;
  apiConfig?: ApiProductConfig;
  mcpConfig?: ApiProductMcpConfig;
  document?: string;
  icon?: ProductIcon | null;
} 

```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/config/RestTemplateConfig.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 lombok.RequiredArgsConstructor;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(OkHttpClient okHttpClient) {
        // 使用OkHttp作为RestTemplate的底层客户端
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient));
    }

    @Bean
    public OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
                .build();
    }
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/product/CreateProductRefParam.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.dto.params.product;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.ProductRef;
import com.alibaba.apiopenplatform.support.enums.SourceType;
import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
import com.alibaba.apiopenplatform.support.product.HigressRefConfig;
import com.alibaba.apiopenplatform.support.product.NacosRefConfig;
import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotNull;

@Data
public class CreateProductRefParam implements InputConverter<ProductRef> {

    @NotNull(message = "数据源类型不能为空")
    private SourceType sourceType;

    private String gatewayId;

    private String nacosId;

    private APIGRefConfig apigRefConfig;

    // 新增:ADP AI 网关引用配置(与 APIGRefConfig 结构一致)
    private APIGRefConfig adpAIGatewayRefConfig;

    private HigressRefConfig higressRefConfig;

    private NacosRefConfig nacosRefConfig;

}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/ProductResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import com.alibaba.apiopenplatform.entity.Product;
import com.alibaba.apiopenplatform.support.enums.ProductStatus;
import com.alibaba.apiopenplatform.support.enums.ProductType;
import com.alibaba.apiopenplatform.support.product.ProductIcon;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class ProductResult implements OutputConverter<ProductResult, Product> {

    private String productId;

    private String name;

    private String description;

    private ProductStatus status = ProductStatus.PENDING;

    private Boolean enableConsumerAuth = false;

    private ProductType type;

    private String document;

    private ProductIcon icon;

    private String category;

    private Boolean autoApprove;

    private LocalDateTime createAt;

    private LocalDateTime updatedAt;

    private APIConfigResult apiConfig;

    private MCPConfigResult mcpConfig;

    private Boolean enabled;
}

```

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

```typescript
import { Modal, Radio, Button, Space } from 'antd'
import { useState } from 'react'

export type NacosImportType = 'OPEN_SOURCE' | 'MSE'

interface NacosTypeSelectorProps {
  visible: boolean
  onCancel: () => void
  onSelect: (type: NacosImportType) => void
}

export default function NacosTypeSelector({ visible, onCancel, onSelect }: NacosTypeSelectorProps) {
  const [selectedType, setSelectedType] = useState<NacosImportType>('MSE')

  const handleConfirm = () => {
    onSelect(selectedType)
  }

  const handleCancel = () => {
  setSelectedType('OPEN_SOURCE')
    onCancel()
  }

  return (
    <Modal
      title="选择 Nacos 类型"
      open={visible}
      onCancel={handleCancel}
      footer={[
        <Button key="cancel" onClick={handleCancel}>
          取消
        </Button>,
        <Button key="confirm" type="primary" onClick={handleConfirm}>
          确定
        </Button>
      ]}
      width={500}
    >
      <div className="py-4">
        <Radio.Group 
          value={selectedType} 
          onChange={(e) => setSelectedType(e.target.value)}
          className="w-full"
        >
          <Space direction="vertical" className="w-full">
            <Radio value="MSE" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">MSE Nacos</div>
                <div className="text-sm text-gray-500">通过阿里云 MSE 账号授权后选择实例导入</div>
              </div>
            </Radio>
            <Radio value="OPEN_SOURCE" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">开源 Nacos</div>
                <div className="text-sm text-gray-500">使用已有自建/开源 Nacos 地址登录创建</div>
              </div>
            </Radio>
          </Space>
        </Radio.Group>
      </div>
    </Modal>
  )
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/PortalDomain.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.entity;

import com.alibaba.apiopenplatform.support.enums.DomainType;
import com.alibaba.apiopenplatform.support.enums.ProtocolType;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@Entity
@Table(name = "portal_domain",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"domain"}, name = "uk_domain")
        }
)
@Data
@EqualsAndHashCode(callSuper = true)
public class PortalDomain extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "portal_id", length = 64, nullable = false)
    private String portalId;

    @Column(name = "domain", length = 128, nullable = false)
    private String domain;

    @Enumerated(EnumType.STRING)
    @Column(name = "type", length = 32, nullable = false)
    private DomainType type = DomainType.DEFAULT;

    @Column(name = "protocol", length = 32, nullable = false)
    @Enumerated(EnumType.STRING)
    private ProtocolType protocol = ProtocolType.HTTP;
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/DeveloperExternalIdentity.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.entity;

import javax.persistence.*;

import com.alibaba.apiopenplatform.support.enums.DeveloperAuthType;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "developer_external_identity", uniqueConstraints = {
        @UniqueConstraint(columnNames = {"provider", "subject"})
})
public class DeveloperExternalIdentity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "developer_id", referencedColumnName = "developerId", nullable = false)
    private Developer developer;

    @Column(nullable = false, length = 32)
    private String provider;

    @Column(nullable = false, length = 128)
    private String subject;

    @Column(length = 128)
    private String displayName;

    @Column(length = 32)
    private DeveloperAuthType authType;

    @Column(columnDefinition = "json")
    private String rawInfoJson;
} 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/ConsumerRef.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.entity;

import com.alibaba.apiopenplatform.converter.GatewayConfigConverter;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
import lombok.*;

import javax.persistence.*;

@Entity
@Table(name = "consumer_ref")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class ConsumerRef extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "consumer_id", length = 64, nullable = false)
    private String consumerId;

    @Column(name = "gateway_type", length = 32, nullable = false)
    @Enumerated(EnumType.STRING)
    private GatewayType gatewayType;

    @Column(name = "gw_consumer_id", length = 64, nullable = false)
    private String gwConsumerId;

    @Column(name = "gateway_config", columnDefinition = "json", nullable = false)
    @Convert(converter = GatewayConfigConverter.class)
    private GatewayConfig gatewayConfig;
}

```

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

```typescript
import { Modal, Form, Input, message } from 'antd'
import { useEffect } from 'react'
import { portalApi } from '@/lib/api'
import { Portal } from '@/types'

interface PortalFormModalProps {
  visible: boolean
  onCancel: () => void
  onSuccess: () => void
  portal: Portal | null
}

export default function PortalFormModal({
  visible,
  onCancel,
  onSuccess,
  portal,
}: PortalFormModalProps) {
  const [form] = Form.useForm()

  useEffect(() => {
    if (visible && portal) {
      form.setFieldsValue({
        name: portal.name,
        description: portal.description || '',
      })
    }
  }, [visible, portal, form])

  const handleOk = async () => {
    try {
      const values = await form.validateFields()
      if (!portal) return

      await portalApi.updatePortal(portal.portalId, {
        name: values.name,
        description: values.description,
        portalSettingConfig: portal.portalSettingConfig,
        portalDomainConfig: portal.portalDomainConfig,
        portalUiConfig: portal.portalUiConfig,
      })

      message.success('Portal信息更新成功')
      form.resetFields()
      onSuccess()
    } catch (error) {
      message.error('更新失败,请稍后重试')
    }
  }

  const handleCancel = () => {
    form.resetFields()
    onCancel()
  }

  return (
    <Modal
      title="编辑Portal"
      open={visible}
      onOk={handleOk}
      onCancel={handleCancel}
      okText="保存"
      cancelText="取消"
      width={600}
      destroyOnClose
    >
      <Form form={form} layout="vertical">
        <Form.Item
          name="name"
          label="Portal名称"
          rules={[{ required: true, message: "请输入Portal名称" }]}
        >
          <Input placeholder="请输入Portal名称" />
        </Form.Item>

        <Form.Item
          name="description"
          label="描述"
        >
          <Input.TextArea rows={3} placeholder="请输入Portal描述" />
        </Form.Item>
      </Form>
    </Modal>
  )
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/constant/Resources.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.constant;

public class Resources {

    public static final String ADMINISTRATOR = "Administrator";

    public static final String PORTAL = "Portal";

    public static final String PORTAL_SETTING = "PortalSetting";

    public static final String PORTAL_UI = "PortalUI";

    public static final String PORTAL_DOMAIN = "PortalDomain";

    public static final String OAUTH2_CONFIG = "OAuth2Config";

    public static final String PUBLIC_KEY = "PublicKey";

    public static final String OIDC_CONFIG = "OidcConfig";

    public static final String PRODUCT = "Product";

    public static final String PRODUCT_SETTING = "ProductSetting";

    public static final String DEVELOPER = "Developer";

    public static final String CONSUMER = "Consumer";

    public static final String CONSUMER_CREDENTIAL = "ConsumerCredential";

    public static final String GATEWAY = "Gateway";

    public static final String PRODUCT_REF = "ProductRef";

    public static final String NACOS_INSTANCE = "NacosInstance";

    public static final String SUBSCRIPTION = "Subscription";

}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/bin/replace_var.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python
# -*- coding: utf-8 -*-
from jinja2 import Template
import sys
import os
import traceback
import json


def readFileTrimspace(filepath):
    try:
        result = list()
        is_yaml = 'yaml' in filepath or 'yml' in filepath
        if os.path.isfile(filepath):
            f = open(filepath, 'r')
            for line in f.readlines():
                line = (line.strip('\n') if is_yaml else line.strip())
                if not len(line):
                    continue
                result.append(line)
        return "\n".join(result)
    except:
        traceback.print_exc()
        return ""

def readFile(filepath):
    try:
        if not os.path.isfile(filepath):
            print("The input [%s] is not a file" % filepath)
            return ""
        if os.path.isfile(filepath):
            try:
                f = open(filepath, 'r')
                return f.read()
            finally:
                if f:
                    f.close()
    except:
        traceback.print_exc()
        return ""


def writeFile(filepath, content):
    try:
        open(filepath, 'w+').write(content)
    except:
        traceback.print_exc()


def dockerenv2json():
    envJson = {}
    with open('/.dockerenv') as ifs:
        data = ifs.read()
    assert len(data) > 2, 'invalid dockerenv'
    envlist = json.loads(data)
    for env in envlist:
        indexOfEqual = env.index('=')
        if indexOfEqual > 0:
            key = env[0: indexOfEqual]
            value = env[indexOfEqual + 1:]
            envJson[key] = value
    return envJson


def createVarMap(filepath):
    map = os.environ
    # add more param to map
    return map


def main():
    filepath = sys.argv[1] if len(sys.argv) > 1 else ''
    t = Template(readFile(filepath))
    map = createVarMap(filepath)
    ret = t.render(map)
    writeFile(filepath, ret)


if __name__ == "__main__":
    reload(sys)
    sys.setdefaultencoding('utf-8')
    main()
```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/bin/replace_var.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/python
# -*- coding: utf-8 -*-
from jinja2 import Template
import sys
import os
import traceback
import json


def readFileTrimspace(filepath):
    try:
        result = list()
        is_yaml = 'yaml' in filepath or 'yml' in filepath
        if os.path.isfile(filepath):
            f = open(filepath, 'r')
            for line in f.readlines():
                line = (line.strip('\n') if is_yaml else line.strip())
                if not len(line):
                    continue
                result.append(line)
        return "\n".join(result)
    except:
        traceback.print_exc()
        return ""

def readFile(filepath):
    try:
        if not os.path.isfile(filepath):
            print("The input [%s] is not a file" % filepath)
            return ""
        if os.path.isfile(filepath):
            try:
                f = open(filepath, 'r')
                return f.read()
            finally:
                if f:
                    f.close()
    except:
        traceback.print_exc()
        return ""


def writeFile(filepath, content):
    try:
        open(filepath, 'w+').write(content)
    except:
        traceback.print_exc()


def dockerenv2json():
    envJson = {}
    with open('/.dockerenv') as ifs:
        data = ifs.read()
    assert len(data) > 2, 'invalid dockerenv'
    envlist = json.loads(data)
    for env in envlist:
        indexOfEqual = env.index('=')
        if indexOfEqual > 0:
            key = env[0: indexOfEqual]
            value = env[indexOfEqual + 1:]
            envJson[key] = value
    return envJson


def createVarMap(filepath):
    map = os.environ
    # add more param to map
    return map


def main():
    filepath = sys.argv[1] if len(sys.argv) > 1 else ''
    t = Template(readFile(filepath))
    map = createVarMap(filepath)
    ret = t.render(map)
    writeFile(filepath, ret)


if __name__ == "__main__":
    reload(sys)
    sys.setdefaultencoding('utf-8')
    main()
```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/config/FilterConfig.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.ContextHolder;
import com.alibaba.apiopenplatform.filter.PortalResolvingFilter;
import com.alibaba.apiopenplatform.service.PortalService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
@RequiredArgsConstructor
public class FilterConfig {

    private final PortalService portalService;

    private final ContextHolder contextHolder;

    @Bean
    public FilterRegistrationBean<PortalResolvingFilter> portalResolvingFilter() {
        FilterRegistrationBean<PortalResolvingFilter> registrationBean = new FilterRegistrationBean<>();

        PortalResolvingFilter filter = new PortalResolvingFilter(portalService, contextHolder);
        registrationBean.setFilter(filter);
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        registrationBean.addUrlPatterns("/*");

        return registrationBean;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/AdpMCPServerResult.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.dto.result;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.List;

@EqualsAndHashCode(callSuper = true)
@Data
public class AdpMCPServerResult extends GatewayMCPServerResult {

    private String gwInstanceId;
    
    @JsonProperty("name")
    private String name;
    
    private String description;
    private List<String> domains;
    private List<Service> services;
    private ConsumerAuthInfo consumerAuthInfo;
    private String rawConfigurations;
    private String type;
    private String dsn;
    private String dbType;
    private String upstreamPathPrefix;

    /**
     * 确保 mcpServerName 字段被正确设置
     */
    public void setName(String name) {
        this.name = name;
        // 同时设置父类的 mcpServerName 字段
        this.setMcpServerName(name);
    }

    @Data
    public static class Service {
        private String name;
        private Integer port;
        private String version;
        private Integer weight;
    }

    @Data
    public static class ConsumerAuthInfo {
        private String type;
        private Boolean enable;
        private List<String> allowedConsumers;
    }
}



```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/product/CreateProductParam.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.dto.params.product;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.Product;
import com.alibaba.apiopenplatform.support.enums.ProductType;
import com.alibaba.apiopenplatform.support.product.ProductIcon;
import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Data
public class CreateProductParam implements InputConverter<Product> {

    @NotBlank(message = "API产品名称不能为空")
    @Size(max = 50, message = "API产品名称长度不能超过50个字符")
    private String name;

    @Size(max = 256, message = "API产品描述长度不能超过256个字符")
    private String description;

    @NotNull(message = "API产品类型不能为空")
    private ProductType type;

    private String document;

    private ProductIcon icon;

    private String category;

    private Boolean autoApprove;

    @AssertTrue(message = "Icon大小不能超过16KB")
    public boolean checkIcon() {
        if (icon == null || StrUtil.isBlank(icon.getValue())) {
            return true;
        }
        return icon.getValue().length() < 16 * 1024;
    }
}

```

--------------------------------------------------------------------------------
/deploy/helm/templates/himarket-server-deployment.yaml:
--------------------------------------------------------------------------------

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: himarket-server
  labels:
    app: himarket-server
spec:
  replicas: {{ .Values.server.replicaCount }}
  selector:
    matchLabels:
      app: himarket-server
  template:
    metadata:
      labels:
        app: himarket-server
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "himarket.serviceAccountName" . }}
      containers:
        - name: server
          image: "{{ .Values.hub }}/{{ .Values.server.image.repository }}:{{ .Values.server.image.tag}}"
          imagePullPolicy: {{ .Values.server.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.server.serverPort }}
              protocol: TCP
          envFrom:
            - configMapRef:
                name: himarket-server        # 非敏感配置
{{- if .Values.mysql.enabled }}
            - secretRef:
                name: himarket-server-secret # 内置MySQL的敏感配置
{{- end }}
{{/*          {{- with .Values.livenessProbe }}*/}}
{{/*          livenessProbe:*/}}
{{/*            {{- toYaml . | nindent 12 }}*/}}
{{/*          {{- end }}*/}}
{{/*          {{- with .Values.readinessProbe }}*/}}
{{/*          readinessProbe:*/}}
{{/*            {{- toYaml . | nindent 12 }}*/}}
{{/*          {{- end }}*/}}
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          {{- with .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml . | nindent 12 }}
          {{- end }}
      {{- with .Values.volumes }}
      volumes:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/NacosInstance.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.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

/**
 * Nacos实例实体
 */
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "nacos_instance",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"nacos_id"}, name = "uk_nacos_id"),
        })
@Data
public class NacosInstance extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "nacos_name", length = 64, nullable = false)
    private String nacosName;

    @Column(name = "nacos_id", length = 64, nullable = false)
    private String nacosId;

    @Column(name = "admin_id", length = 64, nullable = false)
    private String adminId;

    @Column(name = "server_url", length = 256, nullable = false)
    private String serverUrl;


    @Column(name = "username", length = 64)
    private String username;

    @Column(name = "password", length = 128)
    private String password;

    @Column(name = "access_key", length = 128)
    private String accessKey;

    @Column(name = "secret_key", length = 256)
    private String secretKey;

    @Column(name = "description", length = 512)
    private String description;
} 
```

--------------------------------------------------------------------------------
/portal-bootstrap/src/main/java/com/alibaba/apiopenplatform/config/PageConfig.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 org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class PageConfig {
    @Bean
    public PageableHandlerMethodArgumentResolver pageableResolver() {
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        // 默认分页和排序
        resolver.setFallbackPageable(PageRequest.of(0, 100,
                Sort.by(Sort.Direction.DESC, "createAt")));
        // 页码从1开始
        resolver.setOneIndexedParameters(true);
        return resolver;
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
                resolvers.add(pageableResolver());
            }
        };
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Developer.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.entity;

import javax.persistence.*;
import java.util.Date;

import com.alibaba.apiopenplatform.support.enums.DeveloperAuthType;
import com.alibaba.apiopenplatform.support.enums.DeveloperStatus;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "developer", uniqueConstraints = {
        @UniqueConstraint(columnNames = {"developerId"}),
        @UniqueConstraint(columnNames = {"portalId", "username"})
})
public class Developer extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 64)
    private String developerId;

    @Column(length = 64)
    private String username;

    @Column()
    private String passwordHash;

    @Column(length = 128)
    private String email;

    @Column(nullable = false, length = 64)
    private String portalId;

    @Column(length = 256)
    private String avatarUrl;

    @Column(nullable = false, length = 16)
    @Enumerated(EnumType.STRING)
    private DeveloperStatus status;

    @Column(length = 16)
    @Enumerated(EnumType.STRING)
    private DeveloperAuthType authType;

} 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Consumer.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.entity;

import com.alibaba.apiopenplatform.support.enums.ConsumerStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.ColumnDefault;

import javax.persistence.*;

@Entity
@Table(name = "consumer",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"consumer_id"}, name = "uk_consumer_id"),
                @UniqueConstraint(columnNames = {"name", "portal_id", "developer_id"},
                        name = "uk_name_portal_developer")
        })
@Data
@EqualsAndHashCode(callSuper = true)
public class Consumer extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "consumer_id", length = 64, nullable = false)
    private String consumerId;

    @Column(name = "name", length = 64, nullable = false)
    private String name;

    @Column(name = "description", length = 256)
    private String description;

//    @Enumerated(EnumType.STRING)
//    @Column(name = "status", length = 32, nullable = false)
//    private ConsumerStatus status;

    @Column(name = "portal_id", length = 64, nullable = false)
    private String portalId;

    @Column(name = "developer_id", length = 64, nullable = false)
    private String developerId;
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/ConsumerCredential.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.entity;

import com.alibaba.apiopenplatform.converter.ApiKeyConfigConverter;
import com.alibaba.apiopenplatform.converter.HmacConfigConverter;
import com.alibaba.apiopenplatform.converter.JwtConfigConverter;
import com.alibaba.apiopenplatform.support.consumer.ApiKeyConfig;
import com.alibaba.apiopenplatform.support.consumer.HmacConfig;
import com.alibaba.apiopenplatform.support.consumer.JwtConfig;
import lombok.Data;

import javax.persistence.*;

@Entity
@Table(name = "consumer_credential",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"consumer_id"}, name = "uk_consumer_id")
        })
@Data
public class ConsumerCredential extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "consumer_id", nullable = false)
    private String consumerId;

    @Column(name = "apikey_config", columnDefinition = "json")
    @Convert(converter = ApiKeyConfigConverter.class)
    private ApiKeyConfig apiKeyConfig;

    @Column(name = "hmac_config", columnDefinition = "json")
    @Convert(converter = HmacConfigConverter.class)
    private HmacConfig hmacConfig;

    @Column(name = "jwt_config", columnDefinition = "json")
    @Convert(converter = JwtConfigConverter.class)
    private JwtConfig jwtConfig;
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/support/common/Encryptor.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.support.common;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.extra.spring.SpringUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Encryptor {

    private static String ROOT_KEY;

    private static AES getAes() {
        if (StrUtil.isBlank(ROOT_KEY)) {
            ROOT_KEY = SpringUtil.getProperty("encryption.root-key");
        }

        if (StrUtil.isBlank(ROOT_KEY)) {
            throw new RuntimeException("Encryption root key is not set");
        }

        return SecureUtil.aes(ROOT_KEY.getBytes(CharsetUtil.CHARSET_UTF_8));
    }

    public static String encrypt(String value) {
        if (StrUtil.isBlank(value)) {
            return value;
        }
        try {
            return getAes().encryptHex(value);
        } catch (Exception e) {
            log.error("Encrypt failed: {}", e.getMessage());
            return value;
        }
    }

    public static String decrypt(String value) {
        if (StrUtil.isBlank(value)) {
            return value;
        }
        try {
            return getAes().decryptStr(value);
        } catch (Exception e) {
            log.error("Decrypt failed: {}", e.getMessage());
            return value;
        }
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/nginx.conf:
--------------------------------------------------------------------------------

```
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        off;
    gzip               on;
    gzip_http_version  1.0;
    gzip_comp_level    2;
    gzip_proxied       any;
    gzip_types         text/plain text/css application/javascript text/xml application/xml+rss;
    keepalive_timeout  65;
    ssl_protocols      TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers        HIGH:!aNULL:!MD5;
    client_max_body_size 80M;
    server_tokens off;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    # HTTP Server
    server {
        # Port to listen on, can also be set in IP:PORT format
        listen  8000;

        # compression-webpack-plugin 配置
        gzip on;
        gzip_min_length 1k;
        gzip_comp_level 9;
        gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        gzip_vary on;
        # 配置禁用 gzip 条件,支持正则,此处表示 ie6 及以下不启用 gzip(因为ie低版本不支持)
        gzip_disable "MSIE [1-6]\.";

        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers Content-Type;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

        include  "/etc/nginx/default.d/*.conf";

        location /status {
            stub_status on;
            access_log   off;
            allow 127.0.0.1;
            deny all;
        }
    }
}
```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/nginx.conf:
--------------------------------------------------------------------------------

```
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        off;
    gzip               on;
    gzip_http_version  1.0;
    gzip_comp_level    2;
    gzip_proxied       any;
    gzip_types         text/plain text/css application/javascript text/xml application/xml+rss;
    keepalive_timeout  65;
    ssl_protocols      TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers        HIGH:!aNULL:!MD5;
    client_max_body_size 80M;
    server_tokens off;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    # HTTP Server
    server {
        # Port to listen on, can also be set in IP:PORT format
        listen  8000;

        # compression-webpack-plugin 配置
        gzip on;
        gzip_min_length 1k;
        gzip_comp_level 9;
        gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
        gzip_vary on;
        # 配置禁用 gzip 条件,支持正则,此处表示 ie6 及以下不启用 gzip(因为ie低版本不支持)
        gzip_disable "MSIE [1-6]\.";

        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Headers Content-Type;
        add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

        include  "/etc/nginx/default.d/*.conf";

        location /status {
            stub_status on;
            access_log   off;
            allow 127.0.0.1;
            deny all;
        }
    }
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/ProductSubscription.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.entity;

import com.alibaba.apiopenplatform.converter.ConsumerAuthConfigConverter;
import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
import com.alibaba.apiopenplatform.support.enums.SubscriptionStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@Entity
@Table(name = "product_subscription",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"product_id", "consumer_id"}, name = "uk_product_consumer")
        })
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductSubscription extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_id", length = 64, nullable = false)
    private String productId;

    @Column(name = "consumer_id", length = 64, nullable = false)
    private String consumerId;

    @Column(name = "developer_id", length = 64)
    private String developerId;

    @Column(name = "portal_id", length = 64)
    private String portalId;

    @Enumerated(EnumType.STRING)
    @Column(name = "status", length = 32, nullable = false)
    private SubscriptionStatus status;

    @Column(name = "consumer_auth_config", columnDefinition = "json")
    @Convert(converter = ConsumerAuthConfigConverter.class)
    private ConsumerAuthConfig consumerAuthConfig;
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ConsumerRefRepository.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.repository;

import com.alibaba.apiopenplatform.entity.ConsumerRef;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import javax.swing.text.html.Option;
import java.util.List;
import java.util.Optional;

@Repository
public interface ConsumerRefRepository extends JpaRepository<ConsumerRef, Long>, JpaSpecificationExecutor<ConsumerRef> {

    List<ConsumerRef> findAllByConsumerId(String consumerId);

    @Query("SELECT c FROM ConsumerRef c WHERE c.consumerId = :consumerId AND c.gatewayType = :gatewayType AND c.gatewayConfig = :gatewayConfig")
    @Deprecated
    Optional<ConsumerRef> findConsumerRef(@Param("consumerId") String consumerId,
                                          @Param("gatewayType") GatewayType gatewayType,
                                          @Param("gatewayConfig") GatewayConfig gatewayConfig);

    Optional<ConsumerRef> findByGwConsumerId(String gwConsumerId);

    List<ConsumerRef> findAllByConsumerIdAndGatewayType(String consumerId, GatewayType gatewayType);
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/MCPConfigResult.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.dto.result;

import lombok.Builder;
import lombok.Data;
import lombok.Getter;

import java.util.List;

@Data
public class MCPConfigResult {

    protected String mcpServerName;

    protected MCPServerConfig mcpServerConfig;

    protected String tools;

    protected McpMetadata meta;

    @Data
    public static class McpMetadata {

        /**
         * 来源
         * AI网关/Higress/Nacos
         */
        private String source;

        /**
         * 服务类型
         * AI网关:HTTP(HTTP转MCP)/MCP(MCP直接代理)
         * Higress:OPEN_API(OpenAPI转MCP)/DIRECT_ROUTE(直接路由)/DATABASE(数据库)
         */
        private String createFromType;

        /**
         * HTTP/SSE
         */
        private String protocol;
    }

    @Data
    public static class MCPServerConfig {
        /**
         * for gateway
         */
        private String path;
        private List<Domain> domains;

        /**
         * for nacos
         */
        private Object rawConfig;

        private String transportMode = MCPTransportMode.REMOTE.getMode();
    }

    @Data
    @Builder
    public static class Domain {
        private String domain;
        private String protocol;
    }

    @Getter
    public enum MCPTransportMode {
        LOCAL("Local"),
        REMOTE("Remote");

        private final String mode;

        MCPTransportMode(String mode) {
            this.mode = mode;
        }
    }
}

```

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

```typescript
import { Button, Card, Typography } from "antd";
import { Link } from "react-router-dom";
import { Layout } from "../components/Layout";
import { useEffect } from "react";
import { getTokenFromCookie } from "../lib/utils";

const { Title, Paragraph } = Typography;

function HomePage() {
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const fromCookie = params.get("fromCookie");
    const token = getTokenFromCookie();
    if (fromCookie && token) {
      localStorage.setItem("access_token", token);
    }
  }, []);

  return (
    <Layout>
      <div className="text-center">
        <Title level={1} className="text-6xl font-bold text-gray-900 mb-6">
          HiMarket AI 开放平台
        </Title>
        <Paragraph className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
          低成本接入企业级AI能力,助力业务快速创新
        </Paragraph>
        <Link to="/apis">
          <Button 
            type="primary" 
            size="large" 
            className="bg-purple-600 hover:bg-purple-700 text-white px-8 py-3 text-lg"
          >
            Get started
          </Button>
        </Link>
      </div>
      
      <div className="mt-16">
        <Card className="bg-gradient-to-r from-purple-600 via-blue-600 to-purple-800 border-0">
          <div className="relative overflow-hidden">
            <div className="absolute inset-0 bg-gradient-to-r from-purple-600 via-blue-600 to-purple-800 opacity-90"></div>
            <div className="absolute inset-0 grid grid-cols-8 gap-4">
              {Array.from({ length: 32 }, (_, i) => (
                <div key={i} className="bg-white/10 rounded-full aspect-square opacity-30"></div>
              ))}
            </div>
            <div className="relative z-10 h-64 flex items-center justify-center">
              <div className="text-white text-center">
                <Title level={2} className="text-3xl font-bold mb-4 text-white">
                  探索 AI API 服务
                </Title>
                <Paragraph className="text-purple-100 text-lg">
                  丰富多样的 AI 能力,助您打造智能应用
                </Paragraph>
              </div>
            </div>
          </div>
        </Card>
      </div>
    </Layout>
  );
}

export default HomePage; 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/gateway/factory/HTTPClientFactory.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.factory;

import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

@Slf4j
public class HTTPClientFactory {

    public static RestTemplate createRestTemplate() {
        OkHttpClient okHttpClient = okHttpClient();
        // 使用OkHttp作为RestTemplate的底层客户端
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient));
    }

    public static OkHttpClient okHttpClient() {
        return new OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS)
                .readTimeout(5, TimeUnit.SECONDS)
                .writeTimeout(5, TimeUnit.SECONDS)
                .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
                .build();
    }

    public static void closeClient(RestTemplate restTemplate) {
        try {
            if (restTemplate != null) {
                ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
                if (factory instanceof OkHttp3ClientHttpRequestFactory) {
                    ((OkHttp3ClientHttpRequestFactory) factory).destroy();
                }
            }
        } catch (Exception e) {
            log.error("Error closing RestTemplate", e);
        }
    }
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/gateway/ImportGatewayParam.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.dto.params.gateway;

import cn.hutool.core.util.StrUtil;
import com.alibaba.apiopenplatform.dto.converter.InputConverter;
import com.alibaba.apiopenplatform.entity.Gateway;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class ImportGatewayParam implements InputConverter<Gateway> {

    @NotBlank(message = "网关名称不能为空")
    private String gatewayName;

    private String description;

    @NotNull(message = "网关类型不能为空")
    private GatewayType gatewayType;

    private String gatewayId;

    private APIGConfig apigConfig;

    private AdpAIGatewayConfig adpAIGatewayConfig;

    private HigressConfig higressConfig;

    @AssertTrue(message = "网关配置无效")
    private boolean isGatewayConfigValid() {
        return (gatewayType.isAPIG() && !gatewayType.equals(GatewayType.ADP_AI_GATEWAY) && apigConfig != null && StrUtil.isNotBlank(gatewayId))
                || (gatewayType.equals(GatewayType.ADP_AI_GATEWAY) && adpAIGatewayConfig != null && StrUtil.isNotBlank(gatewayId))
                || (gatewayType.isHigress() && higressConfig != null);
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/aliyunThemeToken.ts:
--------------------------------------------------------------------------------

```typescript
export default  {
  "colorPrimary": "#0064c8",
  "colorPrimaryBg": "#F0F7FF",
  "colorPrimaryBgHover": "#CAE3FD",
  "colorPrimaryBorder": "#90C0EF",
  "colorPrimaryBorderHover": "#589ADB",
  "colorPrimaryHover": "#2A7DD1",
  "colorPrimaryActive": "#0057AD",
  "colorPrimaryTextHover": "#2A7DD1",
  "colorPrimaryText": "#0064c8",
  "colorPrimaryTextActive": "#0057AD",
  "fontSize": 12,
  "borderRadius": 2,
  "fontSizeSM": 12,
  "lineHeight": 1.5,
  "lineHeightSM": 1.5,
  "wireframe": true,
  "colorInfo": "#0064c8",
  "colorBgBase": "#ffffff",
  "colorText": "rgba(0, 0, 0, 0.90)",
  "colorTextSecondary": "rgba(0, 0, 0, 0.80)",
  "colorTextTertiary": "rgba(0, 0, 0, 0.50)",
  "colorTextQuaternary": "rgba(0, 0, 0, 0.20)",
  "colorBorder": "#d9d9d9",
  "colorBorderSecondary": "#E5E5E5",
  "colorFillQuaternary": "#F7F7F7",
  "colorFillTertiary": "#F7F7F7",
  "colorFillSecondary": "#E5E5E5",
  "colorFill": "#E5E5E5",
  "colorBgLayout": "#F7F7F7",
  "colorBgSpotlight": "ffffff",
  "colorSuccess": "#23b066",
  "colorSuccessBg": "#EBFFF6",
  "colorSuccessBgHover": "#D1F4E1",
  "colorSuccessBorder": "#90DEB5",
  "colorSuccessActive": "#159953",
  "colorSuccessTextHover": "#159953",
  "colorSuccessTextActive": "#159953",
  "colorSuccessText": "#23B066",
  "colorWarning": "#f98e1a",
  "colorWarningBorder": "#FFCD96",
  "colorWarningActive": "#CF7412",
  "colorWarningTextActive": "#CF7412",
  "colorWarningBorderHover": "#F7A854",
  "colorWarningHover": "#F7A854",
  "colorError": "#e84738",
  "colorErrorBg": "#FFECEB",
  "colorErrorBgHover": "#FCCECA",
  "colorErrorBorder": "#F7AAA3",
  "colorErrorTextActive": "#C43123",
  "colorErrorActive": "#C43123",
  "colorErrorTextHover": "#ED675A",
  "colorErrorHover": "#ED675A",
  "colorInfoActive": "#0057AD",
  "colorInfoBg": "#F0F7FF",
  "colorInfoBorder": "#90C0EF",
  "colorInfoTextActive": "#0057AD",
  "colorInfoHover": "#2A7DD1",
  "colorInfoBorderHover": "#2A7DD1",
  "colorInfoTextHover": "#2A7DD1",
  "fontSizeHeading2": 24,
  "fontSizeHeading3": 20,
  "fontSizeHeading4": 16,
  "marginXXS": 4,
  "boxShadow": "      0 3px 8px 0 rgba(0, 0, 0, 0.06),      0 3px 6px -4px rgba(0, 0, 0, 0.12),      0 9px 28px 8px rgba(0, 0, 0, 0.05)    ",
  "boxShadowSecondary": "      0 3px 8px 0 rgba(0, 0, 0, 0.18),      0 3px 6px -4px rgba(0, 0, 0, 0.12),      0 9px 28px 8px rgba(0, 0, 0, 0.05)    "
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-frontend/src/aliyunThemeToken.ts:
--------------------------------------------------------------------------------

```typescript
export default  {
  "colorPrimary": "#0064c8",
  "colorPrimaryBg": "#F0F7FF",
  "colorPrimaryBgHover": "#CAE3FD",
  "colorPrimaryBorder": "#90C0EF",
  "colorPrimaryBorderHover": "#589ADB",
  "colorPrimaryHover": "#2A7DD1",
  "colorPrimaryActive": "#0057AD",
  "colorPrimaryTextHover": "#2A7DD1",
  "colorPrimaryText": "#0064c8",
  "colorPrimaryTextActive": "#0057AD",
  "fontSize": 12,
  "borderRadius": 2,
  "fontSizeSM": 12,
  "lineHeight": 1.5,
  "lineHeightSM": 1.5,
  "wireframe": true,
  "colorInfo": "#0064c8",
  "colorBgBase": "#ffffff",
  "colorText": "rgba(0, 0, 0, 0.90)",
  "colorTextSecondary": "rgba(0, 0, 0, 0.80)",
  "colorTextTertiary": "rgba(0, 0, 0, 0.50)",
  "colorTextQuaternary": "rgba(0, 0, 0, 0.20)",
  "colorBorder": "#d9d9d9",
  "colorBorderSecondary": "#E5E5E5",
  "colorFillQuaternary": "#F7F7F7",
  "colorFillTertiary": "#F7F7F7",
  "colorFillSecondary": "#E5E5E5",
  "colorFill": "#E5E5E5",
  "colorBgLayout": "#F7F7F7",
  "colorBgSpotlight": "ffffff",
  "colorSuccess": "#23b066",
  "colorSuccessBg": "#EBFFF6",
  "colorSuccessBgHover": "#D1F4E1",
  "colorSuccessBorder": "#90DEB5",
  "colorSuccessActive": "#159953",
  "colorSuccessTextHover": "#159953",
  "colorSuccessTextActive": "#159953",
  "colorSuccessText": "#23B066",
  "colorWarning": "#f98e1a",
  "colorWarningBorder": "#FFCD96",
  "colorWarningActive": "#CF7412",
  "colorWarningTextActive": "#CF7412",
  "colorWarningBorderHover": "#F7A854",
  "colorWarningHover": "#F7A854",
  "colorError": "#e84738",
  "colorErrorBg": "#FFECEB",
  "colorErrorBgHover": "#FCCECA",
  "colorErrorBorder": "#F7AAA3",
  "colorErrorTextActive": "#C43123",
  "colorErrorActive": "#C43123",
  "colorErrorTextHover": "#ED675A",
  "colorErrorHover": "#ED675A",
  "colorInfoActive": "#0057AD",
  "colorInfoBg": "#F0F7FF",
  "colorInfoBorder": "#90C0EF",
  "colorInfoTextActive": "#0057AD",
  "colorInfoHover": "#2A7DD1",
  "colorInfoBorderHover": "#2A7DD1",
  "colorInfoTextHover": "#2A7DD1",
  "fontSizeHeading2": 24,
  "fontSizeHeading3": 20,
  "fontSizeHeading4": 16,
  "marginXXS": 4,
  "boxShadow": "      0 3px 8px 0 rgba(0, 0, 0, 0.06),      0 3px 6px -4px rgba(0, 0, 0, 0.12),      0 9px 28px 8px rgba(0, 0, 0, 0.05)    ",
  "boxShadowSecondary": "      0 3px 8px 0 rgba(0, 0, 0, 0.18),      0 3px 6px -4px rgba(0, 0, 0, 0.12),      0 9px 28px 8px rgba(0, 0, 0, 0.05)    "
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/utils/IdGenerator.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.utils;

import cn.hutool.core.lang.ObjectId;

/**
 * ID生成器
 * <p>
 * 格式为: prefix + 24位字符串
 * <p>
 * 支持的ID类型:
 * - 门户ID: portal-xxxxxx
 * - API产品ID: api-xxxxxx
 * - 开发者ID: dev-xxxxxx
 * - 管理员ID: admin-xxxxxx
 * <p>
 * 注意:
 * - API ID由网关同步,不在此生成
 *
 */
public class IdGenerator {

    private static final String PORTAL_PREFIX = "portal-";
    private static final String API_PRODUCT_PREFIX = "product-";
    private static final String DEVELOPER_PREFIX = "dev-";
    private static final String CONSUMER_PREFIX = "consumer-";
    private static final String ADMINISTRATOR_PREFIX = "admin-";
    private static final String NACOS_PREFIX = "nacos-";
    private static final String HIGRESS_PREFIX = "higress-";

    public static String genHigressGatewayId() {
        return HIGRESS_PREFIX + ObjectId.next();
    }

    public static String genPortalId() {
        return PORTAL_PREFIX + ObjectId.next();
    }

    public static String genApiProductId() {
        return API_PRODUCT_PREFIX + ObjectId.next();
    }

    public static String genDeveloperId() {
        return DEVELOPER_PREFIX + ObjectId.next();
    }

    public static String genConsumerId() {
        return CONSUMER_PREFIX + ObjectId.next();
    }

    public static String genAdministratorId() {
        return ADMINISTRATOR_PREFIX + ObjectId.next();
    }

    public static String genNacosId() {
        return NACOS_PREFIX + ObjectId.next();
    }

    public static String genIdWithPrefix(String prefix) {
        return prefix + ObjectId.next();
    }
}

```

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

```java
package com.alibaba.apiopenplatform.service.gateway.client;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.function.Function;

/**
 * @author zh
 * 通用SDK客户端,解决OpenAPI未开放问题
 */
@Slf4j
public class PopGatewayClient extends GatewayClient {

    private final APIGConfig config;

    private final IAcsClient client;

    public PopGatewayClient(APIGConfig config) {
        this.config = config;
        this.client = createClient(config);
    }

    private IAcsClient createClient(APIGConfig config) {
        DefaultProfile profile = DefaultProfile.getProfile(
                config.getRegion(),
                config.getAccessKey(),
                config.getSecretKey());
        return new DefaultAcsClient(profile);
    }

    @Override
    public void close() {
        client.shutdown();
    }

    public <E> E execute(String uri, MethodType methodType, Map<String, String> queryParams,
                         Function<JSONObject, E> converter) {

        // CommonRequest
        CommonRequest request = new CommonRequest();
        request.setSysProtocol(ProtocolType.HTTPS);
        request.setSysDomain(getAPIGEndpoint(config.getRegion()));
        request.setSysVersion("2024-03-27");
        request.setSysUriPattern(uri);
        request.setSysMethod(methodType);

        // Query Parameters
        if (queryParams != null) {
            for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                request.putQueryParameter(entry.getKey(), entry.getValue());
            }
        }

        try {
            CommonResponse response = client.getCommonResponse(request);
            JSONObject data = JSONUtil.parseObj(response.getData())
                    .getJSONObject("data");

            return converter.apply(data);
        } catch (ClientException e) {
            log.error("Error executing Pop request", e);
            throw new RuntimeException(e);
        }
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/PortalResult.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.dto.result;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import com.alibaba.apiopenplatform.entity.Portal;
import com.alibaba.apiopenplatform.entity.PortalDomain;
import com.alibaba.apiopenplatform.support.enums.DomainType;
import com.alibaba.apiopenplatform.support.enums.ProtocolType;
import com.alibaba.apiopenplatform.support.portal.PortalSettingConfig;
import com.alibaba.apiopenplatform.support.portal.PortalUiConfig;
import lombok.Data;

import java.util.List;
import java.util.stream.Collectors;

@Data
public class PortalResult implements OutputConverter<PortalResult, Portal> {

    private String portalId;

    private String name;

    private String description;

    private String adminId;

    private PortalSettingConfig portalSettingConfig;

    private PortalUiConfig portalUiConfig;

    private List<PortalDomainConfig> portalDomainConfig;

    @Override
    public PortalResult convertFrom(Portal source) {
        OutputConverter.super.convertFrom(source);
        if (CollUtil.isNotEmpty(source.getPortalDomains())) {
            portalDomainConfig = source.getPortalDomains().stream().map(domain -> new PortalDomainConfig().convertFrom(domain)).collect(Collectors.toList());
        }
        return this;
    }

    @Data
    static
    class PortalDomainConfig implements OutputConverter<PortalDomainConfig, PortalDomain> {

        private String domain;

        private DomainType type;

        private ProtocolType protocol = ProtocolType.HTTP;
    }
}

```

--------------------------------------------------------------------------------
/portal-web/api-portal-admin/src/types/portal.ts:
--------------------------------------------------------------------------------

```typescript
export interface AuthCodeConfig {
  clientId: string;
  clientSecret: string;
  scopes: string;
  authorizationEndpoint: string;
  tokenEndpoint: string;
  userInfoEndpoint: string;
  jwkSetUri: string;
  // OIDC issuer地址(用于自动发现模式)
  issuer?: string;
  // 可选的身份映射配置
  identityMapping?: IdentityMapping;
}

export interface OidcConfig {
  provider: string;
  name: string;
  logoUrl?: string | null;
  enabled: boolean;
  grantType: 'AUTHORIZATION_CODE';
  authCodeConfig: AuthCodeConfig;
  identityMapping?: IdentityMapping;
}

// 第三方认证相关类型定义
export enum AuthenticationType {
  OIDC = 'OIDC',
  OAUTH2 = 'OAUTH2'
}

export enum GrantType {
  AUTHORIZATION_CODE = 'AUTHORIZATION_CODE',
  JWT_BEARER = 'JWT_BEARER'
}

export enum PublicKeyFormat {
  PEM = 'PEM',
  JWK = 'JWK'
}

export interface PublicKeyConfig {
  kid: string;
  format: PublicKeyFormat;
  algorithm: string;
  value: string;
}

export interface JwtBearerConfig {
  publicKeys: PublicKeyConfig[];
}

export interface IdentityMapping {
  userIdField?: string | null;
  userNameField?: string | null;
  emailField?: string | null;
  customFields?: { [key: string]: string } | null;
}

// OAuth2配置(使用现有格式)
export interface OAuth2Config {
  provider: string;
  name: string;
  enabled: boolean;
  grantType: GrantType;
  jwtBearerConfig?: JwtBearerConfig;
  identityMapping?: IdentityMapping;
}

// 为了UI显示方便,给配置添加类型标识的联合类型
export type ThirdPartyAuthConfig = 
  | (OidcConfig & { type: AuthenticationType.OIDC })
  | (OAuth2Config & { type: AuthenticationType.OAUTH2 })

export interface PortalSettingConfig {
  builtinAuthEnabled: boolean;
  oidcAuthEnabled: boolean;
  autoApproveDevelopers: boolean;
  autoApproveSubscriptions: boolean;
  frontendRedirectUrl: string;
  
  // 第三方认证配置(分离存储)
  oidcConfigs?: OidcConfig[];
  oauth2Configs?: OAuth2Config[];
}

export interface PortalUiConfig {
  logo: string | null;
  icon: string | null;
}

export interface PortalDomainConfig {
  domain: string;
  type: string;
  protocol: string;
}

export interface Portal {
  portalId: string;
  name: string;
  title: string;
  description: string;
  adminId: string;
  portalSettingConfig: PortalSettingConfig;
  portalUiConfig: PortalUiConfig;
  portalDomainConfig: PortalDomainConfig[];
} 

export interface Developer {
  portalId: string;
  developerId: string;
  username: string;
  status: string;
  avatarUrl?: string;
  createAt: string;
}
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Portal.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.entity;

import com.alibaba.apiopenplatform.converter.PortalSettingConfigConverter;
import com.alibaba.apiopenplatform.converter.PortalUiConfigConverter;
import com.alibaba.apiopenplatform.support.portal.PortalSettingConfig;
import com.alibaba.apiopenplatform.support.portal.PortalUiConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "portal",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"portal_id"}, name = "uk_portal_id"),
                @UniqueConstraint(columnNames = {"name", "admin_id"}, name = "uk_name_admin_id")
        })
@Data
public class Portal extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "portal_id", length = 64, nullable = false)
    private String portalId;

    @Column(name = "name", length = 64, nullable = false)
    private String name;

    @Column(name = "description", length = 256)
    private String description;

    @Column(name = "admin_id", length = 64)
    private String adminId;

    @Column(name = "portal_setting_config", columnDefinition = "json")
    @Convert(converter = PortalSettingConfigConverter.class)
    private PortalSettingConfig portalSettingConfig;

    @Column(name = "portal_ui_config", columnDefinition = "json")
    @Convert(converter = PortalUiConfigConverter.class)
    private PortalUiConfig portalUiConfig;

    @Transient
    private List<PortalDomain> portalDomains = new ArrayList<>();
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/security/DeveloperAuthenticationProvider.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.service.DeveloperService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collections;

@Component
@RequiredArgsConstructor
public class DeveloperAuthenticationProvider implements AuthenticationProvider {

    private final DeveloperService developerService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        try {
            developerService.login(username, password);
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_DEVELOPER");
            return new UsernamePasswordAuthenticationToken(username, null, Collections.singletonList(authority));
        } catch (Exception e) {
            throw new BadCredentialsException("用户名或密码错误");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
} 
```

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

```typescript
import { Modal, Radio, Button, Space } from 'antd'
import { useState } from 'react'
import { GatewayType } from '@/types'
import { GATEWAY_TYPE_LABELS } from '@/lib/constant'

interface GatewayTypeSelectorProps {
  visible: boolean
  onCancel: () => void
  onSelect: (type: GatewayType) => void
}

export default function GatewayTypeSelector({ visible, onCancel, onSelect }: GatewayTypeSelectorProps) {
  const [selectedType, setSelectedType] = useState<GatewayType>('APIG_API')

  const handleConfirm = () => {
    onSelect(selectedType)
  }

  const handleCancel = () => {
    setSelectedType('APIG_API')
    onCancel()
  }

  return (
    <Modal
      title="选择网关类型"
      open={visible}
      onCancel={handleCancel}
      footer={[
        <Button key="cancel" onClick={handleCancel}>
          取消
        </Button>,
        <Button key="confirm" type="primary" onClick={handleConfirm}>
          确定
        </Button>
      ]}
      width={500}
    >
      <div className="py-4">
        <Radio.Group 
          value={selectedType} 
          onChange={(e) => setSelectedType(e.target.value)}
          className="w-full"
        >
          <Space direction="vertical" className="w-full">
            <Radio value="APIG_API" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">{GATEWAY_TYPE_LABELS.APIG_API}</div>
                <div className="text-sm text-gray-500">阿里云 API 网关服务</div>
              </div>
            </Radio>
            <Radio value="APIG_AI" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">{GATEWAY_TYPE_LABELS.APIG_AI}</div>
                <div className="text-sm text-gray-500">阿里云 AI 网关服务</div>
              </div>
            </Radio>
            <Radio value="HIGRESS" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">{GATEWAY_TYPE_LABELS.HIGRESS}</div>
                <div className="text-sm text-gray-500">Higress 云原生网关</div>
              </div>
            </Radio>
            <Radio value="ADP_AI_GATEWAY" className="w-full p-3 border rounded-lg hover:bg-gray-50">
              <div className="ml-2">
                <div className="font-medium">{GATEWAY_TYPE_LABELS.ADP_AI_GATEWAY}</div>
                <div className="text-sm text-gray-500">专有云 AI 网关服务</div>
              </div>
            </Radio>
          </Space>
        </Radio.Group>
      </div>
    </Modal>
  )
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/NacosNamespaceResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * Nacos 命名空间结果
 */
@EqualsAndHashCode(callSuper = false)
@Data
public class NacosNamespaceResult implements OutputConverter<NacosNamespaceResult, Object> {

    private String namespaceId;
    private String namespaceName;
    private String namespaceDesc;

    @Override
    public NacosNamespaceResult convertFrom(Object source) {
        // 兼容不同SDK类型的命名空间对象,尽可能抽取常见字段
        if (source == null) {
            return this;
        }
        try {
            // 优先通过常见getter获取
            String id = invokeGetter(source, "getNamespaceId", "getNamespace", "getId");
            String name = invokeGetter(source, "getNamespaceShowName", "getNamespaceName", "getName");
            String desc = invokeGetter(source, "getNamespaceDesc", "getDescription", "getDesc");
            this.namespaceId = id != null ? id : this.namespaceId;
            this.namespaceName = name != null ? name : this.namespaceName;
            this.namespaceDesc = desc != null ? desc : this.namespaceDesc;
        } catch (Exception ignore) {
            // 回退到通用属性复制
            OutputConverter.super.convertFrom(source);
        }
        return this;
    }

    private String invokeGetter(Object obj, String... methods) {
        for (String m : methods) {
            try {
                java.lang.reflect.Method method = obj.getClass().getMethod(m);
                Object val = method.invoke(obj);
                if (val != null) {
                    return String.valueOf(val);
                }
            } catch (Exception e) {
                // ignore and continue
            }
        }
        return null;
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Product.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.entity;

import com.alibaba.apiopenplatform.converter.ProductIconConverter;
import com.alibaba.apiopenplatform.support.enums.ProductStatus;
import com.alibaba.apiopenplatform.support.enums.ProductType;
import com.alibaba.apiopenplatform.support.product.ProductIcon;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "product",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"product_id"}, name = "uk_product_id"),
                @UniqueConstraint(columnNames = {"name"}, name = "uk_name")
        })
@Data
public class Product extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_id", length = 64, nullable = false)
    private String productId;

    @Column(name = "admin_id", length = 64)
    private String adminId;

    @Column(name = "name", length = 64, nullable = false)
    private String name;

    @Column(name = "type", length = 64)
    @Enumerated(EnumType.STRING)
    private ProductType type;

    @Column(name = "description", length = 256)
    private String description;

    @Column(name = "enable_consumer_auth")
    private Boolean enableConsumerAuth;

    @Column(name = "document", columnDefinition = "longtext")
    private String document;

    @Column(name = "icon", columnDefinition = "json")
    @Convert(converter = ProductIconConverter.class)
    private ProductIcon icon;

    @Column(name = "category", length = 64)
    private String category;

    @Column(name = "status", length = 64)
    @Enumerated(EnumType.STRING)
    private ProductStatus status = ProductStatus.PENDING;

    @Column(name = "auto_approve")
    private Boolean autoApprove;
}
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/gateway/client/APIGClient.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.client;

import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.sdk.service.apig20240327.AsyncClient;
import darabonba.core.client.ClientOverrideConfiguration;
import lombok.extern.slf4j.Slf4j;

import java.util.function.Function;

@Slf4j
public class APIGClient extends GatewayClient {

    private final AsyncClient apigClient;

    public APIGClient(APIGConfig config) {
        this.apigClient = createClient(config);
    }

    @Override
    public void close() {
        if (apigClient != null) {
            apigClient.close();
        }
    }

    public <E> E execute(Function<AsyncClient, E> function) {
        try {
            return function.apply(apigClient);
        } catch (Exception e) {
            log.error("Error executing APIG request", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
        }
    }

    private AsyncClient createClient(APIGConfig config) {
        // noinspection AklessInspection
        StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                .accessKeyId(config.getAccessKey())
                .accessKeySecret(config.getSecretKey())
                .build());

        return AsyncClient.builder()
                .credentialsProvider(provider)
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                .setEndpointOverride(getAPIGEndpoint(config.getRegion()))
                ).build();
    }

}
```

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

```typescript
import React, { useEffect, useState } from 'react'
import { Card, Spin, Button, Space } from 'antd'
import { ReloadOutlined, DashboardOutlined } from '@ant-design/icons'
import { portalApi } from '@/lib/api'
import type { Portal } from '@/types'

interface PortalDashboardProps {
  portal: Portal
}

export const PortalDashboard: React.FC<PortalDashboardProps> = ({ portal }) => {
  const [dashboardUrl, setDashboardUrl] = useState('')
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const [fallback, setFallback] = useState(false)

  const fetchDashboardUrl = async () => {
    if (!portal.portalId) return
    setLoading(true)
    setError('')
    try {
      const res = await portalApi.getPortalDashboard(portal.portalId, 'Portal')
      if (!res?.data) {
        setFallback(true)
      } else {
        setDashboardUrl(res.data)
      }
    } catch (e: any) {
      setError(e?.response?.data?.message || '获取监控面板失败')
      setFallback(true)
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => {
    fetchDashboardUrl()
  }, [portal.portalId])

  if (loading) {
    return (
      <div className="flex items-center justify-center h-64">
        <Spin size="large" />
      </div>
    )
  }

  if (fallback || !dashboardUrl || error) {
    return (
      <div className="p-6">
        <div className="w-full h-[600px] flex items-center justify-center text-gray-500">
          Dashboard 发布中,敬请期待
        </div>
        <div className="mt-4 text-right">
          <Button onClick={fetchDashboardUrl} loading={loading}>刷新</Button>
        </div>
      </div>
    )
  }

  return (
    <div className="p-6 space-y-6">
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-2xl font-bold flex items-center gap-2">
            <DashboardOutlined className="text-blue-500" />
            Dashboard 监控面板
          </h2>
          <p className="text-gray-500 mt-2">实时监控 {portal.name} 的访问与性能</p>
        </div>
        <Space>
          <Button icon={<ReloadOutlined />} onClick={fetchDashboardUrl} loading={loading}>刷新</Button>
        </Space>
      </div>

      <Card title="监控面板" className="w-full">
        <div className="w-full h-[600px] border rounded-lg overflow-hidden">
          <iframe
            src={dashboardUrl}
            title={`${portal.name} Dashboard`}
            className="w-full h-full border-0"
            sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
            onError={() => setFallback(true)}
          />
        </div>
      </Card>
    </div>
  )
}



```

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

```java
package com.alibaba.apiopenplatform.service.gateway.client;

import com.aliyun.sdk.service.sls20201230.*;
import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import darabonba.core.client.ClientOverrideConfiguration;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;

import java.util.function.Function;

@Slf4j
public class SLSClient {
    private final AsyncClient slsClient;

    public SLSClient(APIGConfig config,boolean forTicket) {
        if (forTicket) {
            this.slsClient = createTicketClient(config);
        } else {
            this.slsClient = createClient(config);
        }
    }

    public void close() {
        if (slsClient != null) {
            slsClient.close();
        }
    }

    public <E> E execute(Function<AsyncClient, E> function) {
        try {
            return function.apply(slsClient);
        } catch (Exception e) {
            log.error("Error executing SLS request", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
        }
    }

    private AsyncClient createClient(APIGConfig config) {
        // noinspection AklessInspection
        StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                .accessKeyId(config.getAccessKey())
                .accessKeySecret(config.getSecretKey())
                .build());
        String endpoint = String.format("%s.log.aliyuncs.com", config.getRegion());
        return AsyncClient.builder()
                .region(config.getRegion())
                .credentialsProvider(provider)
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                .setEndpointOverride(endpoint)
                ).build();
    }
    private AsyncClient createTicketClient(APIGConfig config) {
        StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                .accessKeyId(config.getAccessKey())
                .accessKeySecret(config.getSecretKey())
                .build());
        return AsyncClient.builder()
                .region("cn-shanghai")
                .credentialsProvider(provider)
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                .setEndpointOverride("cn-shanghai.log.aliyuncs.com")
                ).build();
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Gateway.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.entity;

import com.alibaba.apiopenplatform.converter.APIGConfigConverter;
import com.alibaba.apiopenplatform.converter.AdpAIGatewayConfigConverter;
import com.alibaba.apiopenplatform.converter.HigressConfigConverter;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "gateway",
        uniqueConstraints = {
                @UniqueConstraint(columnNames = {"gateway_id"}, name = "uk_gateway_id"),
        })
@Data
public class Gateway extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "gateway_name", length = 64, nullable = false)
    private String gatewayName;

    @Enumerated(EnumType.STRING)
    @Column(name = "gateway_type", length = 32, nullable = false)
    private GatewayType gatewayType;

    @Column(name = "gateway_id", length = 64, nullable = false)
    private String gatewayId;

    @Column(name = "admin_id", length = 64, nullable = false)
    private String adminId;

    @Convert(converter = APIGConfigConverter.class)
    @Column(name = "apig_config", columnDefinition = "json")
    private APIGConfig apigConfig;

    @Convert(converter = AdpAIGatewayConfigConverter.class)
    @Column(name = "adp_ai_gateway_config", columnDefinition = "json")
    private AdpAIGatewayConfig adpAIGatewayConfig;

    @Convert(converter = HigressConfigConverter.class)
    @Column(name = "higress_config", columnDefinition = "json")
    private HigressConfig higressConfig;
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/constant/JwtConstants.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.constant;

public class JwtConstants {

    // region JWT Header

    /**
     * 算法字段
     */
    public static final String HEADER_ALG = "alg";

    /**
     * 类型字段
     */
    public static final String HEADER_TYP = "typ";

    /**
     * 密钥ID字段
     */
    public static final String HEADER_KID = "kid";
    // endregion


    // region JWT Payload

    public static final String PAYLOAD_PROVIDER = "provider";

    /**
     * 过期时间
     */
    public static final String PAYLOAD_EXP = "exp";

    /**
     * 签发时间
     */
    public static final String PAYLOAD_IAT = "iat";

    /**
     * JWT唯一标识
     */
    public static final String PAYLOAD_JTI = "jti";

    /**
     * 签发者
     */
    public static final String PAYLOAD_ISS = "iss";

    /**
     * 主题
     */
    public static final String PAYLOAD_SUB = "sub";

    /**
     * 受众
     */
    public static final String PAYLOAD_AUD = "aud";

    /**
     * 门户ID
     */
    public static final String PAYLOAD_PORTAL = "portal";
    // endregion


    // region 自定义Payload

    /**
     * 用户ID(默认身份映射字段)
     */
    public static final String PAYLOAD_USER_ID = "userId";

    /**
     * 用户名(默认身份映射字段)
     */
    public static final String PAYLOAD_USER_NAME = "name";

    /**
     * 邮箱(默认身份映射字段)
     */
    public static final String PAYLOAD_EMAIL = "email";
    // endregion


    // region OAuth2相关常量

    /**
     * JWT Bearer Grant类型
     */
    public static final String JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";

    /**
     * Token类型
     */
    public static final String TOKEN_TYPE_BEARER = "Bearer";

    /**
     * 默认Token过期时间(秒)
     */
    public static final int DEFAULT_TOKEN_EXPIRES_IN = 3600;


    /**
     * JWT Token类型
     */
    public static final String JWT_TOKEN_TYPE = "JWT";

    // endregion
}
```

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

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

interface ImportHigressModalProps {
  visible: boolean
  onCancel: () => void
  onSuccess: () => void
}

export default function ImportHigressModal({ visible, onCancel, onSuccess }: ImportHigressModalProps) {
  const [form] = Form.useForm()
  const [loading, setLoading] = useState(false)

  const handleSubmit = async (values: any) => {
    setLoading(true)
    try {
      // 构建请求参数,将 apiOptions 改为 apiConfig
      const requestData = {
        gatewayName: values.gatewayName,
        description: values.description,
        gatewayType: 'HIGRESS',
        higressConfig: {
          address: values.address,
          username: values.username,
          password: values.password,
        }
      }

      await gatewayApi.importGateway(requestData)
      message.success('导入成功!')
      handleCancel()
      onSuccess()
    } catch (error: any) {
      // message.error(error.response?.data?.message || '导入失败!')
    } finally {
      setLoading(false)
    }
  }

  const handleCancel = () => {
    form.resetFields()
    onCancel()
  }

  return (
    <Modal
      title="导入 Higress 网关"
      open={visible}
      onCancel={handleCancel}
      footer={null}
      width={600}
    >
      <Form 
        form={form} 
        layout="vertical" 
        onFinish={handleSubmit}
        preserve={false}
      >
        <Form.Item 
          label="网关名称" 
          name="gatewayName" 
          rules={[{ required: true, message: '请输入网关名称' }]}
        >
          <Input placeholder="请输入网关名称" />
        </Form.Item>

        <Form.Item 
          label="描述" 
          name="description"
        >
          <Input.TextArea placeholder="请输入网关描述(可选)" rows={3} />
        </Form.Item>

        <Form.Item 
          label="服务地址" 
          name="address" 
          rules={[{ required: true, message: '请输入服务地址' }]}
        >
          <Input placeholder="例如:higress.example.com" />
        </Form.Item>

        <Form.Item 
          label="用户名" 
          name="username" 
          // rules={[{ required: true, message: '请输入用户名' }]}
        >
          <Input placeholder="请输入用户名" />
        </Form.Item>

        <Form.Item 
          label="密码" 
          name="password" 
        >
          <Input.Password placeholder="请输入密码" />
        </Form.Item>

        <div className="flex justify-end space-x-2 pt-4">
          <Button onClick={handleCancel}>
            取消
          </Button>
          <Button type="primary" htmlType="submit" loading={loading}>
            导入
          </Button>
        </div>
      </Form>
    </Modal>
  )
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/AdpGatewayInstanceResult.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.dto.result;

import lombok.Data;

import java.util.List;

/**
 * ADP网关实例列表响应结果
 */
@Data
public class AdpGatewayInstanceResult {

    private Integer code;
    private String msg;
    private String message;
    private AdpGatewayInstanceData data;

    @Data
    public static class AdpGatewayInstanceData {
        private List<AdpGatewayInstance> records;
        private Integer total;
        private Integer size;
        private Integer current;
    }

    @Data
    public static class AdpGatewayInstance {
        private Integer status;
        private String gwInstanceId;
        private String name;
        private String deployClusterNamespace;
        private String deployClusterCode;
        private List<AccessMode> accessMode;
        private String deployClusterName;
        private String k8sServiceName;
        private String createTime;
        private String modifyTime;
        private String tid;
        private String vpcId;
        private String regionId;
        private String zoneId;
        private String deployMode;
        private String edasAppId;
        private String edasNamespaceId;
        private String k8sClusterId;
        private String k8sNamespace;
        private String instanceClass;
        private String edasAppInfos;
        private String department;
        private String resourceGroup;
        private String ingressClassName;
        private String brokerEngineType;
        private String brokerEngineVersion;
        private String deployClusterAttribute;
        private String vSwitchId;
    }

    @Data
    public static class AccessMode {
        private List<String> ips;
        private List<String> ports;
        private String accessModeType;
        private String loadBalancerNetworkType;
        private String loadBalancerAddressType;
        private String serviceName;
        private List<String> externalIps;
        private List<String> clusterIp;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/PageResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.domain.Page;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> implements OutputConverter<PageResult<T>, Page<T>> {

    private List<T> content;

    private int number;

    private int size;

    private long totalElements;

    public <S> PageResult<T> mapFrom(PageResult<S> source, Function<S, T> mapper) {
        setContent(source.getContent().stream()
                .map(mapper)
                .collect(Collectors.toList()));
        setSize(source.getSize());
        setNumber(source.getNumber());
        setTotalElements(source.getTotalElements());
        return this;
    }

    public <S> PageResult<T> convertFrom(Page<S> source, Function<S, T> mapper) {
        setContent(source.getContent().stream()
                .map(mapper)
                .collect(Collectors.toList()));
        setSize(source.getSize());
        // 由Pageable转换时修正
        setNumber(source.getNumber() + 1);
        setTotalElements(source.getTotalElements());
        return this;
    }

    public static <T> PageResult<T> empty(int pageNumber, int pageSize) {
        return PageResult.<T>builder()
                .content(new ArrayList<>())
                .number(pageNumber)
                .size(pageSize)
                .totalElements(0)
                .build();
    }

    public static <T> PageResult<T> of(List<T> content, int pageNumber, int pageSize, long total) {
        return PageResult.<T>builder()
                .content(content)
                .number(pageNumber)
                .size(pageSize)
                .totalElements(total)
                .build();
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/advice/ResponseAdvice.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.advice;

import cn.hutool.json.JSONUtil;
import com.alibaba.apiopenplatform.core.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 统一响应处理
 * <p>
 * 用于封装接口响应数据为统一格式:
 * {
 * "code": "Success",
 * "message": "操作成功",
 * "data": T
 * }
 * <p>
 * 以下情况不会被包装:
 * 1. 返回值已经是 {@link ResponseEntity}
 * 2. 返回值已经是 {@link Response}
 *
 */
@RestControllerAdvice
@Slf4j
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 排除Swagger相关路径
        Class<?> declaringClass = returnType.getDeclaringClass();
        if (declaringClass.getName().contains("org.springdoc") ||
                declaringClass.getName().contains("springfox.documentation")) {
            return false;
        }

        return !returnType.getParameterType().equals(ResponseEntity.class)
                && !returnType.getParameterType().equals(Response.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        // 设置成功响应码
        response.setStatusCode(HttpStatus.OK);

        if (body instanceof String) {
            return JSONUtil.toJsonStr(Response.ok(body));
        }
        return Response.ok(body);
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/ProductRef.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.entity;

import com.alibaba.apiopenplatform.converter.APIGRefConfigConverter;
import com.alibaba.apiopenplatform.converter.HigressRefConfigConverter;
import com.alibaba.apiopenplatform.converter.NacosRefConfigConverter;
import com.alibaba.apiopenplatform.support.enums.SourceType;
import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
import com.alibaba.apiopenplatform.support.product.HigressRefConfig;
import com.alibaba.apiopenplatform.support.product.NacosRefConfig;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;

@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "product_ref")
@Data
public class ProductRef extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "product_id", length = 64, nullable = false)
    private String productId;

    @Column(name = "gateway_id", length = 64)
    private String gatewayId;

    @Column(name = "apig_ref_config", columnDefinition = "json")
    @Convert(converter = APIGRefConfigConverter.class)
    private APIGRefConfig apigRefConfig;

    @Column(name = "adp_ai_gateway_ref_config", columnDefinition = "json")
    @Convert(converter = APIGRefConfigConverter.class)
    private APIGRefConfig adpAIGatewayRefConfig;

    @Column(name = "higress_ref_config", columnDefinition = "json")
    @Convert(converter = HigressRefConfigConverter.class)
    private HigressRefConfig higressRefConfig;

    @Column(name = "nacos_id", length = 64)
    private String nacosId;

    @Column(name = "nacos_ref_config", columnDefinition = "json")
    @Convert(converter = NacosRefConfigConverter.class)
    private NacosRefConfig nacosRefConfig;

    @Column(name = "source_type", length = 32)
    @Enumerated(EnumType.STRING)
    private SourceType sourceType;

    @Column(name = "api_config", columnDefinition = "json")
    private String apiConfig;

    @Column(name = "mcp_config", columnDefinition = "json")
    private String mcpConfig;

    @Column(name = "enabled")
    private Boolean enabled;
}

```

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

```typescript
import React, { useState } from 'react'
import { Link, useNavigate, useLocation } from 'react-router-dom'
import api from '../lib/api'
import { Form, Input, Button, Alert } from 'antd'

const Register: React.FC = () => {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const navigate = useNavigate()
  const location = useLocation()
  const searchParams = new URLSearchParams(location.search)
  const portalId = searchParams.get('portalId') || ''

  const handleRegister = async (values: { username: string; password: string; confirmPassword: string }) => {
    setError('')
    if (!values.username || !values.password || !values.confirmPassword) {
      setError('请填写所有字段')
      return
    }
    if (values.password !== values.confirmPassword) {
      setError('两次输入的密码不一致')
      return
    }
    setLoading(true)
    try {
      await api.post('/admins/init', { username: values.username, password: values.password })
      navigate('/login')
    } 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">注册 AI Portal</h2>
        <Form
          className="w-full flex flex-col gap-4"
          layout="vertical"
          onFinish={handleRegister}
        >
          <Form.Item
            name="username"
            rules={[{ required: true, message: '请输入账号' }]}
          >
            <Input placeholder="账号" autoComplete="username" size="large" />
          </Form.Item>
          <Form.Item
            name="password"
            rules={[{ required: true, message: '请输入密码' }]}
          >
            <Input.Password placeholder="密码" autoComplete="new-password" size="large" />
          </Form.Item>
          <Form.Item
            name="confirmPassword"
            rules={[{ required: true, message: '请确认密码' }]}
          >
            <Input.Password placeholder="确认密码" autoComplete="new-password" 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 className="mt-6 text-gray-400 text-sm text-center w-full">
          已有账号?<Link to="/login" className="text-indigo-500 hover:underline ml-1">登录</Link>
        </div>
      </div>
    </div>
  )
}

export default Register 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/service/PortalService.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.portal.*;
import com.alibaba.apiopenplatform.dto.result.PageResult;
import com.alibaba.apiopenplatform.dto.result.PortalResult;
import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
import org.springframework.data.domain.Pageable;

public interface PortalService {

    /**
     * 创建门户
     *
     * @param param
     * @return
     */
    PortalResult createPortal(CreatePortalParam param);

    /**
     * 查询门户
     *
     * @param portalId
     * @return
     */
    PortalResult getPortal(String portalId);

    /**
     * 检查门户是否存在
     *
     * @param portalId
     */
    void existsPortal(String portalId);

    /**
     * 查询门户列表
     *
     * @param pageable
     * @return
     */
    PageResult<PortalResult> listPortals(Pageable pageable);

    /**
     * 更新门户
     *
     * @param portalId
     * @param param
     * @return
     */
    PortalResult updatePortal(String portalId, UpdatePortalParam param);

    /**
     * 删除门户
     *
     * @param portalId
     */
    void deletePortal(String portalId);

    /**
     * 根据请求域名解析门户
     *
     * @param domain
     * @return
     */
    String resolvePortal(String domain);

    /**
     * 为门户绑定域名
     *
     * @param portalId
     * @param param
     * @return
     */
    PortalResult bindDomain(String portalId, BindDomainParam param);

    /**
     * 删除门户绑定域名
     *
     * @param portalId
     * @param domain
     * @return
     */
    PortalResult unbindDomain(String portalId, String domain);

    /**
     * 获取门户上的API产品订阅列表
     *
     * @param portalId 门户ID
     * @param param    查询参数
     * @param pageable 分页参数
     * @return PageResult of SubscriptionResult
     */
    PageResult<SubscriptionResult> listSubscriptions(String portalId, QuerySubscriptionParam param, Pageable pageable);

    /**
     * 获取默认门户
     *
     * @return
     */
    String getDefaultPortal();

    /**
     * 获取门户的Dashboard监控面板URL
     *
     * @param portalId 门户ID
     * @return Dashboard URL
     */
    String getDashboard(String portalId);
}

```

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

```typescript
import type { ReactNode } from "react";
import { Skeleton } from "antd";
import { Navigation } from "./Navigation";

interface LayoutProps {
  children: ReactNode;
  className?: string;
  loading?: boolean;
}

export function Layout({ children, className = "", loading = false }: LayoutProps) {
  return (
    <div className={`min-h-screen bg-[#f4f4f6] ${className}`}>
      <Navigation loading={loading} />
      <main className="pt-4">
        <div className="w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
          {loading ? (
            <div className="space-y-8">
              {/* 页面标题骨架屏 */}
              <div className="text-center mb-8">
                <Skeleton.Input active size="large" style={{ width: 300, height: 48, margin: '0 auto 16px' }} />
                <Skeleton.Input active size="small" style={{ width: '80%', height: 24, margin: '0 auto' }} />
              </div>
              
              {/* 搜索框骨架屏 */}
              <div className="flex justify-center mb-8">
                <div className="relative w-full max-w-2xl">
                  <Skeleton.Input active size="large" style={{ width: '100%', height: 40 }} />
                </div>
              </div>
              
              {/* 子标题骨架屏 */}
              <div className="mb-6">
                <Skeleton.Input active size="small" style={{ width: 200, height: 32 }} />
              </div>
              
              {/* 内容区域骨架屏 */}
              <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
                {Array.from({ length: 6 }).map((_, index) => (
                  <div key={index} className="h-full rounded-lg shadow-lg bg-white p-4">
                    <div className="flex items-start space-x-4">
                      <Skeleton.Avatar size={48} active />
                      <div className="flex-1 min-w-0">
                        <div className="flex items-center justify-between mb-2">
                          <Skeleton.Input active size="small" style={{ width: 120 }} />
                          <Skeleton.Input active size="small" style={{ width: 60 }} />
                        </div>
                        <Skeleton.Input active size="small" style={{ width: 80, marginBottom: 8 }} />
                        <Skeleton.Input active size="small" style={{ width: '100%', marginBottom: 12 }} />
                        <Skeleton.Input active size="small" style={{ width: '100%', marginBottom: 12 }} />
                        <div className="flex items-center justify-between">
                          <Skeleton.Input active size="small" style={{ width: 60 }} />
                          <Skeleton.Input active size="small" style={{ width: 80 }} />
                        </div>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          ) : (
            children
          )}
        </div>
      </main>
      {/* <Footer /> */}
    </div>
  );
} 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/GatewayResult.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.dto.result;

import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
import com.alibaba.apiopenplatform.entity.Gateway;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GatewayResult implements OutputConverter<GatewayResult, Gateway> {

    private String gatewayId;

    private GatewayType gatewayType;

    private String gatewayName;

    private APIGConfigResult apigConfig;

    private AdpAIGatewayConfigResult adpAIGatewayConfig;

    private HigressConfigResult higressConfig;

    private LocalDateTime createAt;

    @Override
    public GatewayResult convertFrom(Gateway source) {
        OutputConverter.super.convertFrom(source);
        if (source.getGatewayType().isAPIG() && !source.getGatewayType().equals(GatewayType.ADP_AI_GATEWAY)) {
            setApigConfig(new APIGConfigResult().convertFrom(source.getApigConfig()));
        } else if (source.getGatewayType().equals(GatewayType.ADP_AI_GATEWAY)) {
            setAdpAIGatewayConfig(new AdpAIGatewayConfigResult().convertFrom(source.getAdpAIGatewayConfig()));
        } else {
            setHigressConfig(new HigressConfigResult().convertFrom(source.getHigressConfig()));
        }
        return this;
    }

    @Data
    public static class APIGConfigResult implements OutputConverter<APIGConfigResult, APIGConfig> {
        private String region;
    }

    @Data
    public static class AdpAIGatewayConfigResult implements OutputConverter<AdpAIGatewayConfigResult, AdpAIGatewayConfig> {
        private String baseUrl;
        private Integer port;
        private String authSeed;
    }

    @Data
    public static class HigressConfigResult implements OutputConverter<HigressConfigResult, HigressConfig> {
        private String address;
        private String username;
    }
}

```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/advice/ExceptionAdvice.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.advice;

import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;
import com.alibaba.apiopenplatform.core.response.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

/**
 * 全局异常处理
 * <p>
 * 处理三类异常:
 * 1. 业务异常 {@link BusinessException}: 业务异常
 * 2. 参数校验异常 {@link MethodArgumentNotValidException}: 请求参数校验不通过
 * 3. 系统异常 {@link Exception}: 非预期的系统异常
 * <p>
 * 所有异常都会被转换为统一的响应格式:
 * {
 * "code": "错误码",
 * "message": "错误信息",
 * "data": null
 * }
 *
 */
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response<Void>> handleBusinessException(BusinessException e) {
        return ResponseEntity
                .status(e.getStatus())
                .body(Response.fail(e.getCode(), e.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Response<Void>> handleParamVerifyException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getFieldErrors().stream()
                .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
                .collect(Collectors.joining("; "));
        log.error("Validation failed", e);
        return ResponseEntity
                .status(ErrorCode.INVALID_PARAMETER.getStatus())
                .body(Response.fail(ErrorCode.INVALID_PARAMETER.name(), message));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Response<Void>> handleSystemException(Exception e) {
        log.error("System error", e);
        return ResponseEntity
                .status(ErrorCode.INTERNAL_ERROR.getStatus())
                .body(Response.fail(
                        ErrorCode.INTERNAL_ERROR.name(),
                        ErrorCode.INTERNAL_ERROR.getMessage(e.getMessage())
                ));
    }
}

```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/converter/JsonConverter.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.converter;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.apiopenplatform.support.common.Encrypted;
import com.alibaba.apiopenplatform.support.common.Encryptor;
import lombok.extern.slf4j.Slf4j;

import javax.persistence.AttributeConverter;
import java.lang.reflect.Field;

@Slf4j
public abstract class JsonConverter<T> implements AttributeConverter<T, String> {

    private final Class<T> type;

    protected JsonConverter(Class<T> type) {
        this.type = type;
    }

    @Override
    public String convertToDatabaseColumn(T attribute) {
        if (attribute == null) {
            return null;
        }

        T clonedAttribute = cloneAndEncrypt(attribute);
        return JSONUtil.toJsonStr(clonedAttribute);
    }

    @Override
    public T convertToEntityAttribute(String dbData) {
        if (dbData == null) {
            return null;
        }

        T attribute = JSONUtil.toBean(dbData, type);
        decrypt(attribute);
        return attribute;
    }

    private T cloneAndEncrypt(T original) {
        // Clone避免JPA更新数据
        T cloned = JSONUtil.toBean(JSONUtil.toJsonStr(original), type);
        handleEncryption(cloned, true);
        return cloned;
    }

    private void decrypt(T attribute) {
        handleEncryption(attribute, false);
    }

    private void handleEncryption(Object obj, boolean isEncrypt) {
        if (obj == null) {
            return;
        }

        BeanUtil.descForEach(obj.getClass(), pd -> {
            Field field = pd.getField();
            if (field == null) {
                return;
            }

            Object value = ReflectUtil.getFieldValue(obj, field);
            if (value == null) {
                return;
            }

            // 处理需要加密/解密的字段
            if (field.isAnnotationPresent(Encrypted.class) && value instanceof String) {
                String result = isEncrypt ?
                        Encryptor.encrypt((String) value) :
                        Encryptor.decrypt((String) value);
                ReflectUtil.setFieldValue(obj, field, result);
            } else if (!ClassUtil.isSimpleValueType(value.getClass())) {
                handleEncryption(value, isEncrypt);
            }
        });
    }
}

```

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

```typescript
import React, { useState, useEffect } from 'react';
import { Card, Spin, Button, Space } from 'antd';
import { ReloadOutlined, DashboardOutlined } from '@ant-design/icons';
import { apiProductApi } from '@/lib/api';
import type { ApiProduct } from '@/types/api-product';

interface ApiProductDashboardProps {
  apiProduct: ApiProduct;
}

export const ApiProductDashboard: React.FC<ApiProductDashboardProps> = ({ apiProduct }) => {
  const [dashboardUrl, setDashboardUrl] = useState<string>('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string>('');
  const [fallback, setFallback] = useState<boolean>(false);

  // 获取Dashboard URL
  const fetchDashboardUrl = async () => {
    if (!apiProduct.productId) return;
    
    setLoading(true);
    setError('');
    
    try {
      // 直接调用产品的dashboard接口获取监控面板URL
      const response = await apiProductApi.getProductDashboard(apiProduct.productId);
      if (!response?.data) {
        setFallback(true);
      } else {
        setDashboardUrl(response.data);
      }
    } catch (err: any) {
      setError(err?.response?.data?.message || '获取监控面板失败');
      setFallback(true);
    } finally {
      setLoading(false);
    }
  };

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

  const handleRefresh = () => {
    fetchDashboardUrl();
  };

  if (loading) {
    return (
      <div className="flex items-center justify-center h-64">
        <Spin size="large" />
      </div>
    );
  }

  if (fallback || !dashboardUrl || error) {
    return (
      <div className="p-6">
        <div className="w-full h-[600px] flex items-center justify-center text-gray-500">
          Dashboard 发布中,敬请期待
        </div>
        <div className="mt-4 text-right">
          <Button onClick={handleRefresh} loading={loading}>刷新</Button>
        </div>
      </div>
    );
  }

  return (
    <div className="p-6 space-y-6">
      {/* 标题和操作 */}
      <div className="flex items-center justify-between">
        <div>
          <h2 className="text-2xl font-bold flex items-center gap-2">
            <DashboardOutlined className="text-blue-500" />
            Dashboard 监控面板
          </h2>
          <p className="text-gray-500 mt-2">
            实时监控 {apiProduct.name} 的API调用情况和性能指标
          </p>
        </div>
        <Space>
          <Button 
            icon={<ReloadOutlined />} 
            onClick={handleRefresh}
            loading={loading}
          >
            刷新
          </Button>
        </Space>
      </div>

      {/* Dashboard嵌入区域 */}
      <Card title="监控面板" className="w-full">
        <div className="w-full h-[600px] border rounded-lg overflow-hidden">
          {dashboardUrl ? (
            <iframe
              src={dashboardUrl}
              title={`${apiProduct.name} Dashboard`}
              className="w-full h-full border-0"
              sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
              onError={() => setFallback(true)}
            />
          ) : (
            <div className="flex items-center justify-center h-full text-gray-500">
              加载监控面板中...
            </div>
          )}
        </div>
      </Card>


    </div>
  );
};

```

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

```typescript
import React, { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { Form, Input, Button, Card, message } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons'
import api from '../lib/api'

const Register: React.FC = () => {
  const [loading, setLoading] = useState(false)
  const navigate = useNavigate()
  // const location = useLocation()
  // const searchParams = new URLSearchParams(location.search)
  // const portalId = searchParams.get('portalId') || ''

  const handleRegister = async (values: { username: string; password: string; confirmPassword: string }) => {
    setLoading(true)
    try {
      // 这里需要根据实际API调整
      await api.post('/developers', { 
        username: values.username, 
        password: values.password, 
      })
      message.success('注册成功!')
      // 注册成功后跳转到登录页
      navigate('/login')
    } catch {
      message.error('注册失败,请重试')
    } finally {
      setLoading(false)
    }
  }

  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">注册 AI Portal - 前台</h2>
        </div>
        
        <Form
          name="register"
          onFinish={handleRegister}
          autoComplete="off"
          layout="vertical"
          size="large"
        >
          <Form.Item
            name="username"
            rules={[
              { required: true, message: '请输入账号' },
              { min: 3, message: '账号至少3个字符' }
            ]}
          >
            <Input
              prefix={<UserOutlined />}
              placeholder="账号"
              autoComplete="username"
            />
          </Form.Item>

          <Form.Item
            name="password"
            rules={[
              { required: true, message: '请输入密码' },
              { min: 6, message: '密码至少6个字符' }
            ]}
          >
            <Input.Password
              prefix={<LockOutlined />}
              placeholder="密码"
              autoComplete="new-password"
            />
          </Form.Item>

          <Form.Item
            name="confirmPassword"
            dependencies={['password']}
            rules={[
              { required: true, message: '请确认密码' },
              ({ getFieldValue }) => ({
                validator(_, value) {
                  if (!value || getFieldValue('password') === value) {
                    return Promise.resolve()
                  }
                  return Promise.reject(new Error('两次输入的密码不一致'))
                },
              }),
            ]}
          >
            <Input.Password
              prefix={<LockOutlined />}
              placeholder="确认密码"
              autoComplete="new-password"
            />
          </Form.Item>

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

        <div className="text-center text-gray-500">
          已有账号?<Link to="/login" className="text-blue-500 hover:underline">登录</Link>
        </div>
      </Card>
    </div>
  )
}

export default Register 
```

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

```typescript
import { Link, useLocation } from "react-router-dom";
import { Skeleton } from "antd";
import { UserInfo } from "./UserInfo";

interface NavigationProps {
  loading?: boolean;
}

export function Navigation({ loading = false }: NavigationProps) {
  const location = useLocation();
  
  const isActive = (path: string) => {
    if (path === '/') {
      return location.pathname === '/';
    }
    return location.pathname.startsWith(path);
  };

  const getNavLinkClass = (path: string) => {
    const baseClass = "font-medium transition-colors";
    return isActive(path) 
      ? `${baseClass} text-blue-600 border-b-2 border-blue-600 pb-1` 
      : `${baseClass} text-gray-700 hover:text-gray-900`;
  };

  return (
    <nav className="bg-[#f4f4f6] sticky top-0 z-50">
      <div className="w-full mx-auto px-4 sm:px-6 lg:px-8">
        <div className="flex justify-between items-center h-16">
          <div className="flex items-center">
            {loading ? (
              <div className="flex items-center space-x-2">
                <Skeleton.Avatar size={32} active />
                <Skeleton.Input active size="small" style={{ width: 120, height: 24 }} />
              </div>
            ) : (
              <Link to="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
                <div className="w-8 h-8 rounded-full flex items-center justify-center">
                {/* LOGO区域 */}
                <img
                  src="/logo.png"
                  alt="logo"
                  className="w-6 h-6"
                  style={{ display: "block" }}
                />
                </div>
                <span className="text-xl font-bold text-gray-900">HiMarket</span>
              </Link>
            )}
          </div>
          
          <div className="hidden md:flex items-center space-x-8 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
            {loading ? (
              <>
                <Skeleton.Input active size="small" style={{ width: 100, height: 20 }} />
                <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
                <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
                <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
              </>
            ) : (
              <>
                <Link 
                  to="/getting-started" 
                  className={getNavLinkClass('/getting-started')}
                >
                  Getting Started
                </Link>
                <Link 
                  to="/apis" 
                  className={getNavLinkClass('/apis')}
                >
                  APIs
                </Link>
                <Link 
                  to="/mcp" 
                  className={getNavLinkClass('/mcp')}
                >
                  MCP
                </Link>
              </>
            )}
          </div>
          
          <div className="flex items-center space-x-4">
            {/* <div className="hidden sm:block">
              <Input
                placeholder="Search"
                prefix={<SearchOutlined className="text-gray-400" />}
                className="w-48 lg:w-64"
                size="middle"
              />
            </div> */}
            {loading ? (
              <Skeleton.Avatar size={32} active />
            ) : (
              <UserInfo />
            )}
          </div>
        </div>
      </div>
    </nav>
  );
} 
```
Page 2/7FirstPrevNextLast