#
tokens: 48722/50000 45/349 files (page 3/9)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 9. Use http://codebase.md/higress-group/himarket?lines=true&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-dal/src/main/java/com/alibaba/apiopenplatform/entity/ProductSubscription.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.entity;
21 | 
22 | import com.alibaba.apiopenplatform.converter.ConsumerAuthConfigConverter;
23 | import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
24 | import com.alibaba.apiopenplatform.support.enums.SubscriptionStatus;
25 | import lombok.Data;
26 | import lombok.EqualsAndHashCode;
27 | 
28 | import javax.persistence.*;
29 | 
30 | @Entity
31 | @Table(name = "product_subscription",
32 |         uniqueConstraints = {
33 |                 @UniqueConstraint(columnNames = {"product_id", "consumer_id"}, name = "uk_product_consumer")
34 |         })
35 | @Data
36 | @EqualsAndHashCode(callSuper = true)
37 | public class ProductSubscription extends BaseEntity {
38 | 
39 |     @Id
40 |     @GeneratedValue(strategy = GenerationType.IDENTITY)
41 |     private Long id;
42 | 
43 |     @Column(name = "product_id", length = 64, nullable = false)
44 |     private String productId;
45 | 
46 |     @Column(name = "consumer_id", length = 64, nullable = false)
47 |     private String consumerId;
48 | 
49 |     @Column(name = "developer_id", length = 64)
50 |     private String developerId;
51 | 
52 |     @Column(name = "portal_id", length = 64)
53 |     private String portalId;
54 | 
55 |     @Enumerated(EnumType.STRING)
56 |     @Column(name = "status", length = 32, nullable = false)
57 |     private SubscriptionStatus status;
58 | 
59 |     @Column(name = "consumer_auth_config", columnDefinition = "json")
60 |     @Convert(converter = ConsumerAuthConfigConverter.class)
61 |     private ConsumerAuthConfig consumerAuthConfig;
62 | }
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/repository/ConsumerRefRepository.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.repository;
21 | 
22 | import com.alibaba.apiopenplatform.entity.ConsumerRef;
23 | import com.alibaba.apiopenplatform.support.enums.GatewayType;
24 | import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
25 | import org.springframework.data.jpa.repository.JpaRepository;
26 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
27 | import org.springframework.data.jpa.repository.Query;
28 | import org.springframework.data.repository.query.Param;
29 | import org.springframework.stereotype.Repository;
30 | 
31 | import javax.swing.text.html.Option;
32 | import java.util.List;
33 | import java.util.Optional;
34 | 
35 | @Repository
36 | public interface ConsumerRefRepository extends JpaRepository<ConsumerRef, Long>, JpaSpecificationExecutor<ConsumerRef> {
37 | 
38 |     List<ConsumerRef> findAllByConsumerId(String consumerId);
39 | 
40 |     @Query("SELECT c FROM ConsumerRef c WHERE c.consumerId = :consumerId AND c.gatewayType = :gatewayType AND c.gatewayConfig = :gatewayConfig")
41 |     @Deprecated
42 |     Optional<ConsumerRef> findConsumerRef(@Param("consumerId") String consumerId,
43 |                                           @Param("gatewayType") GatewayType gatewayType,
44 |                                           @Param("gatewayConfig") GatewayConfig gatewayConfig);
45 | 
46 |     Optional<ConsumerRef> findByGwConsumerId(String gwConsumerId);
47 | 
48 |     List<ConsumerRef> findAllByConsumerIdAndGatewayType(String consumerId, GatewayType gatewayType);
49 | }
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/MCPConfigResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import lombok.Builder;
23 | import lombok.Data;
24 | import lombok.Getter;
25 | 
26 | import java.util.List;
27 | 
28 | @Data
29 | public class MCPConfigResult {
30 | 
31 |     protected String mcpServerName;
32 | 
33 |     protected MCPServerConfig mcpServerConfig;
34 | 
35 |     protected String tools;
36 | 
37 |     protected McpMetadata meta;
38 | 
39 |     @Data
40 |     public static class McpMetadata {
41 | 
42 |         /**
43 |          * 来源
44 |          * AI网关/Higress/Nacos
45 |          */
46 |         private String source;
47 | 
48 |         /**
49 |          * 服务类型
50 |          * AI网关:HTTP(HTTP转MCP)/MCP(MCP直接代理)
51 |          * Higress:OPEN_API(OpenAPI转MCP)/DIRECT_ROUTE(直接路由)/DATABASE(数据库)
52 |          */
53 |         private String createFromType;
54 | 
55 |         /**
56 |          * HTTP/SSE
57 |          */
58 |         private String protocol;
59 |     }
60 | 
61 |     @Data
62 |     public static class MCPServerConfig {
63 |         /**
64 |          * for gateway
65 |          */
66 |         private String path;
67 |         private List<Domain> domains;
68 | 
69 |         /**
70 |          * for nacos
71 |          */
72 |         private Object rawConfig;
73 | 
74 |         private String transportMode = MCPTransportMode.REMOTE.getMode();
75 |     }
76 | 
77 |     @Data
78 |     @Builder
79 |     public static class Domain {
80 |         private String domain;
81 |         private String protocol;
82 |     }
83 | 
84 |     @Getter
85 |     public enum MCPTransportMode {
86 |         LOCAL("Local"),
87 |         REMOTE("Remote");
88 | 
89 |         private final String mode;
90 | 
91 |         MCPTransportMode(String mode) {
92 |             this.mode = mode;
93 |         }
94 |     }
95 | }
96 | 
```

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

```typescript
 1 | import { Button, Card, Typography } from "antd";
 2 | import { Link } from "react-router-dom";
 3 | import { Layout } from "../components/Layout";
 4 | import { useEffect } from "react";
 5 | import { getTokenFromCookie } from "../lib/utils";
 6 | 
 7 | const { Title, Paragraph } = Typography;
 8 | 
 9 | function HomePage() {
10 |   useEffect(() => {
11 |     const params = new URLSearchParams(window.location.search);
12 |     const fromCookie = params.get("fromCookie");
13 |     const token = getTokenFromCookie();
14 |     if (fromCookie && token) {
15 |       localStorage.setItem("access_token", token);
16 |     }
17 |   }, []);
18 | 
19 |   return (
20 |     <Layout>
21 |       <div className="text-center">
22 |         <Title level={1} className="text-6xl font-bold text-gray-900 mb-6">
23 |           HiMarket AI 开放平台
24 |         </Title>
25 |         <Paragraph className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
26 |           低成本接入企业级AI能力,助力业务快速创新
27 |         </Paragraph>
28 |         <Link to="/apis">
29 |           <Button 
30 |             type="primary" 
31 |             size="large" 
32 |             className="bg-purple-600 hover:bg-purple-700 text-white px-8 py-3 text-lg"
33 |           >
34 |             Get started
35 |           </Button>
36 |         </Link>
37 |       </div>
38 |       
39 |       <div className="mt-16">
40 |         <Card className="bg-gradient-to-r from-purple-600 via-blue-600 to-purple-800 border-0">
41 |           <div className="relative overflow-hidden">
42 |             <div className="absolute inset-0 bg-gradient-to-r from-purple-600 via-blue-600 to-purple-800 opacity-90"></div>
43 |             <div className="absolute inset-0 grid grid-cols-8 gap-4">
44 |               {Array.from({ length: 32 }, (_, i) => (
45 |                 <div key={i} className="bg-white/10 rounded-full aspect-square opacity-30"></div>
46 |               ))}
47 |             </div>
48 |             <div className="relative z-10 h-64 flex items-center justify-center">
49 |               <div className="text-white text-center">
50 |                 <Title level={2} className="text-3xl font-bold mb-4 text-white">
51 |                   探索 AI API 服务
52 |                 </Title>
53 |                 <Paragraph className="text-purple-100 text-lg">
54 |                   丰富多样的 AI 能力,助您打造智能应用
55 |                 </Paragraph>
56 |               </div>
57 |             </div>
58 |           </div>
59 |         </Card>
60 |       </div>
61 |     </Layout>
62 |   );
63 | }
64 | 
65 | export default HomePage; 
```

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

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.service.gateway.factory;
21 | 
22 | import lombok.extern.slf4j.Slf4j;
23 | import okhttp3.ConnectionPool;
24 | import okhttp3.OkHttpClient;
25 | import org.springframework.http.client.ClientHttpRequestFactory;
26 | import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
27 | import org.springframework.web.client.RestTemplate;
28 | 
29 | import java.util.concurrent.TimeUnit;
30 | 
31 | @Slf4j
32 | public class HTTPClientFactory {
33 | 
34 |     public static RestTemplate createRestTemplate() {
35 |         OkHttpClient okHttpClient = okHttpClient();
36 |         // 使用OkHttp作为RestTemplate的底层客户端
37 |         return new RestTemplate(new OkHttp3ClientHttpRequestFactory(okHttpClient));
38 |     }
39 | 
40 |     public static OkHttpClient okHttpClient() {
41 |         return new OkHttpClient.Builder()
42 |                 .connectTimeout(5, TimeUnit.SECONDS)
43 |                 .readTimeout(5, TimeUnit.SECONDS)
44 |                 .writeTimeout(5, TimeUnit.SECONDS)
45 |                 .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
46 |                 .build();
47 |     }
48 | 
49 |     public static void closeClient(RestTemplate restTemplate) {
50 |         try {
51 |             if (restTemplate != null) {
52 |                 ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
53 |                 if (factory instanceof OkHttp3ClientHttpRequestFactory) {
54 |                     ((OkHttp3ClientHttpRequestFactory) factory).destroy();
55 |                 }
56 |             }
57 |         } catch (Exception e) {
58 |             log.error("Error closing RestTemplate", e);
59 |         }
60 |     }
61 | }
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/params/gateway/ImportGatewayParam.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.params.gateway;
21 | 
22 | import cn.hutool.core.util.StrUtil;
23 | import com.alibaba.apiopenplatform.dto.converter.InputConverter;
24 | import com.alibaba.apiopenplatform.entity.Gateway;
25 | import com.alibaba.apiopenplatform.support.enums.GatewayType;
26 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
27 | import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
28 | import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
29 | import lombok.Data;
30 | 
31 | import javax.validation.Valid;
32 | import javax.validation.constraints.AssertTrue;
33 | import javax.validation.constraints.NotBlank;
34 | import javax.validation.constraints.NotNull;
35 | 
36 | @Data
37 | public class ImportGatewayParam implements InputConverter<Gateway> {
38 | 
39 |     @NotBlank(message = "网关名称不能为空")
40 |     private String gatewayName;
41 | 
42 |     private String description;
43 | 
44 |     @NotNull(message = "网关类型不能为空")
45 |     private GatewayType gatewayType;
46 | 
47 |     private String gatewayId;
48 | 
49 |     private APIGConfig apigConfig;
50 | 
51 |     private AdpAIGatewayConfig adpAIGatewayConfig;
52 | 
53 |     private HigressConfig higressConfig;
54 | 
55 |     @AssertTrue(message = "网关配置无效")
56 |     private boolean isGatewayConfigValid() {
57 |         return (gatewayType.isAPIG() && !gatewayType.equals(GatewayType.ADP_AI_GATEWAY) && apigConfig != null && StrUtil.isNotBlank(gatewayId))
58 |                 || (gatewayType.equals(GatewayType.ADP_AI_GATEWAY) && adpAIGatewayConfig != null && StrUtil.isNotBlank(gatewayId))
59 |                 || (gatewayType.isHigress() && higressConfig != null);
60 |     }
61 | }
62 | 
```

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

```typescript
 1 | export default  {
 2 |   "colorPrimary": "#0064c8",
 3 |   "colorPrimaryBg": "#F0F7FF",
 4 |   "colorPrimaryBgHover": "#CAE3FD",
 5 |   "colorPrimaryBorder": "#90C0EF",
 6 |   "colorPrimaryBorderHover": "#589ADB",
 7 |   "colorPrimaryHover": "#2A7DD1",
 8 |   "colorPrimaryActive": "#0057AD",
 9 |   "colorPrimaryTextHover": "#2A7DD1",
10 |   "colorPrimaryText": "#0064c8",
11 |   "colorPrimaryTextActive": "#0057AD",
12 |   "fontSize": 12,
13 |   "borderRadius": 2,
14 |   "fontSizeSM": 12,
15 |   "lineHeight": 1.5,
16 |   "lineHeightSM": 1.5,
17 |   "wireframe": true,
18 |   "colorInfo": "#0064c8",
19 |   "colorBgBase": "#ffffff",
20 |   "colorText": "rgba(0, 0, 0, 0.90)",
21 |   "colorTextSecondary": "rgba(0, 0, 0, 0.80)",
22 |   "colorTextTertiary": "rgba(0, 0, 0, 0.50)",
23 |   "colorTextQuaternary": "rgba(0, 0, 0, 0.20)",
24 |   "colorBorder": "#d9d9d9",
25 |   "colorBorderSecondary": "#E5E5E5",
26 |   "colorFillQuaternary": "#F7F7F7",
27 |   "colorFillTertiary": "#F7F7F7",
28 |   "colorFillSecondary": "#E5E5E5",
29 |   "colorFill": "#E5E5E5",
30 |   "colorBgLayout": "#F7F7F7",
31 |   "colorBgSpotlight": "ffffff",
32 |   "colorSuccess": "#23b066",
33 |   "colorSuccessBg": "#EBFFF6",
34 |   "colorSuccessBgHover": "#D1F4E1",
35 |   "colorSuccessBorder": "#90DEB5",
36 |   "colorSuccessActive": "#159953",
37 |   "colorSuccessTextHover": "#159953",
38 |   "colorSuccessTextActive": "#159953",
39 |   "colorSuccessText": "#23B066",
40 |   "colorWarning": "#f98e1a",
41 |   "colorWarningBorder": "#FFCD96",
42 |   "colorWarningActive": "#CF7412",
43 |   "colorWarningTextActive": "#CF7412",
44 |   "colorWarningBorderHover": "#F7A854",
45 |   "colorWarningHover": "#F7A854",
46 |   "colorError": "#e84738",
47 |   "colorErrorBg": "#FFECEB",
48 |   "colorErrorBgHover": "#FCCECA",
49 |   "colorErrorBorder": "#F7AAA3",
50 |   "colorErrorTextActive": "#C43123",
51 |   "colorErrorActive": "#C43123",
52 |   "colorErrorTextHover": "#ED675A",
53 |   "colorErrorHover": "#ED675A",
54 |   "colorInfoActive": "#0057AD",
55 |   "colorInfoBg": "#F0F7FF",
56 |   "colorInfoBorder": "#90C0EF",
57 |   "colorInfoTextActive": "#0057AD",
58 |   "colorInfoHover": "#2A7DD1",
59 |   "colorInfoBorderHover": "#2A7DD1",
60 |   "colorInfoTextHover": "#2A7DD1",
61 |   "fontSizeHeading2": 24,
62 |   "fontSizeHeading3": 20,
63 |   "fontSizeHeading4": 16,
64 |   "marginXXS": 4,
65 |   "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)    ",
66 |   "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)    "
67 | }
68 | 
```

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

