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 | ```