```typescript
 1 | export default  {
 2 |   "colorPrimary": "#0064c8",
 3 |   "colorPrimaryBg": "#F0F7FF",
 4 |   "colorPrimaryBgHover": "#CAE3FD",
 5 |   "colorPrimaryBorder": "#90C0EF",
 6 |   "colorPrimaryBorderHover": "#589ADB",
 7 |   "colorPrimaryHover": "#2A7DD1",
 8 |   "colorPrimaryActive": "#0057AD",
 9 |   "colorPrimaryTextHover": "#2A7DD1",
10 |   "colorPrimaryText": "#0064c8",
11 |   "colorPrimaryTextActive": "#0057AD",
12 |   "fontSize": 12,
13 |   "borderRadius": 2,
14 |   "fontSizeSM": 12,
15 |   "lineHeight": 1.5,
16 |   "lineHeightSM": 1.5,
17 |   "wireframe": true,
18 |   "colorInfo": "#0064c8",
19 |   "colorBgBase": "#ffffff",
20 |   "colorText": "rgba(0, 0, 0, 0.90)",
21 |   "colorTextSecondary": "rgba(0, 0, 0, 0.80)",
22 |   "colorTextTertiary": "rgba(0, 0, 0, 0.50)",
23 |   "colorTextQuaternary": "rgba(0, 0, 0, 0.20)",
24 |   "colorBorder": "#d9d9d9",
25 |   "colorBorderSecondary": "#E5E5E5",
26 |   "colorFillQuaternary": "#F7F7F7",
27 |   "colorFillTertiary": "#F7F7F7",
28 |   "colorFillSecondary": "#E5E5E5",
29 |   "colorFill": "#E5E5E5",
30 |   "colorBgLayout": "#F7F7F7",
31 |   "colorBgSpotlight": "ffffff",
32 |   "colorSuccess": "#23b066",
33 |   "colorSuccessBg": "#EBFFF6",
34 |   "colorSuccessBgHover": "#D1F4E1",
35 |   "colorSuccessBorder": "#90DEB5",
36 |   "colorSuccessActive": "#159953",
37 |   "colorSuccessTextHover": "#159953",
38 |   "colorSuccessTextActive": "#159953",
39 |   "colorSuccessText": "#23B066",
40 |   "colorWarning": "#f98e1a",
41 |   "colorWarningBorder": "#FFCD96",
42 |   "colorWarningActive": "#CF7412",
43 |   "colorWarningTextActive": "#CF7412",
44 |   "colorWarningBorderHover": "#F7A854",
45 |   "colorWarningHover": "#F7A854",
46 |   "colorError": "#e84738",
47 |   "colorErrorBg": "#FFECEB",
48 |   "colorErrorBgHover": "#FCCECA",
49 |   "colorErrorBorder": "#F7AAA3",
50 |   "colorErrorTextActive": "#C43123",
51 |   "colorErrorActive": "#C43123",
52 |   "colorErrorTextHover": "#ED675A",
53 |   "colorErrorHover": "#ED675A",
54 |   "colorInfoActive": "#0057AD",
55 |   "colorInfoBg": "#F0F7FF",
56 |   "colorInfoBorder": "#90C0EF",
57 |   "colorInfoTextActive": "#0057AD",
58 |   "colorInfoHover": "#2A7DD1",
59 |   "colorInfoBorderHover": "#2A7DD1",
60 |   "colorInfoTextHover": "#2A7DD1",
61 |   "fontSizeHeading2": 24,
62 |   "fontSizeHeading3": 20,
63 |   "fontSizeHeading4": 16,
64 |   "marginXXS": 4,
65 |   "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)    ",
66 |   "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)    "
67 | }
68 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/utils/IdGenerator.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.core.utils;
21 | 
22 | import cn.hutool.core.lang.ObjectId;
23 | 
24 | /**
25 |  * ID生成器
26 |  * <p>
27 |  * 格式为: prefix + 24位字符串
28 |  * <p>
29 |  * 支持的ID类型:
30 |  * - 门户ID: portal-xxxxxx
31 |  * - API产品ID: api-xxxxxx
32 |  * - 开发者ID: dev-xxxxxx
33 |  * - 管理员ID: admin-xxxxxx
34 |  * <p>
35 |  * 注意:
36 |  * - API ID由网关同步,不在此生成
37 |  *
38 |  */
39 | public class IdGenerator {
40 | 
41 |     private static final String PORTAL_PREFIX = "portal-";
42 |     private static final String API_PRODUCT_PREFIX = "product-";
43 |     private static final String DEVELOPER_PREFIX = "dev-";
44 |     private static final String CONSUMER_PREFIX = "consumer-";
45 |     private static final String ADMINISTRATOR_PREFIX = "admin-";
46 |     private static final String NACOS_PREFIX = "nacos-";
47 |     private static final String HIGRESS_PREFIX = "higress-";
48 | 
49 |     public static String genHigressGatewayId() {
50 |         return HIGRESS_PREFIX + ObjectId.next();
51 |     }
52 | 
53 |     public static String genPortalId() {
54 |         return PORTAL_PREFIX + ObjectId.next();
55 |     }
56 | 
57 |     public static String genApiProductId() {
58 |         return API_PRODUCT_PREFIX + ObjectId.next();
59 |     }
60 | 
61 |     public static String genDeveloperId() {
62 |         return DEVELOPER_PREFIX + ObjectId.next();
63 |     }
64 | 
65 |     public static String genConsumerId() {
66 |         return CONSUMER_PREFIX + ObjectId.next();
67 |     }
68 | 
69 |     public static String genAdministratorId() {
70 |         return ADMINISTRATOR_PREFIX + ObjectId.next();
71 |     }
72 | 
73 |     public static String genNacosId() {
74 |         return NACOS_PREFIX + ObjectId.next();
75 |     }
76 | 
77 |     public static String genIdWithPrefix(String prefix) {
78 |         return prefix + ObjectId.next();
79 |     }
80 | }
81 | 
```

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

```java
 1 | package com.alibaba.apiopenplatform.service.gateway.client;
 2 | 
 3 | import cn.hutool.json.JSONObject;
 4 | import cn.hutool.json.JSONUtil;
 5 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
 6 | import com.aliyuncs.CommonRequest;
 7 | import com.aliyuncs.CommonResponse;
 8 | import com.aliyuncs.DefaultAcsClient;
 9 | import com.aliyuncs.IAcsClient;
10 | import com.aliyuncs.exceptions.ClientException;
11 | import com.aliyuncs.http.MethodType;
12 | import com.aliyuncs.http.ProtocolType;
13 | import com.aliyuncs.profile.DefaultProfile;
14 | import lombok.extern.slf4j.Slf4j;
15 | 
16 | import java.util.Map;
17 | import java.util.function.Function;
18 | 
19 | /**
20 |  * @author zh
21 |  * 通用SDK客户端,解决OpenAPI未开放问题
22 |  */
23 | @Slf4j
24 | public class PopGatewayClient extends GatewayClient {
25 | 
26 |     private final APIGConfig config;
27 | 
28 |     private final IAcsClient client;
29 | 
30 |     public PopGatewayClient(APIGConfig config) {
31 |         this.config = config;
32 |         this.client = createClient(config);
33 |     }
34 | 
35 |     private IAcsClient createClient(APIGConfig config) {
36 |         DefaultProfile profile = DefaultProfile.getProfile(
37 |                 config.getRegion(),
38 |                 config.getAccessKey(),
39 |                 config.getSecretKey());
40 |         return new DefaultAcsClient(profile);
41 |     }
42 | 
43 |     @Override
44 |     public void close() {
45 |         client.shutdown();
46 |     }
47 | 
48 |     public <E> E execute(String uri, MethodType methodType, Map<String, String> queryParams,
49 |                          Function<JSONObject, E> converter) {
50 | 
51 |         // CommonRequest
52 |         CommonRequest request = new CommonRequest();
53 |         request.setSysProtocol(ProtocolType.HTTPS);
54 |         request.setSysDomain(getAPIGEndpoint(config.getRegion()));
55 |         request.setSysVersion("2024-03-27");
56 |         request.setSysUriPattern(uri);
57 |         request.setSysMethod(methodType);
58 | 
59 |         // Query Parameters
60 |         if (queryParams != null) {
61 |             for (Map.Entry<String, String> entry : queryParams.entrySet()) {
62 |                 request.putQueryParameter(entry.getKey(), entry.getValue());
63 |             }
64 |         }
65 | 
66 |         try {
67 |             CommonResponse response = client.getCommonResponse(request);
68 |             JSONObject data = JSONUtil.parseObj(response.getData())
69 |                     .getJSONObject("data");
70 | 
71 |             return converter.apply(data);
72 |         } catch (ClientException e) {
73 |             log.error("Error executing Pop request", e);
74 |             throw new RuntimeException(e);
75 |         }
76 |     }
77 | }
78 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/PortalResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import cn.hutool.core.collection.CollUtil;
23 | import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
24 | import com.alibaba.apiopenplatform.entity.Portal;
25 | import com.alibaba.apiopenplatform.entity.PortalDomain;
26 | import com.alibaba.apiopenplatform.support.enums.DomainType;
27 | import com.alibaba.apiopenplatform.support.enums.ProtocolType;
28 | import com.alibaba.apiopenplatform.support.portal.PortalSettingConfig;
29 | import com.alibaba.apiopenplatform.support.portal.PortalUiConfig;
30 | import lombok.Data;
31 | 
32 | import java.util.List;
33 | import java.util.stream.Collectors;
34 | 
35 | @Data
36 | public class PortalResult implements OutputConverter<PortalResult, Portal> {
37 | 
38 |     private String portalId;
39 | 
40 |     private String name;
41 | 
42 |     private String description;
43 | 
44 |     private String adminId;
45 | 
46 |     private PortalSettingConfig portalSettingConfig;
47 | 
48 |     private PortalUiConfig portalUiConfig;
49 | 
50 |     private List<PortalDomainConfig> portalDomainConfig;
51 | 
52 |     @Override
53 |     public PortalResult convertFrom(Portal source) {
54 |         OutputConverter.super.convertFrom(source);
55 |         if (CollUtil.isNotEmpty(source.getPortalDomains())) {
56 |             portalDomainConfig = source.getPortalDomains().stream().map(domain -> new PortalDomainConfig().convertFrom(domain)).collect(Collectors.toList());
57 |         }
58 |         return this;
59 |     }
60 | 
61 |     @Data
62 |     static
63 |     class PortalDomainConfig implements OutputConverter<PortalDomainConfig, PortalDomain> {
64 | 
65 |         private String domain;
66 | 
67 |         private DomainType type;
68 | 
69 |         private ProtocolType protocol = ProtocolType.HTTP;
70 |     }
71 | }
72 | 
```

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

```typescript
  1 | export interface AuthCodeConfig {
  2 |   clientId: string;
  3 |   clientSecret: string;
  4 |   scopes: string;
  5 |   authorizationEndpoint: string;
  6 |   tokenEndpoint: string;
  7 |   userInfoEndpoint: string;
  8 |   jwkSetUri: string;
  9 |   // OIDC issuer地址(用于自动发现模式)
 10 |   issuer?: string;
 11 |   // 可选的身份映射配置
 12 |   identityMapping?: IdentityMapping;
 13 | }
 14 | 
 15 | export interface OidcConfig {
 16 |   provider: string;
 17 |   name: string;
 18 |   logoUrl?: string | null;
 19 |   enabled: boolean;
 20 |   grantType: 'AUTHORIZATION_CODE';
 21 |   authCodeConfig: AuthCodeConfig;
 22 |   identityMapping?: IdentityMapping;
 23 | }
 24 | 
 25 | // 第三方认证相关类型定义
 26 | export enum AuthenticationType {
 27 |   OIDC = 'OIDC',
 28 |   OAUTH2 = 'OAUTH2'
 29 | }
 30 | 
 31 | export enum GrantType {
 32 |   AUTHORIZATION_CODE = 'AUTHORIZATION_CODE',
 33 |   JWT_BEARER = 'JWT_BEARER'
 34 | }
 35 | 
 36 | export enum PublicKeyFormat {
 37 |   PEM = 'PEM',
 38 |   JWK = 'JWK'
 39 | }
 40 | 
 41 | export interface PublicKeyConfig {
 42 |   kid: string;
 43 |   format: PublicKeyFormat;
 44 |   algorithm: string;
 45 |   value: string;
 46 | }
 47 | 
 48 | export interface JwtBearerConfig {
 49 |   publicKeys: PublicKeyConfig[];
 50 | }
 51 | 
 52 | export interface IdentityMapping {
 53 |   userIdField?: string | null;
 54 |   userNameField?: string | null;
 55 |   emailField?: string | null;
 56 |   customFields?: { [key: string]: string } | null;
 57 | }
 58 | 
 59 | // OAuth2配置(使用现有格式)
 60 | export interface OAuth2Config {
 61 |   provider: string;
 62 |   name: string;
 63 |   enabled: boolean;
 64 |   grantType: GrantType;
 65 |   jwtBearerConfig?: JwtBearerConfig;
 66 |   identityMapping?: IdentityMapping;
 67 | }
 68 | 
 69 | // 为了UI显示方便,给配置添加类型标识的联合类型
 70 | export type ThirdPartyAuthConfig = 
 71 |   | (OidcConfig & { type: AuthenticationType.OIDC })
 72 |   | (OAuth2Config & { type: AuthenticationType.OAUTH2 })
 73 | 
 74 | export interface PortalSettingConfig {
 75 |   builtinAuthEnabled: boolean;
 76 |   oidcAuthEnabled: boolean;
 77 |   autoApproveDevelopers: boolean;
 78 |   autoApproveSubscriptions: boolean;
 79 |   frontendRedirectUrl: string;
 80 |   
 81 |   // 第三方认证配置(分离存储)
 82 |   oidcConfigs?: OidcConfig[];
 83 |   oauth2Configs?: OAuth2Config[];
 84 | }
 85 | 
 86 | export interface PortalUiConfig {
 87 |   logo: string | null;
 88 |   icon: string | null;
 89 | }
 90 | 
 91 | export interface PortalDomainConfig {
 92 |   domain: string;
 93 |   type: string;
 94 |   protocol: string;
 95 | }
 96 | 
 97 | export interface Portal {
 98 |   portalId: string;
 99 |   name: string;
100 |   title: string;
101 |   description: string;
102 |   adminId: string;
103 |   portalSettingConfig: PortalSettingConfig;
104 |   portalUiConfig: PortalUiConfig;
105 |   portalDomainConfig: PortalDomainConfig[];
106 | } 
107 | 
108 | export interface Developer {
109 |   portalId: string;
110 |   developerId: string;
111 |   username: string;
112 |   status: string;
113 |   avatarUrl?: string;
114 |   createAt: string;
115 | }
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Portal.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.entity;
21 | 
22 | import com.alibaba.apiopenplatform.converter.PortalSettingConfigConverter;
23 | import com.alibaba.apiopenplatform.converter.PortalUiConfigConverter;
24 | import com.alibaba.apiopenplatform.support.portal.PortalSettingConfig;
25 | import com.alibaba.apiopenplatform.support.portal.PortalUiConfig;
26 | import lombok.Data;
27 | import lombok.EqualsAndHashCode;
28 | 
29 | import javax.persistence.*;
30 | import java.util.ArrayList;
31 | import java.util.List;
32 | 
33 | @EqualsAndHashCode(callSuper = true)
34 | @Entity
35 | @Table(name = "portal",
36 |         uniqueConstraints = {
37 |                 @UniqueConstraint(columnNames = {"portal_id"}, name = "uk_portal_id"),
38 |                 @UniqueConstraint(columnNames = {"name", "admin_id"}, name = "uk_name_admin_id")
39 |         })
40 | @Data
41 | public class Portal extends BaseEntity {
42 |     @Id
43 |     @GeneratedValue(strategy = GenerationType.IDENTITY)
44 |     private Long id;
45 | 
46 |     @Column(name = "portal_id", length = 64, nullable = false)
47 |     private String portalId;
48 | 
49 |     @Column(name = "name", length = 64, nullable = false)
50 |     private String name;
51 | 
52 |     @Column(name = "description", length = 256)
53 |     private String description;
54 | 
55 |     @Column(name = "admin_id", length = 64)
56 |     private String adminId;
57 | 
58 |     @Column(name = "portal_setting_config", columnDefinition = "json")
59 |     @Convert(converter = PortalSettingConfigConverter.class)
60 |     private PortalSettingConfig portalSettingConfig;
61 | 
62 |     @Column(name = "portal_ui_config", columnDefinition = "json")
63 |     @Convert(converter = PortalUiConfigConverter.class)
64 |     private PortalUiConfig portalUiConfig;
65 | 
66 |     @Transient
67 |     private List<PortalDomain> portalDomains = new ArrayList<>();
68 | }
```

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

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.core.security;
21 | 
22 | import com.alibaba.apiopenplatform.service.DeveloperService;
23 | import lombok.RequiredArgsConstructor;
24 | import org.springframework.security.authentication.AuthenticationProvider;
25 | import org.springframework.security.authentication.BadCredentialsException;
26 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
27 | import org.springframework.security.core.Authentication;
28 | import org.springframework.security.core.AuthenticationException;
29 | import org.springframework.security.core.GrantedAuthority;
30 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
31 | import org.springframework.stereotype.Component;
32 | 
33 | import java.util.Collections;
34 | 
35 | @Component
36 | @RequiredArgsConstructor
37 | public class DeveloperAuthenticationProvider implements AuthenticationProvider {
38 | 
39 |     private final DeveloperService developerService;
40 | 
41 |     @Override
42 |     public Authentication authenticate(Authentication authentication) throws AuthenticationException {
43 |         String username = authentication.getName();
44 |         String password = authentication.getCredentials().toString();
45 |         try {
46 |             developerService.login(username, password);
47 |             GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_DEVELOPER");
48 |             return new UsernamePasswordAuthenticationToken(username, null, Collections.singletonList(authority));
49 |         } catch (Exception e) {
50 |             throw new BadCredentialsException("用户名或密码错误");
51 |         }
52 |     }
53 | 
54 |     @Override
55 |     public boolean supports(Class<?> authentication) {
56 |         return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
57 |     }
58 | } 
```

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

```typescript
 1 | import { Modal, Radio, Button, Space } from 'antd'
 2 | import { useState } from 'react'
 3 | import { GatewayType } from '@/types'
 4 | import { GATEWAY_TYPE_LABELS } from '@/lib/constant'
 5 | 
 6 | interface GatewayTypeSelectorProps {
 7 |   visible: boolean
 8 |   onCancel: () => void
 9 |   onSelect: (type: GatewayType) => void
10 | }
11 | 
12 | export default function GatewayTypeSelector({ visible, onCancel, onSelect }: GatewayTypeSelectorProps) {
13 |   const [selectedType, setSelectedType] = useState<GatewayType>('APIG_API')
14 | 
15 |   const handleConfirm = () => {
16 |     onSelect(selectedType)
17 |   }
18 | 
19 |   const handleCancel = () => {
20 |     setSelectedType('APIG_API')
21 |     onCancel()
22 |   }
23 | 
24 |   return (
25 |     <Modal
26 |       title="选择网关类型"
27 |       open={visible}
28 |       onCancel={handleCancel}
29 |       footer={[
30 |         <Button key="cancel" onClick={handleCancel}>
31 |           取消
32 |         </Button>,
33 |         <Button key="confirm" type="primary" onClick={handleConfirm}>
34 |           确定
35 |         </Button>
36 |       ]}
37 |       width={500}
38 |     >
39 |       <div className="py-4">
40 |         <Radio.Group 
41 |           value={selectedType} 
42 |           onChange={(e) => setSelectedType(e.target.value)}
43 |           className="w-full"
44 |         >
45 |           <Space direction="vertical" className="w-full">
46 |             <Radio value="APIG_API" className="w-full p-3 border rounded-lg hover:bg-gray-50">
47 |               <div className="ml-2">
48 |                 <div className="font-medium">{GATEWAY_TYPE_LABELS.APIG_API}</div>
49 |                 <div className="text-sm text-gray-500">阿里云 API 网关服务</div>
50 |               </div>
51 |             </Radio>
52 |             <Radio value="APIG_AI" className="w-full p-3 border rounded-lg hover:bg-gray-50">
53 |               <div className="ml-2">
54 |                 <div className="font-medium">{GATEWAY_TYPE_LABELS.APIG_AI}</div>
55 |                 <div className="text-sm text-gray-500">阿里云 AI 网关服务</div>
56 |               </div>
57 |             </Radio>
58 |             <Radio value="HIGRESS" className="w-full p-3 border rounded-lg hover:bg-gray-50">
59 |               <div className="ml-2">
60 |                 <div className="font-medium">{GATEWAY_TYPE_LABELS.HIGRESS}</div>
61 |                 <div className="text-sm text-gray-500">Higress 云原生网关</div>
62 |               </div>
63 |             </Radio>
64 |             <Radio value="ADP_AI_GATEWAY" className="w-full p-3 border rounded-lg hover:bg-gray-50">
65 |               <div className="ml-2">
66 |                 <div className="font-medium">{GATEWAY_TYPE_LABELS.ADP_AI_GATEWAY}</div>
67 |                 <div className="text-sm text-gray-500">专有云 AI 网关服务</div>
68 |               </div>
69 |             </Radio>
70 |           </Space>
71 |         </Radio.Group>
72 |       </div>
73 |     </Modal>
74 |   )
75 | }
76 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/NacosNamespaceResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
23 | import lombok.Data;
24 | import lombok.EqualsAndHashCode;
25 | 
26 | /**
27 |  * Nacos 命名空间结果
28 |  */
29 | @EqualsAndHashCode(callSuper = false)
30 | @Data
31 | public class NacosNamespaceResult implements OutputConverter<NacosNamespaceResult, Object> {
32 | 
33 |     private String namespaceId;
34 |     private String namespaceName;
35 |     private String namespaceDesc;
36 | 
37 |     @Override
38 |     public NacosNamespaceResult convertFrom(Object source) {
39 |         // 兼容不同SDK类型的命名空间对象,尽可能抽取常见字段
40 |         if (source == null) {
41 |             return this;
42 |         }
43 |         try {
44 |             // 优先通过常见getter获取
45 |             String id = invokeGetter(source, "getNamespaceId", "getNamespace", "getId");
46 |             String name = invokeGetter(source, "getNamespaceShowName", "getNamespaceName", "getName");
47 |             String desc = invokeGetter(source, "getNamespaceDesc", "getDescription", "getDesc");
48 |             this.namespaceId = id != null ? id : this.namespaceId;
49 |             this.namespaceName = name != null ? name : this.namespaceName;
50 |             this.namespaceDesc = desc != null ? desc : this.namespaceDesc;
51 |         } catch (Exception ignore) {
52 |             // 回退到通用属性复制
53 |             OutputConverter.super.convertFrom(source);
54 |         }
55 |         return this;
56 |     }
57 | 
58 |     private String invokeGetter(Object obj, String... methods) {
59 |         for (String m : methods) {
60 |             try {
61 |                 java.lang.reflect.Method method = obj.getClass().getMethod(m);
62 |                 Object val = method.invoke(obj);
63 |                 if (val != null) {
64 |                     return String.valueOf(val);
65 |                 }
66 |             } catch (Exception e) {
67 |                 // ignore and continue
68 |             }
69 |         }
70 |         return null;
71 |     }
72 | }
73 | 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Product.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.entity;
21 | 
22 | import com.alibaba.apiopenplatform.converter.ProductIconConverter;
23 | import com.alibaba.apiopenplatform.support.enums.ProductStatus;
24 | import com.alibaba.apiopenplatform.support.enums.ProductType;
25 | import com.alibaba.apiopenplatform.support.product.ProductIcon;
26 | import lombok.Data;
27 | import lombok.EqualsAndHashCode;
28 | 
29 | import javax.persistence.*;
30 | 
31 | @EqualsAndHashCode(callSuper = true)
32 | @Entity
33 | @Table(name = "product",
34 |         uniqueConstraints = {
35 |                 @UniqueConstraint(columnNames = {"product_id"}, name = "uk_product_id"),
36 |                 @UniqueConstraint(columnNames = {"name"}, name = "uk_name")
37 |         })
38 | @Data
39 | public class Product extends BaseEntity {
40 |     @Id
41 |     @GeneratedValue(strategy = GenerationType.IDENTITY)
42 |     private Long id;
43 | 
44 |     @Column(name = "product_id", length = 64, nullable = false)
45 |     private String productId;
46 | 
47 |     @Column(name = "admin_id", length = 64)
48 |     private String adminId;
49 | 
50 |     @Column(name = "name", length = 64, nullable = false)
51 |     private String name;
52 | 
53 |     @Column(name = "type", length = 64)
54 |     @Enumerated(EnumType.STRING)
55 |     private ProductType type;
56 | 
57 |     @Column(name = "description", length = 256)
58 |     private String description;
59 | 
60 |     @Column(name = "enable_consumer_auth")
61 |     private Boolean enableConsumerAuth;
62 | 
63 |     @Column(name = "document", columnDefinition = "longtext")
64 |     private String document;
65 | 
66 |     @Column(name = "icon", columnDefinition = "json")
67 |     @Convert(converter = ProductIconConverter.class)
68 |     private ProductIcon icon;
69 | 
70 |     @Column(name = "category", length = 64)
71 |     private String category;
72 | 
73 |     @Column(name = "status", length = 64)
74 |     @Enumerated(EnumType.STRING)
75 |     private ProductStatus status = ProductStatus.PENDING;
76 | 
77 |     @Column(name = "auto_approve")
78 |     private Boolean autoApprove;
79 | }
```

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

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.service.gateway.client;
21 | 
22 | import com.alibaba.apiopenplatform.core.exception.BusinessException;
23 | import com.alibaba.apiopenplatform.core.exception.ErrorCode;
24 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
25 | import com.aliyun.auth.credentials.Credential;
26 | import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
27 | import com.aliyun.sdk.service.apig20240327.AsyncClient;
28 | import darabonba.core.client.ClientOverrideConfiguration;
29 | import lombok.extern.slf4j.Slf4j;
30 | 
31 | import java.util.function.Function;
32 | 
33 | @Slf4j
34 | public class APIGClient extends GatewayClient {
35 | 
36 |     private final AsyncClient apigClient;
37 | 
38 |     public APIGClient(APIGConfig config) {
39 |         this.apigClient = createClient(config);
40 |     }
41 | 
42 |     @Override
43 |     public void close() {
44 |         if (apigClient != null) {
45 |             apigClient.close();
46 |         }
47 |     }
48 | 
49 |     public <E> E execute(Function<AsyncClient, E> function) {
50 |         try {
51 |             return function.apply(apigClient);
52 |         } catch (Exception e) {
53 |             log.error("Error executing APIG request", e);
54 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
55 |         }
56 |     }
57 | 
58 |     private AsyncClient createClient(APIGConfig config) {
59 |         // noinspection AklessInspection
60 |         StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
61 |                 .accessKeyId(config.getAccessKey())
62 |                 .accessKeySecret(config.getSecretKey())
63 |                 .build());
64 | 
65 |         return AsyncClient.builder()
66 |                 .credentialsProvider(provider)
67 |                 .overrideConfiguration(
68 |                         ClientOverrideConfiguration.create()
69 |                                 .setEndpointOverride(getAPIGEndpoint(config.getRegion()))
70 |                 ).build();
71 |     }
72 | 
73 | }
```

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

```typescript
 1 | import React, { useEffect, useState } from 'react'
 2 | import { Card, Spin, Button, Space } from 'antd'
 3 | import { ReloadOutlined, DashboardOutlined } from '@ant-design/icons'
 4 | import { portalApi } from '@/lib/api'
 5 | import type { Portal } from '@/types'
 6 | 
 7 | interface PortalDashboardProps {
 8 |   portal: Portal
 9 | }
10 | 
11 | export const PortalDashboard: React.FC<PortalDashboardProps> = ({ portal }) => {
12 |   const [dashboardUrl, setDashboardUrl] = useState('')
13 |   const [loading, setLoading] = useState(false)
14 |   const [error, setError] = useState('')
15 |   const [fallback, setFallback] = useState(false)
16 | 
17 |   const fetchDashboardUrl = async () => {
18 |     if (!portal.portalId) return
19 |     setLoading(true)
20 |     setError('')
21 |     try {
22 |       const res = await portalApi.getPortalDashboard(portal.portalId, 'Portal')
23 |       if (!res?.data) {
24 |         setFallback(true)
25 |       } else {
26 |         setDashboardUrl(res.data)
27 |       }
28 |     } catch (e: any) {
29 |       setError(e?.response?.data?.message || '获取监控面板失败')
30 |       setFallback(true)
31 |     } finally {
32 |       setLoading(false)
33 |     }
34 |   }
35 | 
36 |   useEffect(() => {
37 |     fetchDashboardUrl()
38 |   }, [portal.portalId])
39 | 
40 |   if (loading) {
41 |     return (
42 |       <div className="flex items-center justify-center h-64">
43 |         <Spin size="large" />
44 |       </div>
45 |     )
46 |   }
47 | 
48 |   if (fallback || !dashboardUrl || error) {
49 |     return (
50 |       <div className="p-6">
51 |         <div className="w-full h-[600px] flex items-center justify-center text-gray-500">
52 |           Dashboard 发布中,敬请期待
53 |         </div>
54 |         <div className="mt-4 text-right">
55 |           <Button onClick={fetchDashboardUrl} loading={loading}>刷新</Button>
56 |         </div>
57 |       </div>
58 |     )
59 |   }
60 | 
61 |   return (
62 |     <div className="p-6 space-y-6">
63 |       <div className="flex items-center justify-between">
64 |         <div>
65 |           <h2 className="text-2xl font-bold flex items-center gap-2">
66 |             <DashboardOutlined className="text-blue-500" />
67 |             Dashboard 监控面板
68 |           </h2>
69 |           <p className="text-gray-500 mt-2">实时监控 {portal.name} 的访问与性能</p>
70 |         </div>
71 |         <Space>
72 |           <Button icon={<ReloadOutlined />} onClick={fetchDashboardUrl} loading={loading}>刷新</Button>
73 |         </Space>
74 |       </div>
75 | 
76 |       <Card title="监控面板" className="w-full">
77 |         <div className="w-full h-[600px] border rounded-lg overflow-hidden">
78 |           <iframe
79 |             src={dashboardUrl}
80 |             title={`${portal.name} Dashboard`}
81 |             className="w-full h-full border-0"
82 |             sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
83 |             onError={() => setFallback(true)}
84 |           />
85 |         </div>
86 |       </Card>
87 |     </div>
88 |   )
89 | }
90 | 
91 | 
92 | 
```

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

```java
 1 | package com.alibaba.apiopenplatform.service.gateway.client;
 2 | 
 3 | import com.aliyun.sdk.service.sls20201230.*;
 4 | import com.aliyun.auth.credentials.Credential;
 5 | import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
 6 | import darabonba.core.client.ClientOverrideConfiguration;
 7 | import lombok.extern.slf4j.Slf4j;
 8 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
 9 | import com.alibaba.apiopenplatform.core.exception.BusinessException;
10 | import com.alibaba.apiopenplatform.core.exception.ErrorCode;
11 | 
12 | import java.util.function.Function;
13 | 
14 | @Slf4j
15 | public class SLSClient {
16 |     private final AsyncClient slsClient;
17 | 
18 |     public SLSClient(APIGConfig config,boolean forTicket) {
19 |         if (forTicket) {
20 |             this.slsClient = createTicketClient(config);
21 |         } else {
22 |             this.slsClient = createClient(config);
23 |         }
24 |     }
25 | 
26 |     public void close() {
27 |         if (slsClient != null) {
28 |             slsClient.close();
29 |         }
30 |     }
31 | 
32 |     public <E> E execute(Function<AsyncClient, E> function) {
33 |         try {
34 |             return function.apply(slsClient);
35 |         } catch (Exception e) {
36 |             log.error("Error executing SLS request", e);
37 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
38 |         }
39 |     }
40 | 
41 |     private AsyncClient createClient(APIGConfig config) {
42 |         // noinspection AklessInspection
43 |         StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
44 |                 .accessKeyId(config.getAccessKey())
45 |                 .accessKeySecret(config.getSecretKey())
46 |                 .build());
47 |         String endpoint = String.format("%s.log.aliyuncs.com", config.getRegion());
48 |         return AsyncClient.builder()
49 |                 .region(config.getRegion())
50 |                 .credentialsProvider(provider)
51 |                 .overrideConfiguration(
52 |                         ClientOverrideConfiguration.create()
53 |                                 .setEndpointOverride(endpoint)
54 |                 ).build();
55 |     }
56 |     private AsyncClient createTicketClient(APIGConfig config) {
57 |         StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
58 |                 .accessKeyId(config.getAccessKey())
59 |                 .accessKeySecret(config.getSecretKey())
60 |                 .build());
61 |         return AsyncClient.builder()
62 |                 .region("cn-shanghai")
63 |                 .credentialsProvider(provider)
64 |                 .overrideConfiguration(
65 |                         ClientOverrideConfiguration.create()
66 |                                 .setEndpointOverride("cn-shanghai.log.aliyuncs.com")
67 |                 ).build();
68 |     }
69 | }
70 | 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/Gateway.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.entity;
21 | 
22 | import com.alibaba.apiopenplatform.converter.APIGConfigConverter;
23 | import com.alibaba.apiopenplatform.converter.AdpAIGatewayConfigConverter;
24 | import com.alibaba.apiopenplatform.converter.HigressConfigConverter;
25 | import com.alibaba.apiopenplatform.support.enums.GatewayType;
26 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
27 | import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
28 | import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
29 | import lombok.Data;
30 | import lombok.EqualsAndHashCode;
31 | 
32 | import javax.persistence.*;
33 | 
34 | @EqualsAndHashCode(callSuper = true)
35 | @Entity
36 | @Table(name = "gateway",
37 |         uniqueConstraints = {
38 |                 @UniqueConstraint(columnNames = {"gateway_id"}, name = "uk_gateway_id"),
39 |         })
40 | @Data
41 | public class Gateway extends BaseEntity {
42 | 
43 |     @Id
44 |     @GeneratedValue(strategy = GenerationType.IDENTITY)
45 |     private Long id;
46 | 
47 |     @Column(name = "gateway_name", length = 64, nullable = false)
48 |     private String gatewayName;
49 | 
50 |     @Enumerated(EnumType.STRING)
51 |     @Column(name = "gateway_type", length = 32, nullable = false)
52 |     private GatewayType gatewayType;
53 | 
54 |     @Column(name = "gateway_id", length = 64, nullable = false)
55 |     private String gatewayId;
56 | 
57 |     @Column(name = "admin_id", length = 64, nullable = false)
58 |     private String adminId;
59 | 
60 |     @Convert(converter = APIGConfigConverter.class)
61 |     @Column(name = "apig_config", columnDefinition = "json")
62 |     private APIGConfig apigConfig;
63 | 
64 |     @Convert(converter = AdpAIGatewayConfigConverter.class)
65 |     @Column(name = "adp_ai_gateway_config", columnDefinition = "json")
66 |     private AdpAIGatewayConfig adpAIGatewayConfig;
67 | 
68 |     @Convert(converter = HigressConfigConverter.class)
69 |     @Column(name = "higress_config", columnDefinition = "json")
70 |     private HigressConfig higressConfig;
71 | }
72 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/constant/JwtConstants.java:
--------------------------------------------------------------------------------

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.core.constant;
 21 | 
 22 | public class JwtConstants {
 23 | 
 24 |     // region JWT Header
 25 | 
 26 |     /**
 27 |      * 算法字段
 28 |      */
 29 |     public static final String HEADER_ALG = "alg";
 30 | 
 31 |     /**
 32 |      * 类型字段
 33 |      */
 34 |     public static final String HEADER_TYP = "typ";
 35 | 
 36 |     /**
 37 |      * 密钥ID字段
 38 |      */
 39 |     public static final String HEADER_KID = "kid";
 40 |     // endregion
 41 | 
 42 | 
 43 |     // region JWT Payload
 44 | 
 45 |     public static final String PAYLOAD_PROVIDER = "provider";
 46 | 
 47 |     /**
 48 |      * 过期时间
 49 |      */
 50 |     public static final String PAYLOAD_EXP = "exp";
 51 | 
 52 |     /**
 53 |      * 签发时间
 54 |      */
 55 |     public static final String PAYLOAD_IAT = "iat";
 56 | 
 57 |     /**
 58 |      * JWT唯一标识
 59 |      */
 60 |     public static final String PAYLOAD_JTI = "jti";
 61 | 
 62 |     /**
 63 |      * 签发者
 64 |      */
 65 |     public static final String PAYLOAD_ISS = "iss";
 66 | 
 67 |     /**
 68 |      * 主题
 69 |      */
 70 |     public static final String PAYLOAD_SUB = "sub";
 71 | 
 72 |     /**
 73 |      * 受众
 74 |      */
 75 |     public static final String PAYLOAD_AUD = "aud";
 76 | 
 77 |     /**
 78 |      * 门户ID
 79 |      */
 80 |     public static final String PAYLOAD_PORTAL = "portal";
 81 |     // endregion
 82 | 
 83 | 
 84 |     // region 自定义Payload
 85 | 
 86 |     /**
 87 |      * 用户ID(默认身份映射字段)
 88 |      */
 89 |     public static final String PAYLOAD_USER_ID = "userId";
 90 | 
 91 |     /**
 92 |      * 用户名(默认身份映射字段)
 93 |      */
 94 |     public static final String PAYLOAD_USER_NAME = "name";
 95 | 
 96 |     /**
 97 |      * 邮箱(默认身份映射字段)
 98 |      */
 99 |     public static final String PAYLOAD_EMAIL = "email";
100 |     // endregion
101 | 
102 | 
103 |     // region OAuth2相关常量
104 | 
105 |     /**
106 |      * JWT Bearer Grant类型
107 |      */
108 |     public static final String JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
109 | 
110 |     /**
111 |      * Token类型
112 |      */
113 |     public static final String TOKEN_TYPE_BEARER = "Bearer";
114 | 
115 |     /**
116 |      * 默认Token过期时间(秒)
117 |      */
118 |     public static final int DEFAULT_TOKEN_EXPIRES_IN = 3600;
119 | 
120 | 
121 |     /**
122 |      * JWT Token类型
123 |      */
124 |     public static final String JWT_TOKEN_TYPE = "JWT";
125 | 
126 |     // endregion
127 | }
```

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

```typescript
  1 | import { useState } from 'react'
  2 | import { Button, Modal, Form, Input, message } from 'antd'
  3 | import { gatewayApi } from '@/lib/api'
  4 | 
  5 | interface ImportHigressModalProps {
  6 |   visible: boolean
  7 |   onCancel: () => void
  8 |   onSuccess: () => void
  9 | }
 10 | 
 11 | export default function ImportHigressModal({ visible, onCancel, onSuccess }: ImportHigressModalProps) {
 12 |   const [form] = Form.useForm()
 13 |   const [loading, setLoading] = useState(false)
 14 | 
 15 |   const handleSubmit = async (values: any) => {
 16 |     setLoading(true)
 17 |     try {
 18 |       // 构建请求参数,将 apiOptions 改为 apiConfig
 19 |       const requestData = {
 20 |         gatewayName: values.gatewayName,
 21 |         description: values.description,
 22 |         gatewayType: 'HIGRESS',
 23 |         higressConfig: {
 24 |           address: values.address,
 25 |           username: values.username,
 26 |           password: values.password,
 27 |         }
 28 |       }
 29 | 
 30 |       await gatewayApi.importGateway(requestData)
 31 |       message.success('导入成功!')
 32 |       handleCancel()
 33 |       onSuccess()
 34 |     } catch (error: any) {
 35 |       // message.error(error.response?.data?.message || '导入失败!')
 36 |     } finally {
 37 |       setLoading(false)
 38 |     }
 39 |   }
 40 | 
 41 |   const handleCancel = () => {
 42 |     form.resetFields()
 43 |     onCancel()
 44 |   }
 45 | 
 46 |   return (
 47 |     <Modal
 48 |       title="导入 Higress 网关"
 49 |       open={visible}
 50 |       onCancel={handleCancel}
 51 |       footer={null}
 52 |       width={600}
 53 |     >
 54 |       <Form 
 55 |         form={form} 
 56 |         layout="vertical" 
 57 |         onFinish={handleSubmit}
 58 |         preserve={false}
 59 |       >
 60 |         <Form.Item 
 61 |           label="网关名称" 
 62 |           name="gatewayName" 
 63 |           rules={[{ required: true, message: '请输入网关名称' }]}
 64 |         >
 65 |           <Input placeholder="请输入网关名称" />
 66 |         </Form.Item>
 67 | 
 68 |         <Form.Item 
 69 |           label="描述" 
 70 |           name="description"
 71 |         >
 72 |           <Input.TextArea placeholder="请输入网关描述(可选)" rows={3} />
 73 |         </Form.Item>
 74 | 
 75 |         <Form.Item 
 76 |           label="服务地址" 
 77 |           name="address" 
 78 |           rules={[{ required: true, message: '请输入服务地址' }]}
 79 |         >
 80 |           <Input placeholder="例如:higress.example.com" />
 81 |         </Form.Item>
 82 | 
 83 |         <Form.Item 
 84 |           label="用户名" 
 85 |           name="username" 
 86 |           // rules={[{ required: true, message: '请输入用户名' }]}
 87 |         >
 88 |           <Input placeholder="请输入用户名" />
 89 |         </Form.Item>
 90 | 
 91 |         <Form.Item 
 92 |           label="密码" 
 93 |           name="password" 
 94 |         >
 95 |           <Input.Password placeholder="请输入密码" />
 96 |         </Form.Item>
 97 | 
 98 |         <div className="flex justify-end space-x-2 pt-4">
 99 |           <Button onClick={handleCancel}>
100 |             取消
101 |           </Button>
102 |           <Button type="primary" htmlType="submit" loading={loading}>
103 |             导入
104 |           </Button>
105 |         </div>
106 |       </Form>
107 |     </Modal>
108 |   )
109 | }
110 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/AdpGatewayInstanceResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the specific
16 |  * language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import lombok.Data;
23 | 
24 | import java.util.List;
25 | 
26 | /**
27 |  * ADP网关实例列表响应结果
28 |  */
29 | @Data
30 | public class AdpGatewayInstanceResult {
31 | 
32 |     private Integer code;
33 |     private String msg;
34 |     private String message;
35 |     private AdpGatewayInstanceData data;
36 | 
37 |     @Data
38 |     public static class AdpGatewayInstanceData {
39 |         private List<AdpGatewayInstance> records;
40 |         private Integer total;
41 |         private Integer size;
42 |         private Integer current;
43 |     }
44 | 
45 |     @Data
46 |     public static class AdpGatewayInstance {
47 |         private Integer status;
48 |         private String gwInstanceId;
49 |         private String name;
50 |         private String deployClusterNamespace;
51 |         private String deployClusterCode;
52 |         private List<AccessMode> accessMode;
53 |         private String deployClusterName;
54 |         private String k8sServiceName;
55 |         private String createTime;
56 |         private String modifyTime;
57 |         private String tid;
58 |         private String vpcId;
59 |         private String regionId;
60 |         private String zoneId;
61 |         private String deployMode;
62 |         private String edasAppId;
63 |         private String edasNamespaceId;
64 |         private String k8sClusterId;
65 |         private String k8sNamespace;
66 |         private String instanceClass;
67 |         private String edasAppInfos;
68 |         private String department;
69 |         private String resourceGroup;
70 |         private String ingressClassName;
71 |         private String brokerEngineType;
72 |         private String brokerEngineVersion;
73 |         private String deployClusterAttribute;
74 |         private String vSwitchId;
75 |     }
76 | 
77 |     @Data
78 |     public static class AccessMode {
79 |         private List<String> ips;
80 |         private List<String> ports;
81 |         private String accessModeType;
82 |         private String loadBalancerNetworkType;
83 |         private String loadBalancerAddressType;
84 |         private String serviceName;
85 |         private List<String> externalIps;
86 |         private List<String> clusterIp;
87 |     }
88 | }
89 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/PageResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
23 | import lombok.AllArgsConstructor;
24 | import lombok.Builder;
25 | import lombok.Data;
26 | import lombok.NoArgsConstructor;
27 | import org.springframework.data.domain.Page;
28 | 
29 | import java.util.ArrayList;
30 | import java.util.List;
31 | import java.util.function.Function;
32 | import java.util.stream.Collectors;
33 | 
34 | @Data
35 | @Builder
36 | @NoArgsConstructor
37 | @AllArgsConstructor
38 | public class PageResult<T> implements OutputConverter<PageResult<T>, Page<T>> {
39 | 
40 |     private List<T> content;
41 | 
42 |     private int number;
43 | 
44 |     private int size;
45 | 
46 |     private long totalElements;
47 | 
48 |     public <S> PageResult<T> mapFrom(PageResult<S> source, Function<S, T> mapper) {
49 |         setContent(source.getContent().stream()
50 |                 .map(mapper)
51 |                 .collect(Collectors.toList()));
52 |         setSize(source.getSize());
53 |         setNumber(source.getNumber());
54 |         setTotalElements(source.getTotalElements());
55 |         return this;
56 |     }
57 | 
58 |     public <S> PageResult<T> convertFrom(Page<S> source, Function<S, T> mapper) {
59 |         setContent(source.getContent().stream()
60 |                 .map(mapper)
61 |                 .collect(Collectors.toList()));
62 |         setSize(source.getSize());
63 |         // 由Pageable转换时修正
64 |         setNumber(source.getNumber() + 1);
65 |         setTotalElements(source.getTotalElements());
66 |         return this;
67 |     }
68 | 
69 |     public static <T> PageResult<T> empty(int pageNumber, int pageSize) {
70 |         return PageResult.<T>builder()
71 |                 .content(new ArrayList<>())
72 |                 .number(pageNumber)
73 |                 .size(pageSize)
74 |                 .totalElements(0)
75 |                 .build();
76 |     }
77 | 
78 |     public static <T> PageResult<T> of(List<T> content, int pageNumber, int pageSize, long total) {
79 |         return PageResult.<T>builder()
80 |                 .content(content)
81 |                 .number(pageNumber)
82 |                 .size(pageSize)
83 |                 .totalElements(total)
84 |                 .build();
85 |     }
86 | }
87 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/advice/ResponseAdvice.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.core.advice;
21 | 
22 | import cn.hutool.json.JSONUtil;
23 | import com.alibaba.apiopenplatform.core.response.Response;
24 | import lombok.extern.slf4j.Slf4j;
25 | import org.springframework.core.MethodParameter;
26 | import org.springframework.http.HttpStatus;
27 | import org.springframework.http.MediaType;
28 | import org.springframework.http.ResponseEntity;
29 | import org.springframework.http.converter.HttpMessageConverter;
30 | import org.springframework.http.server.ServerHttpRequest;
31 | import org.springframework.http.server.ServerHttpResponse;
32 | import org.springframework.web.bind.annotation.RestControllerAdvice;
33 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
34 | 
35 | /**
36 |  * 统一响应处理
37 |  * <p>
38 |  * 用于封装接口响应数据为统一格式:
39 |  * {
40 |  * "code": "Success",
41 |  * "message": "操作成功",
42 |  * "data": T
43 |  * }
44 |  * <p>
45 |  * 以下情况不会被包装:
46 |  * 1. 返回值已经是 {@link ResponseEntity}
47 |  * 2. 返回值已经是 {@link Response}
48 |  *
49 |  */
50 | @RestControllerAdvice
51 | @Slf4j
52 | public class ResponseAdvice implements ResponseBodyAdvice<Object> {
53 | 
54 |     @Override
55 |     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
56 |         // 排除Swagger相关路径
57 |         Class<?> declaringClass = returnType.getDeclaringClass();
58 |         if (declaringClass.getName().contains("org.springdoc") ||
59 |                 declaringClass.getName().contains("springfox.documentation")) {
60 |             return false;
61 |         }
62 | 
63 |         return !returnType.getParameterType().equals(ResponseEntity.class)
64 |                 && !returnType.getParameterType().equals(Response.class);
65 |     }
66 | 
67 |     @Override
68 |     public Object beforeBodyWrite(Object body, MethodParameter returnType,
69 |                                   MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
70 |                                   ServerHttpRequest request, ServerHttpResponse response) {
71 |         // 设置成功响应码
72 |         response.setStatusCode(HttpStatus.OK);
73 | 
74 |         if (body instanceof String) {
75 |             return JSONUtil.toJsonStr(Response.ok(body));
76 |         }
77 |         return Response.ok(body);
78 |     }
79 | }
80 | 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/entity/ProductRef.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.entity;
21 | 
22 | import com.alibaba.apiopenplatform.converter.APIGRefConfigConverter;
23 | import com.alibaba.apiopenplatform.converter.HigressRefConfigConverter;
24 | import com.alibaba.apiopenplatform.converter.NacosRefConfigConverter;
25 | import com.alibaba.apiopenplatform.support.enums.SourceType;
26 | import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
27 | import com.alibaba.apiopenplatform.support.product.HigressRefConfig;
28 | import com.alibaba.apiopenplatform.support.product.NacosRefConfig;
29 | import lombok.Data;
30 | import lombok.EqualsAndHashCode;
31 | 
32 | import javax.persistence.*;
33 | 
34 | @EqualsAndHashCode(callSuper = true)
35 | @Entity
36 | @Table(name = "product_ref")
37 | @Data
38 | public class ProductRef extends BaseEntity {
39 | 
40 |     @Id
41 |     @GeneratedValue(strategy = GenerationType.IDENTITY)
42 |     private Long id;
43 | 
44 |     @Column(name = "product_id", length = 64, nullable = false)
45 |     private String productId;
46 | 
47 |     @Column(name = "gateway_id", length = 64)
48 |     private String gatewayId;
49 | 
50 |     @Column(name = "apig_ref_config", columnDefinition = "json")
51 |     @Convert(converter = APIGRefConfigConverter.class)
52 |     private APIGRefConfig apigRefConfig;
53 | 
54 |     @Column(name = "adp_ai_gateway_ref_config", columnDefinition = "json")
55 |     @Convert(converter = APIGRefConfigConverter.class)
56 |     private APIGRefConfig adpAIGatewayRefConfig;
57 | 
58 |     @Column(name = "higress_ref_config", columnDefinition = "json")
59 |     @Convert(converter = HigressRefConfigConverter.class)
60 |     private HigressRefConfig higressRefConfig;
61 | 
62 |     @Column(name = "nacos_id", length = 64)
63 |     private String nacosId;
64 | 
65 |     @Column(name = "nacos_ref_config", columnDefinition = "json")
66 |     @Convert(converter = NacosRefConfigConverter.class)
67 |     private NacosRefConfig nacosRefConfig;
68 | 
69 |     @Column(name = "source_type", length = 32)
70 |     @Enumerated(EnumType.STRING)
71 |     private SourceType sourceType;
72 | 
73 |     @Column(name = "api_config", columnDefinition = "json")
74 |     private String apiConfig;
75 | 
76 |     @Column(name = "mcp_config", columnDefinition = "json")
77 |     private String mcpConfig;
78 | 
79 |     @Column(name = "enabled")
80 |     private Boolean enabled;
81 | }
82 | 
```

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

```typescript
 1 | import React, { useState } from 'react'
 2 | import { Link, useNavigate, useLocation } from 'react-router-dom'
 3 | import api from '../lib/api'
 4 | import { Form, Input, Button, Alert } from 'antd'
 5 | 
 6 | const Register: React.FC = () => {
 7 |   const [loading, setLoading] = useState(false)
 8 |   const [error, setError] = useState('')
 9 |   const navigate = useNavigate()
10 |   const location = useLocation()
11 |   const searchParams = new URLSearchParams(location.search)
12 |   const portalId = searchParams.get('portalId') || ''
13 | 
14 |   const handleRegister = async (values: { username: string; password: string; confirmPassword: string }) => {
15 |     setError('')
16 |     if (!values.username || !values.password || !values.confirmPassword) {
17 |       setError('请填写所有字段')
18 |       return
19 |     }
20 |     if (values.password !== values.confirmPassword) {
21 |       setError('两次输入的密码不一致')
22 |       return
23 |     }
24 |     setLoading(true)
25 |     try {
26 |       await api.post('/admins/init', { username: values.username, password: values.password })
27 |       navigate('/login')
28 |     } catch {
29 |       setError('注册失败')
30 |     } finally {
31 |       setLoading(false)
32 |     }
33 |   }
34 | 
35 |   return (
36 |     <div className="flex items-center justify-center min-h-screen bg-white">
37 |       <div className="bg-white p-8 rounded-xl shadow-2xl w-full max-w-md flex flex-col items-center border border-gray-100">
38 |         {/* Logo */}
39 |         <div className="mb-4">
40 |           <img src="/logo.png" alt="Logo" className="w-16 h-16 mx-auto mb-4" />
41 |         </div>
42 |         <h2 className="text-2xl font-bold mb-6 text-gray-900 text-center">注册 AI Portal</h2>
43 |         <Form
44 |           className="w-full flex flex-col gap-4"
45 |           layout="vertical"
46 |           onFinish={handleRegister}
47 |         >
48 |           <Form.Item
49 |             name="username"
50 |             rules={[{ required: true, message: '请输入账号' }]}
51 |           >
52 |             <Input placeholder="账号" autoComplete="username" size="large" />
53 |           </Form.Item>
54 |           <Form.Item
55 |             name="password"
56 |             rules={[{ required: true, message: '请输入密码' }]}
57 |           >
58 |             <Input.Password placeholder="密码" autoComplete="new-password" size="large" />
59 |           </Form.Item>
60 |           <Form.Item
61 |             name="confirmPassword"
62 |             rules={[{ required: true, message: '请确认密码' }]}
63 |           >
64 |             <Input.Password placeholder="确认密码" autoComplete="new-password" size="large" />
65 |           </Form.Item>
66 |           {error && <Alert message={error} type="error" showIcon className="mb-2" />}
67 |           <Form.Item>
68 |             <Button
69 |               type="primary"
70 |               htmlType="submit"
71 |               className="w-full"
72 |               loading={loading}
73 |               size="large"
74 |             >
75 |               注册
76 |             </Button>
77 |           </Form.Item>
78 |         </Form>
79 |         <div className="mt-6 text-gray-400 text-sm text-center w-full">
80 |           已有账号?<Link to="/login" className="text-indigo-500 hover:underline ml-1">登录</Link>
81 |         </div>
82 |       </div>
83 |     </div>
84 |   )
85 | }
86 | 
87 | export default Register 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.service;
 21 | 
 22 | import com.alibaba.apiopenplatform.dto.params.portal.*;
 23 | import com.alibaba.apiopenplatform.dto.result.PageResult;
 24 | import com.alibaba.apiopenplatform.dto.result.PortalResult;
 25 | import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
 26 | import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
 27 | import org.springframework.data.domain.Pageable;
 28 | 
 29 | public interface PortalService {
 30 | 
 31 |     /**
 32 |      * 创建门户
 33 |      *
 34 |      * @param param
 35 |      * @return
 36 |      */
 37 |     PortalResult createPortal(CreatePortalParam param);
 38 | 
 39 |     /**
 40 |      * 查询门户
 41 |      *
 42 |      * @param portalId
 43 |      * @return
 44 |      */
 45 |     PortalResult getPortal(String portalId);
 46 | 
 47 |     /**
 48 |      * 检查门户是否存在
 49 |      *
 50 |      * @param portalId
 51 |      */
 52 |     void existsPortal(String portalId);
 53 | 
 54 |     /**
 55 |      * 查询门户列表
 56 |      *
 57 |      * @param pageable
 58 |      * @return
 59 |      */
 60 |     PageResult<PortalResult> listPortals(Pageable pageable);
 61 | 
 62 |     /**
 63 |      * 更新门户
 64 |      *
 65 |      * @param portalId
 66 |      * @param param
 67 |      * @return
 68 |      */
 69 |     PortalResult updatePortal(String portalId, UpdatePortalParam param);
 70 | 
 71 |     /**
 72 |      * 删除门户
 73 |      *
 74 |      * @param portalId
 75 |      */
 76 |     void deletePortal(String portalId);
 77 | 
 78 |     /**
 79 |      * 根据请求域名解析门户
 80 |      *
 81 |      * @param domain
 82 |      * @return
 83 |      */
 84 |     String resolvePortal(String domain);
 85 | 
 86 |     /**
 87 |      * 为门户绑定域名
 88 |      *
 89 |      * @param portalId
 90 |      * @param param
 91 |      * @return
 92 |      */
 93 |     PortalResult bindDomain(String portalId, BindDomainParam param);
 94 | 
 95 |     /**
 96 |      * 删除门户绑定域名
 97 |      *
 98 |      * @param portalId
 99 |      * @param domain
100 |      * @return
101 |      */
102 |     PortalResult unbindDomain(String portalId, String domain);
103 | 
104 |     /**
105 |      * 获取门户上的API产品订阅列表
106 |      *
107 |      * @param portalId 门户ID
108 |      * @param param    查询参数
109 |      * @param pageable 分页参数
110 |      * @return PageResult of SubscriptionResult
111 |      */
112 |     PageResult<SubscriptionResult> listSubscriptions(String portalId, QuerySubscriptionParam param, Pageable pageable);
113 | 
114 |     /**
115 |      * 获取默认门户
116 |      *
117 |      * @return
118 |      */
119 |     String getDefaultPortal();
120 | 
121 |     /**
122 |      * 获取门户的Dashboard监控面板URL
123 |      *
124 |      * @param portalId 门户ID
125 |      * @return Dashboard URL
126 |      */
127 |     String getDashboard(String portalId);
128 | }
129 | 
```

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

```typescript
 1 | import type { ReactNode } from "react";
 2 | import { Skeleton } from "antd";
 3 | import { Navigation } from "./Navigation";
 4 | 
 5 | interface LayoutProps {
 6 |   children: ReactNode;
 7 |   className?: string;
 8 |   loading?: boolean;
 9 | }
10 | 
11 | export function Layout({ children, className = "", loading = false }: LayoutProps) {
12 |   return (
13 |     <div className={`min-h-screen bg-[#f4f4f6] ${className}`}>
14 |       <Navigation loading={loading} />
15 |       <main className="pt-4">
16 |         <div className="w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
17 |           {loading ? (
18 |             <div className="space-y-8">
19 |               {/* 页面标题骨架屏 */}
20 |               <div className="text-center mb-8">
21 |                 <Skeleton.Input active size="large" style={{ width: 300, height: 48, margin: '0 auto 16px' }} />
22 |                 <Skeleton.Input active size="small" style={{ width: '80%', height: 24, margin: '0 auto' }} />
23 |               </div>
24 |               
25 |               {/* 搜索框骨架屏 */}
26 |               <div className="flex justify-center mb-8">
27 |                 <div className="relative w-full max-w-2xl">
28 |                   <Skeleton.Input active size="large" style={{ width: '100%', height: 40 }} />
29 |                 </div>
30 |               </div>
31 |               
32 |               {/* 子标题骨架屏 */}
33 |               <div className="mb-6">
34 |                 <Skeleton.Input active size="small" style={{ width: 200, height: 32 }} />
35 |               </div>
36 |               
37 |               {/* 内容区域骨架屏 */}
38 |               <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
39 |                 {Array.from({ length: 6 }).map((_, index) => (
40 |                   <div key={index} className="h-full rounded-lg shadow-lg bg-white p-4">
41 |                     <div className="flex items-start space-x-4">
42 |                       <Skeleton.Avatar size={48} active />
43 |                       <div className="flex-1 min-w-0">
44 |                         <div className="flex items-center justify-between mb-2">
45 |                           <Skeleton.Input active size="small" style={{ width: 120 }} />
46 |                           <Skeleton.Input active size="small" style={{ width: 60 }} />
47 |                         </div>
48 |                         <Skeleton.Input active size="small" style={{ width: 80, marginBottom: 8 }} />
49 |                         <Skeleton.Input active size="small" style={{ width: '100%', marginBottom: 12 }} />
50 |                         <Skeleton.Input active size="small" style={{ width: '100%', marginBottom: 12 }} />
51 |                         <div className="flex items-center justify-between">
52 |                           <Skeleton.Input active size="small" style={{ width: 60 }} />
53 |                           <Skeleton.Input active size="small" style={{ width: 80 }} />
54 |                         </div>
55 |                       </div>
56 |                     </div>
57 |                   </div>
58 |                 ))}
59 |               </div>
60 |             </div>
61 |           ) : (
62 |             children
63 |           )}
64 |         </div>
65 |       </main>
66 |       {/* <Footer /> */}
67 |     </div>
68 |   );
69 | } 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/dto/result/GatewayResult.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.dto.result;
21 | 
22 | import com.alibaba.apiopenplatform.dto.converter.OutputConverter;
23 | import com.alibaba.apiopenplatform.entity.Gateway;
24 | import com.alibaba.apiopenplatform.support.enums.GatewayType;
25 | import com.alibaba.apiopenplatform.support.gateway.APIGConfig;
26 | import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
27 | import com.alibaba.apiopenplatform.support.gateway.HigressConfig;
28 | import lombok.AllArgsConstructor;
29 | import lombok.Builder;
30 | import lombok.Data;
31 | import lombok.NoArgsConstructor;
32 | 
33 | import java.time.LocalDateTime;
34 | 
35 | @Data
36 | @Builder
37 | @AllArgsConstructor
38 | @NoArgsConstructor
39 | public class GatewayResult implements OutputConverter<GatewayResult, Gateway> {
40 | 
41 |     private String gatewayId;
42 | 
43 |     private GatewayType gatewayType;
44 | 
45 |     private String gatewayName;
46 | 
47 |     private APIGConfigResult apigConfig;
48 | 
49 |     private AdpAIGatewayConfigResult adpAIGatewayConfig;
50 | 
51 |     private HigressConfigResult higressConfig;
52 | 
53 |     private LocalDateTime createAt;
54 | 
55 |     @Override
56 |     public GatewayResult convertFrom(Gateway source) {
57 |         OutputConverter.super.convertFrom(source);
58 |         if (source.getGatewayType().isAPIG() && !source.getGatewayType().equals(GatewayType.ADP_AI_GATEWAY)) {
59 |             setApigConfig(new APIGConfigResult().convertFrom(source.getApigConfig()));
60 |         } else if (source.getGatewayType().equals(GatewayType.ADP_AI_GATEWAY)) {
61 |             setAdpAIGatewayConfig(new AdpAIGatewayConfigResult().convertFrom(source.getAdpAIGatewayConfig()));
62 |         } else {
63 |             setHigressConfig(new HigressConfigResult().convertFrom(source.getHigressConfig()));
64 |         }
65 |         return this;
66 |     }
67 | 
68 |     @Data
69 |     public static class APIGConfigResult implements OutputConverter<APIGConfigResult, APIGConfig> {
70 |         private String region;
71 |     }
72 | 
73 |     @Data
74 |     public static class AdpAIGatewayConfigResult implements OutputConverter<AdpAIGatewayConfigResult, AdpAIGatewayConfig> {
75 |         private String baseUrl;
76 |         private Integer port;
77 |         private String authSeed;
78 |     }
79 | 
80 |     @Data
81 |     public static class HigressConfigResult implements OutputConverter<HigressConfigResult, HigressConfig> {
82 |         private String address;
83 |         private String username;
84 |     }
85 | }
86 | 
```

--------------------------------------------------------------------------------
/portal-server/src/main/java/com/alibaba/apiopenplatform/core/advice/ExceptionAdvice.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.core.advice;
21 | 
22 | import com.alibaba.apiopenplatform.core.exception.BusinessException;
23 | import com.alibaba.apiopenplatform.core.exception.ErrorCode;
24 | import com.alibaba.apiopenplatform.core.response.Response;
25 | import lombok.extern.slf4j.Slf4j;
26 | import org.springframework.http.ResponseEntity;
27 | import org.springframework.web.bind.MethodArgumentNotValidException;
28 | import org.springframework.web.bind.annotation.ExceptionHandler;
29 | import org.springframework.web.bind.annotation.RestControllerAdvice;
30 | 
31 | import java.util.stream.Collectors;
32 | 
33 | /**
34 |  * 全局异常处理
35 |  * <p>
36 |  * 处理三类异常:
37 |  * 1. 业务异常 {@link BusinessException}: 业务异常
38 |  * 2. 参数校验异常 {@link MethodArgumentNotValidException}: 请求参数校验不通过
39 |  * 3. 系统异常 {@link Exception}: 非预期的系统异常
40 |  * <p>
41 |  * 所有异常都会被转换为统一的响应格式:
42 |  * {
43 |  * "code": "错误码",
44 |  * "message": "错误信息",
45 |  * "data": null
46 |  * }
47 |  *
48 |  */
49 | @Slf4j
50 | @RestControllerAdvice
51 | public class ExceptionAdvice {
52 | 
53 |     @ExceptionHandler(BusinessException.class)
54 |     public ResponseEntity<Response<Void>> handleBusinessException(BusinessException e) {
55 |         return ResponseEntity
56 |                 .status(e.getStatus())
57 |                 .body(Response.fail(e.getCode(), e.getMessage()));
58 |     }
59 | 
60 |     @ExceptionHandler(MethodArgumentNotValidException.class)
61 |     public ResponseEntity<Response<Void>> handleParamVerifyException(MethodArgumentNotValidException e) {
62 |         String message = e.getBindingResult().getFieldErrors().stream()
63 |                 .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
64 |                 .collect(Collectors.joining("; "));
65 |         log.error("Validation failed", e);
66 |         return ResponseEntity
67 |                 .status(ErrorCode.INVALID_PARAMETER.getStatus())
68 |                 .body(Response.fail(ErrorCode.INVALID_PARAMETER.name(), message));
69 |     }
70 | 
71 |     @ExceptionHandler(Exception.class)
72 |     public ResponseEntity<Response<Void>> handleSystemException(Exception e) {
73 |         log.error("System error", e);
74 |         return ResponseEntity
75 |                 .status(ErrorCode.INTERNAL_ERROR.getStatus())
76 |                 .body(Response.fail(
77 |                         ErrorCode.INTERNAL_ERROR.name(),
78 |                         ErrorCode.INTERNAL_ERROR.getMessage(e.getMessage())
79 |                 ));
80 |     }
81 | }
82 | 
```

--------------------------------------------------------------------------------
/portal-dal/src/main/java/com/alibaba/apiopenplatform/converter/JsonConverter.java:
--------------------------------------------------------------------------------

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.converter;
 21 | 
 22 | import cn.hutool.core.bean.BeanUtil;
 23 | import cn.hutool.core.util.ClassUtil;
 24 | import cn.hutool.core.util.ReflectUtil;
 25 | import cn.hutool.json.JSONUtil;
 26 | import com.alibaba.apiopenplatform.support.common.Encrypted;
 27 | import com.alibaba.apiopenplatform.support.common.Encryptor;
 28 | import lombok.extern.slf4j.Slf4j;
 29 | 
 30 | import javax.persistence.AttributeConverter;
 31 | import java.lang.reflect.Field;
 32 | 
 33 | @Slf4j
 34 | public abstract class JsonConverter<T> implements AttributeConverter<T, String> {
 35 | 
 36 |     private final Class<T> type;
 37 | 
 38 |     protected JsonConverter(Class<T> type) {
 39 |         this.type = type;
 40 |     }
 41 | 
 42 |     @Override
 43 |     public String convertToDatabaseColumn(T attribute) {
 44 |         if (attribute == null) {
 45 |             return null;
 46 |         }
 47 | 
 48 |         T clonedAttribute = cloneAndEncrypt(attribute);
 49 |         return JSONUtil.toJsonStr(clonedAttribute);
 50 |     }
 51 | 
 52 |     @Override
 53 |     public T convertToEntityAttribute(String dbData) {
 54 |         if (dbData == null) {
 55 |             return null;
 56 |         }
 57 | 
 58 |         T attribute = JSONUtil.toBean(dbData, type);
 59 |         decrypt(attribute);
 60 |         return attribute;
 61 |     }
 62 | 
 63 |     private T cloneAndEncrypt(T original) {
 64 |         // Clone避免JPA更新数据
 65 |         T cloned = JSONUtil.toBean(JSONUtil.toJsonStr(original), type);
 66 |         handleEncryption(cloned, true);
 67 |         return cloned;
 68 |     }
 69 | 
 70 |     private void decrypt(T attribute) {
 71 |         handleEncryption(attribute, false);
 72 |     }
 73 | 
 74 |     private void handleEncryption(Object obj, boolean isEncrypt) {
 75 |         if (obj == null) {
 76 |             return;
 77 |         }
 78 | 
 79 |         BeanUtil.descForEach(obj.getClass(), pd -> {
 80 |             Field field = pd.getField();
 81 |             if (field == null) {
 82 |                 return;
 83 |             }
 84 | 
 85 |             Object value = ReflectUtil.getFieldValue(obj, field);
 86 |             if (value == null) {
 87 |                 return;
 88 |             }
 89 | 
 90 |             // 处理需要加密/解密的字段
 91 |             if (field.isAnnotationPresent(Encrypted.class) && value instanceof String) {
 92 |                 String result = isEncrypt ?
 93 |                         Encryptor.encrypt((String) value) :
 94 |                         Encryptor.decrypt((String) value);
 95 |                 ReflectUtil.setFieldValue(obj, field, result);
 96 |             } else if (!ClassUtil.isSimpleValueType(value.getClass())) {
 97 |                 handleEncryption(value, isEncrypt);
 98 |             }
 99 |         });
100 |     }
101 | }
102 | 
```

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

```typescript
  1 | import React, { useState, useEffect } from 'react';
  2 | import { Card, Spin, Button, Space } from 'antd';
  3 | import { ReloadOutlined, DashboardOutlined } from '@ant-design/icons';
  4 | import { apiProductApi } from '@/lib/api';
  5 | import type { ApiProduct } from '@/types/api-product';
  6 | 
  7 | interface ApiProductDashboardProps {
  8 |   apiProduct: ApiProduct;
  9 | }
 10 | 
 11 | export const ApiProductDashboard: React.FC<ApiProductDashboardProps> = ({ apiProduct }) => {
 12 |   const [dashboardUrl, setDashboardUrl] = useState<string>('');
 13 |   const [loading, setLoading] = useState(false);
 14 |   const [error, setError] = useState<string>('');
 15 |   const [fallback, setFallback] = useState<boolean>(false);
 16 | 
 17 |   // 获取Dashboard URL
 18 |   const fetchDashboardUrl = async () => {
 19 |     if (!apiProduct.productId) return;
 20 |     
 21 |     setLoading(true);
 22 |     setError('');
 23 |     
 24 |     try {
 25 |       // 直接调用产品的dashboard接口获取监控面板URL
 26 |       const response = await apiProductApi.getProductDashboard(apiProduct.productId);
 27 |       if (!response?.data) {
 28 |         setFallback(true);
 29 |       } else {
 30 |         setDashboardUrl(response.data);
 31 |       }
 32 |     } catch (err: any) {
 33 |       setError(err?.response?.data?.message || '获取监控面板失败');
 34 |       setFallback(true);
 35 |     } finally {
 36 |       setLoading(false);
 37 |     }
 38 |   };
 39 | 
 40 |   useEffect(() => {
 41 |     if (apiProduct.productId) {
 42 |       fetchDashboardUrl();
 43 |     }
 44 |   }, [apiProduct.productId]);
 45 | 
 46 |   const handleRefresh = () => {
 47 |     fetchDashboardUrl();
 48 |   };
 49 | 
 50 |   if (loading) {
 51 |     return (
 52 |       <div className="flex items-center justify-center h-64">
 53 |         <Spin size="large" />
 54 |       </div>
 55 |     );
 56 |   }
 57 | 
 58 |   if (fallback || !dashboardUrl || error) {
 59 |     return (
 60 |       <div className="p-6">
 61 |         <div className="w-full h-[600px] flex items-center justify-center text-gray-500">
 62 |           Dashboard 发布中,敬请期待
 63 |         </div>
 64 |         <div className="mt-4 text-right">
 65 |           <Button onClick={handleRefresh} loading={loading}>刷新</Button>
 66 |         </div>
 67 |       </div>
 68 |     );
 69 |   }
 70 | 
 71 |   return (
 72 |     <div className="p-6 space-y-6">
 73 |       {/* 标题和操作 */}
 74 |       <div className="flex items-center justify-between">
 75 |         <div>
 76 |           <h2 className="text-2xl font-bold flex items-center gap-2">
 77 |             <DashboardOutlined className="text-blue-500" />
 78 |             Dashboard 监控面板
 79 |           </h2>
 80 |           <p className="text-gray-500 mt-2">
 81 |             实时监控 {apiProduct.name} 的API调用情况和性能指标
 82 |           </p>
 83 |         </div>
 84 |         <Space>
 85 |           <Button 
 86 |             icon={<ReloadOutlined />} 
 87 |             onClick={handleRefresh}
 88 |             loading={loading}
 89 |           >
 90 |             刷新
 91 |           </Button>
 92 |         </Space>
 93 |       </div>
 94 | 
 95 |       {/* Dashboard嵌入区域 */}
 96 |       <Card title="监控面板" className="w-full">
 97 |         <div className="w-full h-[600px] border rounded-lg overflow-hidden">
 98 |           {dashboardUrl ? (
 99 |             <iframe
100 |               src={dashboardUrl}
101 |               title={`${apiProduct.name} Dashboard`}
102 |               className="w-full h-full border-0"
103 |               sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
104 |               onError={() => setFallback(true)}
105 |             />
106 |           ) : (
107 |             <div className="flex items-center justify-center h-full text-gray-500">
108 |               加载监控面板中...
109 |             </div>
110 |           )}
111 |         </div>
112 |       </Card>
113 | 
114 | 
115 |     </div>
116 |   );
117 | };
118 | 
```

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

```typescript
  1 | import React, { useState } from 'react'
  2 | import { Link, useNavigate } from 'react-router-dom'
  3 | import { Form, Input, Button, Card, message } from 'antd'
  4 | import { UserOutlined, LockOutlined } from '@ant-design/icons'
  5 | import api from '../lib/api'
  6 | 
  7 | const Register: React.FC = () => {
  8 |   const [loading, setLoading] = useState(false)
  9 |   const navigate = useNavigate()
 10 |   // const location = useLocation()
 11 |   // const searchParams = new URLSearchParams(location.search)
 12 |   // const portalId = searchParams.get('portalId') || ''
 13 | 
 14 |   const handleRegister = async (values: { username: string; password: string; confirmPassword: string }) => {
 15 |     setLoading(true)
 16 |     try {
 17 |       // 这里需要根据实际API调整
 18 |       await api.post('/developers', { 
 19 |         username: values.username, 
 20 |         password: values.password, 
 21 |       })
 22 |       message.success('注册成功!')
 23 |       // 注册成功后跳转到登录页
 24 |       navigate('/login')
 25 |     } catch {
 26 |       message.error('注册失败,请重试')
 27 |     } finally {
 28 |       setLoading(false)
 29 |     }
 30 |   }
 31 | 
 32 |   return (
 33 |     <div className="flex items-center justify-center min-h-screen bg-gray-50">
 34 |       <Card className="w-full max-w-md shadow-lg">
 35 |         {/* Logo */}
 36 |         <div className="text-center mb-6">
 37 |           <img src="/logo.png" alt="Logo" className="w-16 h-16 mx-auto mb-4" />
 38 |           <h2 className="text-2xl font-bold text-gray-900">注册 AI Portal - 前台</h2>
 39 |         </div>
 40 |         
 41 |         <Form
 42 |           name="register"
 43 |           onFinish={handleRegister}
 44 |           autoComplete="off"
 45 |           layout="vertical"
 46 |           size="large"
 47 |         >
 48 |           <Form.Item
 49 |             name="username"
 50 |             rules={[
 51 |               { required: true, message: '请输入账号' },
 52 |               { min: 3, message: '账号至少3个字符' }
 53 |             ]}
 54 |           >
 55 |             <Input
 56 |               prefix={<UserOutlined />}
 57 |               placeholder="账号"
 58 |               autoComplete="username"
 59 |             />
 60 |           </Form.Item>
 61 | 
 62 |           <Form.Item
 63 |             name="password"
 64 |             rules={[
 65 |               { required: true, message: '请输入密码' },
 66 |               { min: 6, message: '密码至少6个字符' }
 67 |             ]}
 68 |           >
 69 |             <Input.Password
 70 |               prefix={<LockOutlined />}
 71 |               placeholder="密码"
 72 |               autoComplete="new-password"
 73 |             />
 74 |           </Form.Item>
 75 | 
 76 |           <Form.Item
 77 |             name="confirmPassword"
 78 |             dependencies={['password']}
 79 |             rules={[
 80 |               { required: true, message: '请确认密码' },
 81 |               ({ getFieldValue }) => ({
 82 |                 validator(_, value) {
 83 |                   if (!value || getFieldValue('password') === value) {
 84 |                     return Promise.resolve()
 85 |                   }
 86 |                   return Promise.reject(new Error('两次输入的密码不一致'))
 87 |                 },
 88 |               }),
 89 |             ]}
 90 |           >
 91 |             <Input.Password
 92 |               prefix={<LockOutlined />}
 93 |               placeholder="确认密码"
 94 |               autoComplete="new-password"
 95 |             />
 96 |           </Form.Item>
 97 | 
 98 |           <Form.Item>
 99 |             <Button
100 |               type="primary"
101 |               htmlType="submit"
102 |               loading={loading}
103 |               className="w-full"
104 |               size="large"
105 |             >
106 |               {loading ? '注册中...' : '注册'}
107 |             </Button>
108 |           </Form.Item>
109 |         </Form>
110 | 
111 |         <div className="text-center text-gray-500">
112 |           已有账号?<Link to="/login" className="text-blue-500 hover:underline">登录</Link>
113 |         </div>
114 |       </Card>
115 |     </div>
116 |   )
117 | }
118 | 
119 | export default Register 
```

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

```typescript
  1 | import { Link, useLocation } from "react-router-dom";
  2 | import { Skeleton } from "antd";
  3 | import { UserInfo } from "./UserInfo";
  4 | 
  5 | interface NavigationProps {
  6 |   loading?: boolean;
  7 | }
  8 | 
  9 | export function Navigation({ loading = false }: NavigationProps) {
 10 |   const location = useLocation();
 11 |   
 12 |   const isActive = (path: string) => {
 13 |     if (path === '/') {
 14 |       return location.pathname === '/';
 15 |     }
 16 |     return location.pathname.startsWith(path);
 17 |   };
 18 | 
 19 |   const getNavLinkClass = (path: string) => {
 20 |     const baseClass = "font-medium transition-colors";
 21 |     return isActive(path) 
 22 |       ? `${baseClass} text-blue-600 border-b-2 border-blue-600 pb-1` 
 23 |       : `${baseClass} text-gray-700 hover:text-gray-900`;
 24 |   };
 25 | 
 26 |   return (
 27 |     <nav className="bg-[#f4f4f6] sticky top-0 z-50">
 28 |       <div className="w-full mx-auto px-4 sm:px-6 lg:px-8">
 29 |         <div className="flex justify-between items-center h-16">
 30 |           <div className="flex items-center">
 31 |             {loading ? (
 32 |               <div className="flex items-center space-x-2">
 33 |                 <Skeleton.Avatar size={32} active />
 34 |                 <Skeleton.Input active size="small" style={{ width: 120, height: 24 }} />
 35 |               </div>
 36 |             ) : (
 37 |               <Link to="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
 38 |                 <div className="w-8 h-8 rounded-full flex items-center justify-center">
 39 |                 {/* LOGO区域 */}
 40 |                 <img
 41 |                   src="/logo.png"
 42 |                   alt="logo"
 43 |                   className="w-6 h-6"
 44 |                   style={{ display: "block" }}
 45 |                 />
 46 |                 </div>
 47 |                 <span className="text-xl font-bold text-gray-900">HiMarket</span>
 48 |               </Link>
 49 |             )}
 50 |           </div>
 51 |           
 52 |           <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">
 53 |             {loading ? (
 54 |               <>
 55 |                 <Skeleton.Input active size="small" style={{ width: 100, height: 20 }} />
 56 |                 <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
 57 |                 <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
 58 |                 <Skeleton.Input active size="small" style={{ width: 60, height: 20 }} />
 59 |               </>
 60 |             ) : (
 61 |               <>
 62 |                 <Link 
 63 |                   to="/getting-started" 
 64 |                   className={getNavLinkClass('/getting-started')}
 65 |                 >
 66 |                   Getting Started
 67 |                 </Link>
 68 |                 <Link 
 69 |                   to="/apis" 
 70 |                   className={getNavLinkClass('/apis')}
 71 |                 >
 72 |                   APIs
 73 |                 </Link>
 74 |                 <Link 
 75 |                   to="/mcp" 
 76 |                   className={getNavLinkClass('/mcp')}
 77 |                 >
 78 |                   MCP
 79 |                 </Link>
 80 |               </>
 81 |             )}
 82 |           </div>
 83 |           
 84 |           <div className="flex items-center space-x-4">
 85 |             {/* <div className="hidden sm:block">
 86 |               <Input
 87 |                 placeholder="Search"
 88 |                 prefix={<SearchOutlined className="text-gray-400" />}
 89 |                 className="w-48 lg:w-64"
 90 |                 size="middle"
 91 |               />
 92 |             </div> */}
 93 |             {loading ? (
 94 |               <Skeleton.Avatar size={32} active />
 95 |             ) : (
 96 |               <UserInfo />
 97 |             )}
 98 |           </div>
 99 |         </div>
100 |       </div>
101 |     </nav>
102 |   );
103 | } 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.service;
 21 | 
 22 | import com.alibaba.apiopenplatform.core.event.PortalDeletingEvent;
 23 | import com.alibaba.apiopenplatform.dto.params.product.*;
 24 | import com.alibaba.apiopenplatform.dto.result.*;
 25 | import org.springframework.data.domain.Pageable;
 26 | 
 27 | import java.util.List;
 28 | import java.util.Map;
 29 | 
 30 | public interface ProductService {
 31 | 
 32 |     /**
 33 |      * 创建API产品
 34 |      *
 35 |      * @param param
 36 |      * @return
 37 |      */
 38 |     ProductResult createProduct(CreateProductParam param);
 39 | 
 40 |     /**
 41 |      * 查询API产品
 42 |      *
 43 |      * @param productId
 44 |      * @return
 45 |      */
 46 |     ProductResult getProduct(String productId);
 47 | 
 48 |     /**
 49 |      * 查询API产品列表
 50 |      *
 51 |      * @param param
 52 |      * @param pageable
 53 |      * @return
 54 |      */
 55 |     PageResult<ProductResult> listProducts(QueryProductParam param, Pageable pageable);
 56 | 
 57 |     /**
 58 |      * 更新门户
 59 |      *
 60 |      * @param productId
 61 |      * @param param
 62 |      * @return
 63 |      */
 64 |     ProductResult updateProduct(String productId, UpdateProductParam param);
 65 | 
 66 |     /**
 67 |      * 发布API产品
 68 |      *
 69 |      * @param productId
 70 |      * @param portalId
 71 |      * @return
 72 |      */
 73 |     void publishProduct(String productId, String portalId);
 74 | 
 75 |     /**
 76 |      * 获取API产品的发布信息
 77 |      *
 78 |      * @param productId
 79 |      * @param pageable
 80 |      * @return
 81 |      */
 82 |     PageResult<ProductPublicationResult> getPublications(String productId, Pageable pageable);
 83 | 
 84 |     /**
 85 |      * 下线产品
 86 |      *
 87 |      * @param productId
 88 |      * @param portalId
 89 |      * @return
 90 |      */
 91 |     void unpublishProduct(String productId, String portalId);
 92 | 
 93 |     /**
 94 |      * 删除产品
 95 |      *
 96 |      * @param productId
 97 |      */
 98 |     void deleteProduct(String productId);
 99 | 
100 |     /**
101 |      * API产品引用API或MCP Server
102 |      *
103 |      * @param productId
104 |      * @param param
105 |      */
106 |     void addProductRef(String productId, CreateProductRefParam param);
107 | 
108 |     /**
109 |      * 查询API产品引用的资源
110 |      *
111 |      * @param productId
112 |      * @return
113 |      */
114 |     ProductRefResult getProductRef(String productId);
115 | 
116 |     /**
117 |      * 删除API产品的引用
118 |      *
119 |      * @param productId
120 |      */
121 |     void deleteProductRef(String productId);
122 | 
123 |     /**
124 |      * 清理门户资源
125 |      *
126 |      * @param event
127 |      */
128 |     void handlePortalDeletion(PortalDeletingEvent event);
129 | 
130 |     Map<String, ProductResult> getProducts(List<String> productIds);
131 | 
132 |     /**
133 |      * 获取API产品的Dashboard监控面板URL
134 |      *
135 |      * @param productId
136 |      * @return Dashboard URL
137 |      */
138 |     String getProductDashboard(String productId);
139 | 
140 |     /**
141 |      * 获取API产品的订阅信息
142 |      *
143 |      * @param productId
144 |      * @param param
145 |      * @param pageable
146 |      * @return
147 |      */
148 |     PageResult<SubscriptionResult> listProductSubscriptions(String productId, QueryProductSubscriptionParam param, Pageable pageable);
149 | 
150 |     /**
151 |      * 检查API产品是否存在
152 |      *
153 |      * @param productId
154 |      * @return
155 |      */
156 |     void existsProduct(String productId);
157 | }
158 | 
```

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

```css
  1 | .test-container {
  2 |   padding: 2rem;
  3 |   max-width: 800px;
  4 |   margin: 0 auto;
  5 |   font-family: 'Arial', sans-serif;
  6 | }
  7 | 
  8 | .test-container h1 {
  9 |   text-align: center;
 10 |   color: #333;
 11 |   margin-bottom: 2rem;
 12 | }
 13 | 
 14 | .demo-section {
 15 |   margin-bottom: 3rem;
 16 |   padding: 1.5rem;
 17 |   border: 1px solid #e0e0e0;
 18 |   border-radius: 8px;
 19 |   background: #fafafa;
 20 | }
 21 | 
 22 | .demo-section h2 {
 23 |   color: #555;
 24 |   margin-bottom: 1rem;
 25 |   font-size: 1.2rem;
 26 | }
 27 | 
 28 | .text-flow-container {
 29 |   text-align: center;
 30 |   padding: 2rem 0;
 31 | }
 32 | 
 33 | .text-flow-container.large {
 34 |   padding: 3rem 0;
 35 | }
 36 | 
 37 | .text-flow {
 38 |   display: inline-block;
 39 |   margin: 0 1rem;
 40 |   /* font-weight: bold; */
 41 |   position: relative;
 42 | }
 43 | 
 44 | /* 基础流光效果 - 红色到白色 */
 45 | .text-flow-primary {
 46 |   background-image: linear-gradient(
 47 |     to right,
 48 |     #ff0000,
 49 |     #ffffff 12.5%,
 50 |     #ff0000 25%,
 51 |     #ffffff 37.5%,
 52 |     #ff0000 50%,
 53 |     #ff0000 100%
 54 |   );
 55 |   -webkit-text-fill-color: transparent;
 56 |   -webkit-background-clip: text;
 57 |   background-clip: text;
 58 |   background-size: 400% 100%;
 59 |   animation: light 2s infinite linear;
 60 | }
 61 | 
 62 | /* 次要流光效果 - 黑色到白色 */
 63 | .text-flow-secondary {
 64 |   background-image: linear-gradient(
 65 |     to right,
 66 |     #000000,
 67 |     #000000 50%,
 68 |     #ffffff 62.5%,
 69 |     #000000 75%,
 70 |     #ffffff 87.5%,
 71 |     #000000 100%
 72 |   );
 73 |   -webkit-text-fill-color: transparent;
 74 |   -webkit-background-clip: text;
 75 |   background-clip: text;
 76 |   background-size: 800% 100%;
 77 |   animation: light 2s infinite linear;
 78 | }
 79 | 
 80 | .text-flow-grey {
 81 |   background-image: linear-gradient(
 82 |     to right,
 83 |     #4b5563,
 84 |     #ffffff 12.5%,
 85 |     #4b5563 25%,
 86 |     #ffffff 37.5%,
 87 |     #4b5563 50%,
 88 |     #4b5563 100%
 89 |   );
 90 |   -webkit-text-fill-color: transparent;
 91 |   -webkit-background-clip: text;
 92 |   background-clip: text;
 93 |   background-size: 400% 100%;
 94 |   animation: light 2s infinite linear;
 95 | }
 96 | 
 97 | 
 98 | /* 蓝色流光效果 */
 99 | .text-flow-blue {
100 |   background-image: linear-gradient(
101 |     to right,
102 |     #0066cc,
103 |     #ffffff 12.5%,
104 |     #0066cc 25%,
105 |     #ffffff 37.5%,
106 |     #0066cc 50%,
107 |     #0066cc 100%
108 |   );
109 |   -webkit-text-fill-color: transparent;
110 |   -webkit-background-clip: text;
111 |   background-clip: text;
112 |   background-size: 400% 100%;
113 |   animation: light 2s infinite linear;
114 | }
115 | 
116 | /* 绿色流光效果 */
117 | .text-flow-green {
118 |   background-image: linear-gradient(
119 |     to right,
120 |     #00cc00,
121 |     #ffffff 12.5%,
122 |     #00cc00 25%,
123 |     #ffffff 37.5%,
124 |     #00cc00 50%,
125 |     #00cc00 100%
126 |   );
127 |   -webkit-text-fill-color: transparent;
128 |   -webkit-background-clip: text;
129 |   background-clip: text;
130 |   background-size: 400% 100%;
131 |   animation: light 2s infinite linear;
132 | }
133 | 
134 | /* 紫色流光效果 */
135 | .text-flow-purple {
136 |   background-image: linear-gradient(
137 |     to right,
138 |     #6600cc,
139 |     #ffffff 12.5%,
140 |     #6600cc 25%,
141 |     #ffffff 37.5%,
142 |     #6600cc 50%,
143 |     #6600cc 100%
144 |   );
145 |   -webkit-text-fill-color: transparent;
146 |   -webkit-background-clip: text;
147 |   background-clip: text;
148 |   background-size: 400% 100%;
149 |   animation: light 2s infinite linear;
150 | }
151 | 
152 | /* 大字体流光效果 */
153 | .text-flow-container.large .text-flow {
154 |   font-size: 3.5rem;
155 |   margin: 0 1.5rem;
156 | }
157 | 
158 | /* 慢速动画 */
159 | .text-flow.slow {
160 |   /* animation: light 4s infinite linear; */
161 | }
162 | 
163 | /* 快速动画 */
164 | .text-flow.fast {
165 |   animation: light 1s infinite linear;
166 | }
167 | 
168 | /* 流光动画关键帧 */
169 | @keyframes light {
170 |   0% {
171 |     background-position: 0 0;
172 |   }
173 |   100% {
174 |     background-position: -100% 0;
175 |   }
176 | }
177 | 
178 | /* 兼容性处理 */
179 | @-webkit-keyframes light {
180 |   0% {
181 |     background-position: 0 0;
182 |   }
183 |   100% {
184 |     background-position: -100% 0;
185 |   }
186 | }
187 | 
188 | /* 响应式设计 */
189 | @media (max-width: 768px) {
190 |   .test-container {
191 |     padding: 1rem;
192 |   }
193 |   
194 |   .text-flow {
195 |     font-size: 1.5rem;
196 |     margin: 0 0.5rem;
197 |   }
198 |   
199 |   .text-flow-container.large .text-flow {
200 |     font-size: 2.5rem;
201 |     margin: 0 1rem;
202 |   }
203 | } 
```

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

```java
 1 | /*
 2 |  * Licensed to the Apache Software Foundation (ASF) under one
 3 |  * or more contributor license agreements.  See the NOTICE file
 4 |  * distributed with this work for additional information
 5 |  * regarding copyright ownership.  The ASF licenses this file
 6 |  * to you under the Apache License, Version 2.0 (the
 7 |  * "License"); you may not use this file except in compliance
 8 |  * with the License.  You may obtain a copy of the License at
 9 |  *
10 |  *   http://www.apache.org/licenses/LICENSE-2.0
11 |  *
12 |  * Unless required by applicable law or agreed to in writing,
13 |  * software distributed under the License is distributed on an
14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 |  * KIND, either express or implied.  See the License for the
16 |  * specific language governing permissions and limitations
17 |  * under the License.
18 |  */
19 | 
20 | package com.alibaba.apiopenplatform.filter;
21 | 
22 | import cn.hutool.core.util.StrUtil;
23 | import com.alibaba.apiopenplatform.core.security.ContextHolder;
24 | import com.alibaba.apiopenplatform.service.PortalService;
25 | import lombok.RequiredArgsConstructor;
26 | import lombok.extern.slf4j.Slf4j;
27 | import org.jetbrains.annotations.NotNull;
28 | import org.springframework.web.filter.OncePerRequestFilter;
29 | 
30 | import javax.servlet.FilterChain;
31 | import javax.servlet.ServletException;
32 | import javax.servlet.http.HttpServletRequest;
33 | import javax.servlet.http.HttpServletResponse;
34 | import java.io.IOException;
35 | import java.net.URI;
36 | 
37 | @Slf4j
38 | @RequiredArgsConstructor
39 | public class PortalResolvingFilter extends OncePerRequestFilter {
40 | 
41 |     private final PortalService portalService;
42 | 
43 |     private final ContextHolder contextHolder;
44 | 
45 |     @Override
46 |     protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain chain)
47 |             throws ServletException, IOException {
48 |         try {
49 |             String origin = request.getHeader("Origin");
50 |             String host = request.getHeader("Host");
51 |             String xForwardedHost = request.getHeader("X-Forwarded-Host");
52 |             String xRealIp = request.getHeader("X-Real-IP");
53 |             String xForwardedFor = request.getHeader("X-Forwarded-For");
54 | 
55 |             String domain = null;
56 |             if (origin != null) {
57 |                 try {
58 |                     URI uri = new URI(origin);
59 |                     domain = uri.getHost();
60 |                 } catch (Exception ignored) {
61 |                 }
62 |             }
63 | 
64 |             log.info("域名解析调试 - Origin: {}, Host: {}, X-Forwarded-Host: {}, ServerName: {}, X-Real-IP: {}, X-Forwarded-For: {}",
65 |                     origin, host, xForwardedHost, request.getServerName(), xRealIp, xForwardedFor);
66 | 
67 |             if (domain == null) {
68 |                 // 优先使用Host头,如果没有则使用ServerName
69 |                 if (host != null && !host.isEmpty()) {
70 |                     domain = host.split(":")[0]; // 去掉端口号
71 |                 } else {
72 |                     domain = request.getServerName();
73 |                 }
74 |             }
75 |             String portalId = portalService.resolvePortal(domain);
76 | 
77 |             if (StrUtil.isNotBlank(portalId)) {
78 |                 contextHolder.savePortal(portalId);
79 |                 log.info("Resolved portal for domain: {} with portalId: {}", domain, portalId);
80 |             } else {
81 |                 log.info("No portal found for domain: {}", domain);
82 |                 String defaultPortalId = portalService.getDefaultPortal();
83 |                 if (StrUtil.isNotBlank(defaultPortalId)) {
84 |                     contextHolder.savePortal(defaultPortalId);
85 |                     log.info("Use default portal: {}", defaultPortalId);
86 |                 }
87 |             }
88 | 
89 |             chain.doFilter(request, response);
90 |         } finally {
91 |             contextHolder.clearPortal();
92 |         }
93 |     }
94 | }
95 | 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.service;
 21 | 
 22 | import com.alibaba.apiopenplatform.dto.params.gateway.ImportGatewayParam;
 23 | import com.alibaba.apiopenplatform.dto.params.gateway.QueryAPIGParam;
 24 | import com.alibaba.apiopenplatform.dto.params.gateway.QueryGatewayParam;
 25 | import com.alibaba.apiopenplatform.dto.result.GatewayMCPServerResult;
 26 | import com.alibaba.apiopenplatform.dto.result.*;
 27 | import com.alibaba.apiopenplatform.entity.Consumer;
 28 | import com.alibaba.apiopenplatform.entity.ConsumerCredential;
 29 | import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
 30 | import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
 31 | import org.springframework.data.domain.Pageable;
 32 | 
 33 | public interface GatewayService {
 34 | 
 35 |     /**
 36 |      * 获取APIG Gateway列表
 37 |      *
 38 |      * @param param
 39 |      * @param page
 40 |      * @param size
 41 |      * @return
 42 |      */
 43 |     PageResult<GatewayResult> fetchGateways(QueryAPIGParam param, int page, int size);
 44 | 
 45 |     /**
 46 |      * 导入Gateway
 47 |      *
 48 |      * @param param
 49 |      */
 50 |     void importGateway(ImportGatewayParam param);
 51 | 
 52 |     GatewayResult getGateway(String gatewayId);
 53 | 
 54 |     /**
 55 |      * 获取导入的Gateway列表
 56 |      *
 57 |      * @param param
 58 |      * @param pageable
 59 |      * @return
 60 |      */
 61 |     PageResult<GatewayResult> listGateways(QueryGatewayParam param, Pageable pageable);
 62 | 
 63 |     /**
 64 |      * 删除Gateway
 65 |      *
 66 |      * @param gatewayId
 67 |      */
 68 |     void deleteGateway(String gatewayId);
 69 | 
 70 |     /**
 71 |      * 拉取网关API列表
 72 |      *
 73 |      * @param gatewayId
 74 |      * @param apiType
 75 |      * @param page
 76 |      * @param size
 77 |      * @return
 78 |      */
 79 |     PageResult<APIResult> fetchAPIs(String gatewayId, String apiType, int page, int size);
 80 | 
 81 |     PageResult<APIResult> fetchHTTPAPIs(String gatewayId, int page, int size);
 82 | 
 83 |     PageResult<APIResult> fetchRESTAPIs(String gatewayId, int page, int size);
 84 | 
 85 |     PageResult<APIResult> fetchRoutes(String gatewayId, int page, int size);
 86 | 
 87 |     PageResult<GatewayMCPServerResult> fetchMcpServers(String gatewayId, int page, int size);
 88 | 
 89 |     String fetchAPIConfig(String gatewayId, Object config);
 90 | 
 91 |     String fetchMcpConfig(String gatewayId, Object conf);
 92 | 
 93 |     String createConsumer(Consumer consumer, ConsumerCredential credential, GatewayConfig config);
 94 | 
 95 |     void updateConsumer(String gwConsumerId, ConsumerCredential credential, GatewayConfig config);
 96 | 
 97 |     void deleteConsumer(String gwConsumerId, GatewayConfig config);
 98 | 
 99 |     /**
100 |      * 检查消费者是否存在于网关中
101 |      * @param gwConsumerId 网关消费者ID
102 |      * @param config 网关配置
103 |      * @return 是否存在
104 |      */
105 |     boolean isConsumerExists(String gwConsumerId, GatewayConfig config);
106 | 
107 |     ConsumerAuthConfig authorizeConsumer(String gatewayId, String gwConsumerId, ProductRefResult productRef);
108 | 
109 |     void revokeConsumerAuthorization(String gatewayId, String gwConsumerId, ConsumerAuthConfig config);
110 | 
111 |     GatewayConfig getGatewayConfig(String gatewayId);
112 | 
113 |     /**
114 |      * 获取仪表板URL
115 |      *
116 |      * @return 仪表板URL
117 |      */
118 |     String getDashboard(String gatewayId, String type);
119 | }
120 | 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.service;
 21 | 
 22 | import com.alibaba.apiopenplatform.dto.params.consumer.QueryConsumerParam;
 23 | import com.alibaba.apiopenplatform.dto.params.consumer.CreateConsumerParam;
 24 | import com.alibaba.apiopenplatform.dto.result.ConsumerResult;
 25 | import com.alibaba.apiopenplatform.dto.result.PageResult;
 26 | import com.alibaba.apiopenplatform.dto.result.ConsumerCredentialResult;
 27 | import com.alibaba.apiopenplatform.dto.params.consumer.CreateCredentialParam;
 28 | import com.alibaba.apiopenplatform.dto.params.consumer.UpdateCredentialParam;
 29 | import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
 30 | import com.alibaba.apiopenplatform.dto.params.consumer.CreateSubscriptionParam;
 31 | import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
 32 | import org.springframework.data.domain.Pageable;
 33 | 
 34 | public interface ConsumerService {
 35 | 
 36 |     /**
 37 |      * 创建Consumer
 38 |      *
 39 |      * @param param
 40 |      * @return
 41 |      */
 42 |     ConsumerResult createConsumer(CreateConsumerParam param);
 43 | 
 44 |     /**
 45 |      * 获取Consumer列表
 46 |      *
 47 |      * @param param
 48 |      * @param pageable
 49 |      * @return
 50 |      */
 51 |     PageResult<ConsumerResult> listConsumers(QueryConsumerParam param, Pageable pageable);
 52 | 
 53 |     /**
 54 |      * 查询Consumer
 55 |      *
 56 |      * @param consumerId
 57 |      * @return
 58 |      */
 59 |     ConsumerResult getConsumer(String consumerId);
 60 | 
 61 |     /**
 62 |      * 删除Consumer
 63 |      *
 64 |      * @param consumerId
 65 |      */
 66 |     void deleteConsumer(String consumerId);
 67 | 
 68 |     /**
 69 |      * 创建Consumer凭证
 70 |      *
 71 |      * @param consumerId
 72 |      * @param param
 73 |      */
 74 |     void createCredential(String consumerId, CreateCredentialParam param);
 75 | 
 76 |     /**
 77 |      * 获取Consumer凭证
 78 |      *
 79 |      * @param consumerId
 80 |      * @return
 81 |      */
 82 |     ConsumerCredentialResult getCredential(String consumerId);
 83 | 
 84 |     /**
 85 |      * 更新Consumer凭证
 86 |      *
 87 |      * @param consumerId
 88 |      * @param param
 89 |      */
 90 |     void updateCredential(String consumerId, UpdateCredentialParam param);
 91 | 
 92 |     /**
 93 |      * 删除Consumer凭证
 94 |      *
 95 |      * @param consumerId Consumer ID
 96 |      */
 97 |     void deleteCredential(String consumerId);
 98 | 
 99 |     /**
100 |      * 订阅API产品
101 |      *
102 |      * @param consumerId
103 |      * @param param
104 |      * @return
105 |      */
106 |     SubscriptionResult subscribeProduct(String consumerId, CreateSubscriptionParam param);
107 | 
108 |     /**
109 |      * 取消订阅
110 |      *
111 |      * @param consumerId
112 |      * @param productId
113 |      */
114 |     void unsubscribeProduct(String consumerId, String productId);
115 | 
116 |     /**
117 |      * 获取Consumer的订阅列表
118 |      *
119 |      * @param consumerId
120 |      * @param param
121 |      * @param pageable
122 |      * @return
123 |      */
124 |     PageResult<SubscriptionResult> listSubscriptions(String consumerId, QuerySubscriptionParam param, Pageable pageable);
125 | 
126 |     /**
127 |      * 取消订阅API产品
128 |      *
129 |      * @param consumerId
130 |      * @param productId
131 |      */
132 |     void deleteSubscription(String consumerId, String productId);
133 | 
134 |     /**
135 |      * 审批订阅API产品
136 |      *
137 |      * @param consumerId
138 |      * @param productId
139 |      */
140 |     SubscriptionResult approveSubscription(String consumerId, String productId);
141 | }
142 | 
```

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

```typescript
  1 | 
  2 | // 与 Admin 端保持一致的 API 产品配置接口
  3 | export interface ApiProductConfig {
  4 |   spec: string;
  5 |   meta: {
  6 |     source: string;
  7 |     type: string;
  8 |   }
  9 | }
 10 | 
 11 | export interface ApiProductMcpConfig {
 12 |   mcpServerName: string;
 13 |   tools: string;
 14 |   meta: {
 15 |     source: string;
 16 |     mcpServerName: string;
 17 |     mcpServerConfig: any;
 18 |     fromType: string;
 19 |     protocol?: string;
 20 |   }
 21 |   mcpServerConfig: {
 22 |     path: string;
 23 |     domains: {
 24 |       domain: string;
 25 |       protocol: string;
 26 |     }[];
 27 |     rawConfig?: unknown;
 28 |   }
 29 | }
 30 | 
 31 | export interface ApiProduct {
 32 |   productId: string;
 33 |   name: string;
 34 |   description: string;
 35 |   type: 'REST_API' | 'MCP_SERVER';
 36 |   category: string;
 37 |   status: 'PENDING' | 'READY' | 'PUBLISHED' | string;
 38 |   createAt: string;
 39 |   createdAt?: string; // 兼容字段
 40 |   enableConsumerAuth?: boolean;
 41 |   autoApprove?: boolean;
 42 |   apiConfig?: ApiProductConfig;
 43 |   mcpConfig?: ApiProductMcpConfig;
 44 |   document?: string;
 45 |   icon?: ProductIcon | null;
 46 |   // 向后兼容
 47 |   apiSpec?: string;
 48 | }
 49 | 
 50 | export const ProductType = {
 51 |   REST_API: 'REST_API',
 52 |   MCP_SERVER: 'MCP_SERVER',
 53 | } as const;
 54 | export type ProductType = typeof ProductType[keyof typeof ProductType];
 55 | 
 56 | // 产品状态枚举
 57 | export const ProductStatus = {
 58 |   ENABLE: 'ENABLE',
 59 |   DISABLE: 'DISABLE',
 60 | } as const;
 61 | export type ProductStatus = typeof ProductStatus[keyof typeof ProductStatus];
 62 | 
 63 | // 产品分类
 64 | export const ProductCategory = {
 65 |   OFFICIAL: 'official',
 66 |   COMMUNITY: 'community',
 67 |   CUSTOM: 'custom',
 68 | } as const;
 69 | export type ProductCategory = typeof ProductCategory[keyof typeof ProductCategory];
 70 | 
 71 | // 基础产品接口
 72 | export interface BaseProduct {
 73 |   productId: string;
 74 |   name: string;
 75 |   description: string;
 76 |   status: ProductStatus;
 77 |   enableConsumerAuth: boolean | null;
 78 |   autoApprove?: boolean;
 79 |   type: ProductType;
 80 |   document: string | null;
 81 |   icon: ProductIcon | null;
 82 |   category: ProductCategory;
 83 |   productType: ProductType;
 84 |   productName: string;
 85 |   mcpConfig: any;
 86 |   updatedAt: string;
 87 |   lastUpdated: string;
 88 | }
 89 | 
 90 | // REST API 产品
 91 | export interface RestApiProduct extends BaseProduct {
 92 |   apiSpec: string | null;
 93 |   mcpSpec: null;
 94 | }
 95 | 
 96 | // MCP Server 产品
 97 | // @ts-ignore
 98 | export interface McpServerProduct extends BaseProduct {
 99 |   apiSpec: null;
100 |   mcpSpec?: McpServerConfig; // 保持向后兼容
101 |   mcpConfig?: McpConfig; // 新的nacos格式
102 |   enabled?: boolean;
103 | }
104 | 
105 | // 联合类型
106 | export type Product = RestApiProduct | McpServerProduct;
107 | 
108 | // 产品图标类型(与 Admin 端保持一致)
109 | export interface ProductIcon {
110 |   type: 'URL' | 'BASE64';
111 |   value: string;
112 | }
113 | 
114 | // API 响应结构
115 | export interface ApiResponse<T> {
116 |   code: string;
117 |   message: string | null;
118 |   data: T;
119 | }
120 | 
121 | // 分页响应结构
122 | export interface PaginatedResponse<T> {
123 |   content: T[];
124 |   totalElements: number;
125 |   totalPages: number;
126 |   size: number;
127 |   number: number;
128 |   first: boolean;
129 |   last: boolean;
130 | }
131 | 
132 | // MCP 配置解析后的结构 (旧格式,保持向后兼容)
133 | export interface McpServerConfig {
134 |   mcpRouteId?: string;
135 |   mcpServerName?: string;
136 |   fromType?: string;
137 |   fromGatewayType?: string;
138 |   domains?: Array<{
139 |     domain: string;
140 |     protocol: string;
141 |   }>;
142 |   mcpServerConfig?: string; // YAML配置字符串
143 |   enabled?: boolean;
144 |   server?: {
145 |     name: string;
146 |     config: Record<string, unknown>;
147 |     allowTools: string[];
148 |   };
149 |   tools?: Array<{
150 |     name: string;
151 |     description: string;
152 |     args: Array<{
153 |       name: string;
154 |       description: string;
155 |       type: string;
156 |       required: boolean;
157 |       position: string;
158 |       default?: string;
159 |       enum?: string[];
160 |     }>;
161 |     requestTemplate: {
162 |       url: string;
163 |       method: string;
164 |       headers: Array<{
165 |         key: string;
166 |         value: string;
167 |       }>;
168 |     };
169 |     responseTemplate: {
170 |       body: string;
171 |     };
172 |   }>;
173 | }
174 | 
175 | // 新的nacos格式MCP配置
176 | export interface McpConfig {
177 |   mcpServerName: string;
178 |   mcpServerConfig: {
179 |     path: string;
180 |     domains: Array<{
181 |       domain: string;
182 |       protocol: string;
183 |     }>;
184 |     rawConfig?: string;
185 |   };
186 |   tools: string; // YAML格式的tools配置字符串
187 |   meta: {
188 |     source: string;
189 |     fromType: string;
190 |     protocol?: string;
191 |   };
192 | }
193 | 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.core.security;
 21 | 
 22 | import cn.hutool.core.util.EnumUtil;
 23 | import com.alibaba.apiopenplatform.core.constant.CommonConstants;
 24 | import com.alibaba.apiopenplatform.support.enums.UserType;
 25 | import org.springframework.security.authentication.AnonymousAuthenticationToken;
 26 | import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 27 | import org.springframework.security.core.Authentication;
 28 | import org.springframework.security.core.AuthenticationException;
 29 | import org.springframework.security.core.GrantedAuthority;
 30 | import org.springframework.security.core.context.SecurityContextHolder;
 31 | import org.springframework.stereotype.Component;
 32 | 
 33 | @Component
 34 | public class ContextHolder {
 35 | 
 36 |     private final ThreadLocal<String> portalContext = new ThreadLocal<>();
 37 | 
 38 |     public String getPortal() {
 39 |         return portalContext.get();
 40 |     }
 41 | 
 42 |     public void savePortal(String portalId) {
 43 |         portalContext.set(portalId);
 44 |     }
 45 | 
 46 |     public void clearPortal() {
 47 |         portalContext.remove();
 48 |     }
 49 | 
 50 |     /**
 51 |      * 获取当前认证用户ID
 52 |      *
 53 |      * @return
 54 |      */
 55 |     public String getUser() {
 56 |         Authentication authentication = getAuthenticationFromContext();
 57 |         Object principal = authentication.getPrincipal();
 58 |         if (principal instanceof String) {
 59 |             return (String) principal;
 60 |         }
 61 |         throw new AuthenticationCredentialsNotFoundException("User ID not found in authentication");
 62 |     }
 63 | 
 64 |     /**
 65 |      * 获取当前认证用户类型
 66 |      *
 67 |      * @return 用户类型
 68 |      * @throws AuthenticationException 如果用户未认证或类型无效
 69 |      */
 70 |     private UserType getCurrentUserType() {
 71 |         Authentication authentication = getAuthenticationFromContext();
 72 |         return authentication.getAuthorities().stream()
 73 |                 .map(GrantedAuthority::getAuthority)
 74 |                 .filter(authority -> authority.startsWith(CommonConstants.ROLE_PREFIX))
 75 |                 .map(authority -> authority.substring(5))
 76 |                 .map(role -> EnumUtil.likeValueOf(UserType.class, role))
 77 |                 .findFirst()
 78 |                 .orElseThrow(() -> new AuthenticationCredentialsNotFoundException("User type not found in authentication"));
 79 |     }
 80 | 
 81 |     public boolean isAdministrator() {
 82 |         try {
 83 |             return getCurrentUserType() == UserType.ADMIN;
 84 |         } catch (AuthenticationException e) {
 85 |             return false;
 86 |         }
 87 |     }
 88 | 
 89 |     public boolean isDeveloper() {
 90 |         try {
 91 |             return getCurrentUserType() == UserType.DEVELOPER;
 92 |         } catch (AuthenticationException e) {
 93 |             return false;
 94 |         }
 95 |     }
 96 | 
 97 |     /**
 98 |      * 获取当前认证信息
 99 |      *
100 |      * @return
101 |      */
102 |     private Authentication getAuthenticationFromContext() {
103 |         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
104 |         if (authentication == null || !authentication.isAuthenticated() ||
105 |                 authentication instanceof AnonymousAuthenticationToken) {
106 |             throw new AuthenticationCredentialsNotFoundException("No authenticated user found");
107 |         }
108 |         return authentication;
109 |     }
110 | }
111 | 
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.service;
 21 | 
 22 | import com.alibaba.apiopenplatform.core.event.PortalDeletingEvent;
 23 | import com.alibaba.apiopenplatform.dto.params.developer.CreateDeveloperParam;
 24 | import com.alibaba.apiopenplatform.dto.params.developer.CreateExternalDeveloperParam;
 25 | import com.alibaba.apiopenplatform.dto.params.developer.QueryDeveloperParam;
 26 | import com.alibaba.apiopenplatform.dto.params.developer.UpdateDeveloperParam;
 27 | import com.alibaba.apiopenplatform.dto.result.AuthResult;
 28 | import com.alibaba.apiopenplatform.dto.result.DeveloperResult;
 29 | import com.alibaba.apiopenplatform.dto.result.PageResult;
 30 | import com.alibaba.apiopenplatform.support.enums.DeveloperStatus;
 31 | import org.springframework.data.domain.Pageable;
 32 | 
 33 | import javax.servlet.http.HttpServletRequest;
 34 | 
 35 | public interface DeveloperService {
 36 | 
 37 |     /**
 38 |      * 开发者注册
 39 |      *
 40 |      * @param param
 41 |      * @return
 42 |      */
 43 |     AuthResult registerDeveloper(CreateDeveloperParam param);
 44 | 
 45 |     /**
 46 |      * 创建开发者
 47 |      *
 48 |      * @param param
 49 |      * @return
 50 |      */
 51 |     DeveloperResult createDeveloper(CreateDeveloperParam param);
 52 | 
 53 |     /**
 54 |      * 开发者登录
 55 |      *
 56 |      * @param username
 57 |      * @param password
 58 |      * @return
 59 |      */
 60 |     AuthResult login(String username, String password);
 61 | 
 62 |     /**
 63 |      * 校验Developer
 64 |      *
 65 |      * @param developerId
 66 |      */
 67 |     void existsDeveloper(String developerId);
 68 | 
 69 |     /**
 70 |      * 获取外部开发者详情
 71 |      *
 72 |      * @param provider
 73 |      * @param subject
 74 |      * @return
 75 |      */
 76 |     DeveloperResult getExternalDeveloper(String provider, String subject);
 77 | 
 78 |     /**
 79 |      * 外部账号创建开发者
 80 |      *
 81 |      * @param param
 82 |      * @return
 83 |      */
 84 |     DeveloperResult createExternalDeveloper(CreateExternalDeveloperParam param);
 85 | 
 86 |     /**
 87 |      * 删除开发者账号(删除账号及所有外部身份)
 88 |      *
 89 |      * @param developerId
 90 |      */
 91 |     void deleteDeveloper(String developerId);
 92 | 
 93 |     /**
 94 |      * 查询开发者详情
 95 |      *
 96 |      * @param developerId
 97 |      * @return
 98 |      */
 99 |     DeveloperResult getDeveloper(String developerId);
100 | 
101 |     /**
102 |      * 查询门户下的开发者列表
103 |      *
104 |      * @param param
105 |      * @param pageable
106 |      * @return
107 |      */
108 |     PageResult<DeveloperResult> listDevelopers(QueryDeveloperParam param, Pageable pageable);
109 | 
110 |     /**
111 |      * 设置开发者状态
112 |      *
113 |      * @param developerId
114 |      * @param status
115 |      * @return
116 |      */
117 |     void setDeveloperStatus(String developerId, DeveloperStatus status);
118 | 
119 |     /**
120 |      * 开发者修改密码
121 |      *
122 |      * @param developerId
123 |      * @param oldPassword
124 |      * @param newPassword
125 |      * @return
126 |      */
127 |     boolean resetPassword(String developerId, String oldPassword, String newPassword);
128 | 
129 |     /**
130 |      * 开发者更新个人信息
131 |      *
132 |      * @param param
133 |      * @return
134 |      */
135 |     boolean updateProfile(UpdateDeveloperParam param);
136 | 
137 |     /**
138 |      * 清理门户资源
139 |      *
140 |      * @param event
141 |      */
142 |     void handlePortalDeletion(PortalDeletingEvent event);
143 | 
144 |     /**
145 |      * 开发者登出
146 |      *
147 |      * @param request HTTP请求
148 |      */
149 |     void logout(HttpServletRequest request);
150 | 
151 |     /**
152 |      * 获取当前登录开发者信息
153 |      *
154 |      * @return 开发者信息
155 |      */
156 |     DeveloperResult getCurrentDeveloperInfo();
157 | 
158 |     /**
159 |      * 当前开发者修改密码
160 |      *
161 |      * @param oldPassword 旧密码
162 |      * @param newPassword 新密码
163 |      * @return 是否成功
164 |      */
165 |     boolean changeCurrentDeveloperPassword(String oldPassword, String newPassword);
166 | }
```

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

```java
  1 | /*
  2 |  * Licensed to the Apache Software Foundation (ASF) under one
  3 |  * or more contributor license agreements.  See the NOTICE file
  4 |  * distributed with this work for additional information
  5 |  * regarding copyright ownership.  The ASF licenses this file
  6 |  * to you under the Apache License, Version 2.0 (the
  7 |  * "License"); you may not use this file except in compliance
  8 |  * with the License.  You may obtain a copy of the License at
  9 |  *
 10 |  *   http://www.apache.org/licenses/LICENSE-2.0
 11 |  *
 12 |  * Unless required by applicable law or agreed to in writing,
 13 |  * software distributed under the License is distributed on an
 14 |  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15 |  * KIND, either express or implied.  See the License for the
 16 |  * specific language governing permissions and limitations
 17 |  * under the License.
 18 |  */
 19 | 
 20 | package com.alibaba.apiopenplatform.controller;
 21 | 
 22 | import com.alibaba.apiopenplatform.core.annotation.AdminAuth;
 23 | import com.alibaba.apiopenplatform.dto.params.consumer.QuerySubscriptionParam;
 24 | import com.alibaba.apiopenplatform.dto.params.portal.*;
 25 | import com.alibaba.apiopenplatform.dto.result.PageResult;
 26 | import com.alibaba.apiopenplatform.dto.result.PortalResult;
 27 | import com.alibaba.apiopenplatform.dto.result.SubscriptionResult;
 28 | import com.alibaba.apiopenplatform.service.PortalService;
 29 | import io.swagger.v3.oas.annotations.Operation;
 30 | import io.swagger.v3.oas.annotations.tags.Tag;
 31 | import lombok.RequiredArgsConstructor;
 32 | import lombok.extern.slf4j.Slf4j;
 33 | import org.springframework.data.domain.Pageable;
 34 | import org.springframework.validation.annotation.Validated;
 35 | import org.springframework.web.bind.annotation.*;
 36 | 
 37 | import javax.validation.Valid;
 38 | 
 39 | @RestController
 40 | @RequestMapping("/portals")
 41 | @Slf4j
 42 | @Validated
 43 | @Tag(name = "门户管理")
 44 | @AdminAuth
 45 | @RequiredArgsConstructor
 46 | public class PortalController {
 47 | 
 48 |     private final PortalService portalService;
 49 | 
 50 |     @Operation(summary = "创建门户")
 51 |     @PostMapping
 52 |     public PortalResult createPortal(@Valid @RequestBody CreatePortalParam param) {
 53 |         return portalService.createPortal(param);
 54 |     }
 55 | 
 56 |     @Operation(summary = "获取门户详情")
 57 |     @GetMapping("/{portalId}")
 58 |     public PortalResult getPortal(@PathVariable String portalId) {
 59 |         return portalService.getPortal(portalId);
 60 |     }
 61 | 
 62 |     @Operation(summary = "获取门户列表")
 63 |     @GetMapping
 64 |     public PageResult<PortalResult> listPortals(Pageable pageable) {
 65 |         return portalService.listPortals(pageable);
 66 |     }
 67 | 
 68 |     @Operation(summary = "更新门户信息")
 69 |     @PutMapping("/{portalId}")
 70 |     public PortalResult updatePortal(@PathVariable String portalId, @Valid @RequestBody UpdatePortalParam param) {
 71 |         return portalService.updatePortal(portalId, param);
 72 |     }
 73 | 
 74 |     @Operation(summary = "删除门户")
 75 |     @DeleteMapping("/{portalId}")
 76 |     public void deletePortal(@PathVariable String portalId) {
 77 |         portalService.deletePortal(portalId);
 78 |     }
 79 | 
 80 |     @Operation(summary = "绑定域名")
 81 |     @PostMapping("/{portalId}/domains")
 82 |     public PortalResult bindDomain(@PathVariable String portalId, @Valid @RequestBody BindDomainParam param) {
 83 |         return portalService.bindDomain(portalId, param);
 84 |     }
 85 | 
 86 |     @Operation(summary = "解绑域名")
 87 |     @DeleteMapping("/{portalId}/domains/{domain}")
 88 |     public PortalResult unbindDomain(@PathVariable String portalId, @PathVariable String domain) {
 89 |         return portalService.unbindDomain(portalId, domain);
 90 |     }
 91 | 
 92 |     @Operation(summary = "获取门户上的API产品订阅列表")
 93 |     @GetMapping("/{portalId}/subscriptions")
 94 |     public PageResult<SubscriptionResult> listSubscriptions(@PathVariable String portalId,
 95 |                                                             QuerySubscriptionParam param,
 96 |                                                             Pageable pageable) {
 97 |         return portalService.listSubscriptions(portalId, param, pageable);
 98 |     }
 99 | 
100 |     @Operation(summary = "获取门户Dashboard监控面板URL")
101 |     @GetMapping("/{portalId}/dashboard")
102 |     public String getDashboard(@PathVariable String portalId,
103 |                                @RequestParam(required = false, defaultValue = "Portal") String type) {
104 |         return portalService.getDashboard(portalId);
105 |     }
106 | }
107 | 
```
Page 3/9FirstPrevNextLast