#
tokens: 25663/50000 2/349 files (page 9/9)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 9 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-web/api-portal-admin/src/components/portal/ThirdPartyAuthManager.tsx:
--------------------------------------------------------------------------------

```typescript
  1 | import {useState} from 'react'
  2 | import {Button, Form, Input, Select, Switch, Table, Modal, Space, message, Divider, Steps, Card, Tabs, Collapse, Radio} from 'antd'
  3 | import {PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, MinusCircleOutlined, KeyOutlined, CheckCircleFilled, MinusCircleFilled} from '@ant-design/icons'
  4 | import {ThirdPartyAuthConfig, AuthenticationType, GrantType, AuthCodeConfig, OAuth2Config, OidcConfig, PublicKeyFormat} from '@/types'
  5 | 
  6 | interface ThirdPartyAuthManagerProps {
  7 |   configs: ThirdPartyAuthConfig[]
  8 |   onSave: (configs: ThirdPartyAuthConfig[]) => Promise<void>
  9 | }
 10 | 
 11 | export function ThirdPartyAuthManager({configs, onSave}: ThirdPartyAuthManagerProps) {
 12 |   const [form] = Form.useForm()
 13 |   const [modalVisible, setModalVisible] = useState(false)
 14 |   const [loading, setLoading] = useState(false)
 15 |   const [editingConfig, setEditingConfig] = useState<ThirdPartyAuthConfig | null>(null)
 16 |   const [currentStep, setCurrentStep] = useState(0)
 17 |   const [selectedType, setSelectedType] = useState<AuthenticationType | null>(null)
 18 | 
 19 | 
 20 |   // 添加新配置
 21 |   const handleAdd = () => {
 22 |     setEditingConfig(null)
 23 |     setSelectedType(null)
 24 |     setCurrentStep(0)
 25 |     setModalVisible(true)
 26 |     form.resetFields()
 27 |   }
 28 | 
 29 |   // 编辑配置
 30 |   const handleEdit = (config: ThirdPartyAuthConfig) => {
 31 |     setEditingConfig(config)
 32 |     setSelectedType(config.type)
 33 |     setCurrentStep(1) // 直接进入配置步骤
 34 |     setModalVisible(true)
 35 |     
 36 |     // 根据类型设置表单值
 37 |     if (config.type === AuthenticationType.OIDC) {
 38 |       // OIDC配置:直接使用OidcConfig的字段
 39 |       const oidcConfig = config as (OidcConfig & { type: AuthenticationType.OIDC })
 40 |       
 41 |       // 检查是否是手动配置模式(有具体的端点地址)
 42 |       const hasManualEndpoints = !!(oidcConfig.authCodeConfig?.authorizationEndpoint && 
 43 |                                    oidcConfig.authCodeConfig?.tokenEndpoint && 
 44 |                                    oidcConfig.authCodeConfig?.userInfoEndpoint)
 45 |       
 46 |       form.setFieldsValue({
 47 |         provider: oidcConfig.provider,
 48 |         name: oidcConfig.name,
 49 |         enabled: oidcConfig.enabled,
 50 |         type: oidcConfig.type,
 51 |         configMode: hasManualEndpoints ? 'manual' : 'auto',
 52 |         ...oidcConfig.authCodeConfig,
 53 |         // 身份映射字段可能在根级别或authCodeConfig中
 54 |         userIdField: oidcConfig.identityMapping?.userIdField || oidcConfig.authCodeConfig?.identityMapping?.userIdField,
 55 |         userNameField: oidcConfig.identityMapping?.userNameField || oidcConfig.authCodeConfig?.identityMapping?.userNameField,
 56 |         emailField: oidcConfig.identityMapping?.emailField || oidcConfig.authCodeConfig?.identityMapping?.emailField
 57 |       })
 58 |     } else if (config.type === AuthenticationType.OAUTH2) {
 59 |       // OAuth2配置:直接使用OAuth2Config的字段
 60 |       const oauth2Config = config as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
 61 |       form.setFieldsValue({
 62 |         provider: oauth2Config.provider,
 63 |         name: oauth2Config.name,
 64 |         enabled: oauth2Config.enabled,
 65 |         type: oauth2Config.type,
 66 |         grantType: oauth2Config.grantType || GrantType.JWT_BEARER, // 确保有默认值
 67 |         userIdField: oauth2Config.identityMapping?.userIdField,
 68 |         userNameField: oauth2Config.identityMapping?.userNameField,
 69 |         emailField: oauth2Config.identityMapping?.emailField,
 70 |         publicKeys: oauth2Config.jwtBearerConfig?.publicKeys || []
 71 |       })
 72 |     }
 73 |   }
 74 | 
 75 |   // 删除配置
 76 |   const handleDelete = async (provider: string, name: string) => {
 77 |     Modal.confirm({
 78 |       title: '确认删除',
 79 |       icon: <ExclamationCircleOutlined/>,
 80 |       content: `确定要删除第三方认证配置 "${name}" 吗?此操作不可恢复。`,
 81 |       okText: '确认删除',
 82 |       okType: 'danger',
 83 |       cancelText: '取消',
 84 |       async onOk() {
 85 |         try {
 86 |           const updatedConfigs = configs.filter(config => config.provider !== provider)
 87 |           await onSave(updatedConfigs)
 88 |           message.success('第三方认证配置删除成功')
 89 |         } catch (error) {
 90 |           message.error('删除第三方认证配置失败')
 91 |         }
 92 |       },
 93 |     })
 94 |   }
 95 | 
 96 | 
 97 |   // 下一步
 98 |   const handleNext = async () => {
 99 |     if (currentStep === 0) {
100 |       try {
101 |         const values = await form.validateFields(['type'])
102 |         setSelectedType(values.type)
103 |         setCurrentStep(1)
104 |         
105 |         // 为不同类型设置默认值
106 |         if (values.type === AuthenticationType.OAUTH2) {
107 |           form.setFieldsValue({
108 |             grantType: GrantType.JWT_BEARER,
109 |             enabled: true
110 |           })
111 |         } else if (values.type === AuthenticationType.OIDC) {
112 |           form.setFieldsValue({
113 |             enabled: true,
114 |             configMode: 'auto'
115 |           })
116 |         }
117 |       } catch (error) {
118 |         // 验证失败
119 |       }
120 |     }
121 |   }
122 | 
123 |   // 上一步
124 |   const handlePrevious = () => {
125 |     setCurrentStep(0)
126 |   }
127 | 
128 |   // 保存配置
129 |   const handleSave = async () => {
130 |     try {
131 |       setLoading(true)
132 |       
133 |       const values = await form.validateFields()
134 | 
135 |       let newConfig: ThirdPartyAuthConfig
136 | 
137 |       if (selectedType === AuthenticationType.OIDC) {
138 |         // OIDC配置:根据配置模式创建不同的authCodeConfig
139 |         let authCodeConfig: AuthCodeConfig
140 |         
141 |         if (values.configMode === 'auto') {
142 |           // 自动发现模式:只保存issuer,端点置空(后端会通过issuer自动发现)
143 |           authCodeConfig = {
144 |             clientId: values.clientId,
145 |             clientSecret: values.clientSecret,
146 |             scopes: values.scopes,
147 |             issuer: values.issuer,
148 |             authorizationEndpoint: '',  // 自动发现模式下端点为空
149 |             tokenEndpoint: '',
150 |             userInfoEndpoint: '',
151 |             jwkSetUri: '',
152 |             // 可选的身份映射配置
153 |             identityMapping: (values.userIdField || values.userNameField || values.emailField) ? {
154 |               userIdField: values.userIdField || null,
155 |               userNameField: values.userNameField || null,
156 |               emailField: values.emailField || null
157 |             } : undefined
158 |           }
159 |         } else {
160 |           // 手动配置模式:保存具体的端点地址
161 |           authCodeConfig = {
162 |             clientId: values.clientId,
163 |             clientSecret: values.clientSecret,
164 |             scopes: values.scopes,
165 |             issuer: values.issuer || '',  // 手动配置模式下issuer可选
166 |             authorizationEndpoint: values.authorizationEndpoint,
167 |             tokenEndpoint: values.tokenEndpoint,
168 |             userInfoEndpoint: values.userInfoEndpoint,
169 |             jwkSetUri: values.jwkSetUri || '',
170 |             // 可选的身份映射配置
171 |             identityMapping: (values.userIdField || values.userNameField || values.emailField) ? {
172 |               userIdField: values.userIdField || null,
173 |               userNameField: values.userNameField || null,
174 |               emailField: values.emailField || null
175 |             } : undefined
176 |           }
177 |         }
178 | 
179 |         newConfig = {
180 |           provider: values.provider,
181 |           name: values.name,
182 |           logoUrl: null,
183 |           enabled: values.enabled ?? true,
184 |           grantType: 'AUTHORIZATION_CODE' as const,
185 |           authCodeConfig,
186 |           // 根级别的身份映射(为兼容后端格式)
187 |           identityMapping: authCodeConfig.identityMapping,
188 |           type: AuthenticationType.OIDC
189 |         } as (OidcConfig & { type: AuthenticationType.OIDC })
190 |       } else {
191 |         // OAuth2配置:直接创建OAuth2Config格式
192 |         const grantType = values.grantType || GrantType.JWT_BEARER // 确保有默认值
193 |         newConfig = {
194 |           provider: values.provider,
195 |           name: values.name,
196 |           enabled: values.enabled ?? true,
197 |           grantType: grantType,
198 |           jwtBearerConfig: grantType === GrantType.JWT_BEARER ? {
199 |             publicKeys: values.publicKeys || []
200 |           } : undefined,
201 |           identityMapping: {
202 |             userIdField: values.userIdField || null,
203 |             userNameField: values.userNameField || null,
204 |             emailField: values.emailField || null
205 |           },
206 |           type: AuthenticationType.OAUTH2
207 |         } as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
208 |       }
209 | 
210 |       let updatedConfigs
211 |       if (editingConfig) {
212 |         updatedConfigs = configs.map(config => 
213 |           config.provider === editingConfig.provider ? newConfig : config
214 |         )
215 |       } else {
216 |         updatedConfigs = [...configs, newConfig]
217 |       }
218 | 
219 |       await onSave(updatedConfigs)
220 |       
221 |       message.success(editingConfig ? '第三方认证配置更新成功' : '第三方认证配置添加成功')
222 |       setModalVisible(false)
223 |     } catch (error) {
224 |       message.error('保存第三方认证配置失败')
225 |     } finally {
226 |       setLoading(false)
227 |     }
228 |   }
229 | 
230 |   // 取消
231 |   const handleCancel = () => {
232 |     setModalVisible(false)
233 |     setEditingConfig(null)
234 |     setSelectedType(null)
235 |     setCurrentStep(0)
236 |     form.resetFields()
237 |   }
238 | 
239 |   // OIDC表格列定义(不包含类型列)
240 |   const oidcColumns = [
241 |     {
242 |       title: '提供商',
243 |       dataIndex: 'provider',
244 |       key: 'provider',
245 |       width: 120,
246 |       render: (provider: string) => (
247 |         <span className="font-medium text-gray-700">{provider}</span>
248 |       )
249 |     },
250 |     {
251 |       title: '名称',
252 |       dataIndex: 'name',
253 |       key: 'name',
254 |       width: 150,
255 |     },
256 |     {
257 |       title: '授权模式',
258 |       key: 'grantType',
259 |       width: 120,
260 |       render: () => <span className="text-gray-600">授权码模式</span>
261 |     },
262 |     {
263 |       title: '状态',
264 |       dataIndex: 'enabled',
265 |       key: 'enabled',
266 |       width: 80,
267 |       render: (enabled: boolean) => (
268 |         <div className="flex items-center">
269 |           {enabled ? (
270 |             <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '12px'}} />
271 |           ) : (
272 |             <MinusCircleFilled className="text-gray-500 mr-2" style={{fontSize: '12px'}} />
273 |           )}
274 |           <span className="text-gray-700">
275 |             {enabled ? '已启用' : '已停用'}
276 |           </span>
277 |         </div>
278 |       )
279 |     },
280 |     {
281 |       title: '操作',
282 |       key: 'action',
283 |       width: 150,
284 |       render: (_: any, record: ThirdPartyAuthConfig) => (
285 |         <Space>
286 |           <Button
287 |             type="link"
288 |             icon={<EditOutlined/>}
289 |             onClick={() => handleEdit(record)}
290 |           >
291 |             编辑
292 |           </Button>
293 |           <Button
294 |             type="link"
295 |             danger
296 |             icon={<DeleteOutlined/>}
297 |             onClick={() => handleDelete(record.provider, record.name)}
298 |           >
299 |             删除
300 |           </Button>
301 |         </Space>
302 |       )
303 |     }
304 |   ]
305 | 
306 |   // OAuth2表格列定义(不包含类型列)
307 |   const oauth2Columns = [
308 |     {
309 |       title: '提供商',
310 |       dataIndex: 'provider',
311 |       key: 'provider',
312 |       width: 120,
313 |       render: (provider: string) => (
314 |         <span className="font-medium text-gray-700">{provider}</span>
315 |       )
316 |     },
317 |     {
318 |       title: '名称',
319 |       dataIndex: 'name',
320 |       key: 'name',
321 |       width: 150,
322 |     },
323 |     {
324 |       title: '授权模式',
325 |       key: 'grantType',
326 |       width: 120,
327 |       render: (record: ThirdPartyAuthConfig) => {
328 |         if (record.type === AuthenticationType.OAUTH2) {
329 |           const oauth2Config = record as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
330 |           return (
331 |             <span className="text-gray-600">
332 |               {oauth2Config.grantType === GrantType.JWT_BEARER ? 'JWT断言' : '授权码模式'}
333 |             </span>
334 |           )
335 |         }
336 |         return <span className="text-gray-600">授权码模式</span>
337 |       }
338 |     },
339 |     {
340 |       title: '状态',
341 |       dataIndex: 'enabled',
342 |       key: 'enabled',
343 |       width: 80,
344 |       render: (enabled: boolean) => (
345 |         <div className="flex items-center">
346 |           {enabled ? (
347 |             <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '12px'}} />
348 |           ) : (
349 |             <MinusCircleFilled className="text-gray-500 mr-2" style={{fontSize: '12px'}} />
350 |           )}
351 |           <span className="text-gray-700">
352 |             {enabled ? '已启用' : '已停用'}
353 |           </span>
354 |         </div>
355 |       )
356 |     },
357 |     {
358 |       title: '操作',
359 |       key: 'action',
360 |       width: 150,
361 |       render: (_: any, record: ThirdPartyAuthConfig) => (
362 |         <Space>
363 |           <Button
364 |             type="link"
365 |             icon={<EditOutlined/>}
366 |             onClick={() => handleEdit(record)}
367 |           >
368 |             编辑
369 |           </Button>
370 |           <Button
371 |             type="link"
372 |             danger
373 |             icon={<DeleteOutlined/>}
374 |             onClick={() => handleDelete(record.provider, record.name)}
375 |           >
376 |             删除
377 |           </Button>
378 |         </Space>
379 |       )
380 |     }
381 |   ]
382 | 
383 |   // 渲染OIDC配置表单
384 |   const renderOidcForm = () => (
385 |     <div className="space-y-6">
386 |       <Form.Item
387 |         name="grantType"
388 |         label="授权模式"
389 |         initialValue="AUTHORIZATION_CODE"
390 |       >
391 |         <Select disabled>
392 |           <Select.Option value="AUTHORIZATION_CODE">授权码模式</Select.Option>
393 |         </Select>
394 |       </Form.Item>
395 | 
396 |       <div className="grid grid-cols-2 gap-4">
397 |         <Form.Item
398 |           name="clientId"
399 |           label="Client ID"
400 |           rules={[{required: true, message: '请输入 Client ID'}]}
401 |         >
402 |           <Input placeholder="Client ID"/>
403 |         </Form.Item>
404 |         <Form.Item
405 |           name="clientSecret"
406 |           label="Client Secret"
407 |           rules={[{required: true, message: '请输入 Client Secret'}]}
408 |         >
409 |           <Input.Password placeholder="Client Secret"/>
410 |         </Form.Item>
411 |       </div>
412 | 
413 |       <div className="grid grid-cols-2 gap-4">
414 |         <Form.Item
415 |           name="scopes"
416 |           label="授权范围"
417 |           rules={[{required: true, message: '请输入授权范围'}]}
418 |         >
419 |           <Input placeholder="如: openid profile email"/>
420 |         </Form.Item>
421 |         <div></div>
422 |       </div>
423 | 
424 |       <Divider />
425 | 
426 |       {/* 配置模式选择 */}
427 |       <Form.Item
428 |         name="configMode"
429 |         label="端点配置"
430 |         initialValue="auto"
431 |       >
432 |         <Radio.Group>
433 |           <Radio value="auto">自动发现</Radio>
434 |           <Radio value="manual">手动配置</Radio>
435 |         </Radio.Group>
436 |       </Form.Item>
437 | 
438 |       {/* 根据配置模式显示不同字段 */}
439 |       <Form.Item
440 |         noStyle
441 |         shouldUpdate={(prevValues, curValues) => prevValues.configMode !== curValues.configMode}
442 |       >
443 |         {({ getFieldValue }) => {
444 |           const configMode = getFieldValue('configMode') || 'auto'
445 |           
446 |           if (configMode === 'auto') {
447 |             // 自动发现模式:只需要Issuer地址
448 |             return (
449 |               <Form.Item
450 |                 name="issuer"
451 |                 label="Issuer"
452 |                 rules={[
453 |                   { required: true, message: '请输入Issuer地址' },
454 |                   { type: 'url', message: '请输入有效的URL' }
455 |                 ]}
456 |               >
457 |                 <Input placeholder="如: https://accounts.google.com" />
458 |               </Form.Item>
459 |             )
460 |           } else {
461 |             // 手动配置模式:需要各个端点
462 |             return (
463 |               <div className="space-y-4">
464 |                 <div className="grid grid-cols-2 gap-4">
465 |                   <Form.Item
466 |                     name="authorizationEndpoint"
467 |                     label="授权端点"
468 |                     rules={[{ required: true, message: '请输入授权端点' }]}
469 |                   >
470 |                     <Input placeholder="Authorization 授权端点"/>
471 |                   </Form.Item>
472 |                   <Form.Item
473 |                     name="tokenEndpoint"
474 |                     label="令牌端点"
475 |                     rules={[{ required: true, message: '请输入令牌端点' }]}
476 |                   >
477 |                     <Input placeholder="Token 令牌端点"/>
478 |                   </Form.Item>
479 |                 </div>
480 |                 <div className="grid grid-cols-2 gap-4">
481 |                   <Form.Item
482 |                     name="userInfoEndpoint"
483 |                     label="用户信息端点"
484 |                     rules={[{ required: true, message: '请输入用户信息端点' }]}
485 |                   >
486 |                     <Input placeholder="UserInfo 端点"/>
487 |                   </Form.Item>
488 |                   <Form.Item
489 |                     name="jwkSetUri"
490 |                     label="公钥端点"
491 |                   >
492 |                     <Input placeholder="可选"/>
493 |                   </Form.Item>
494 |                 </div>
495 |               </div>
496 |             )
497 |           }
498 |         }}
499 |       </Form.Item>
500 | 
501 |       <div className="-ml-3">
502 |         <Collapse
503 |           size="small"
504 |           ghost
505 |           expandIcon={({ isActive }) => (
506 |             <svg 
507 |               className={`w-4 h-4 transition-transform ${isActive ? 'rotate-90' : ''}`}
508 |               fill="currentColor" 
509 |               viewBox="0 0 20 20"
510 |             >
511 |               <path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
512 |             </svg>
513 |           )}
514 |           items={[
515 |             {
516 |               key: 'advanced',
517 |               label: (
518 |                 <div className="flex items-center text-gray-600">
519 |                   <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
520 |                     <path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947z" clipRule="evenodd" />
521 |                     <path fillRule="evenodd" d="M10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
522 |                   </svg>
523 |                   <span className="ml-2">高级配置</span>
524 |                   <span className="text-xs text-gray-400 ml-2">身份映射</span>
525 |                 </div>
526 |               ),
527 |               children: (
528 |                 <div className="space-y-4 pt-2 ml-3">
529 |                   <div className="grid grid-cols-3 gap-4">
530 |                     <Form.Item
531 |                       name="userIdField"
532 |                       label="开发者ID"
533 |                     >
534 |                       <Input placeholder="默认: sub"/>
535 |                     </Form.Item>
536 |                     <Form.Item
537 |                       name="userNameField"
538 |                       label="开发者名称"
539 |                     >
540 |                       <Input placeholder="默认: name"/>
541 |                     </Form.Item>
542 |                     <Form.Item
543 |                       name="emailField"
544 |                       label="邮箱"
545 |                     >
546 |                       <Input placeholder="默认: email"/>
547 |                     </Form.Item>
548 |                   </div>
549 | 
550 |                 <div className="bg-blue-50 p-3 rounded-lg">
551 |                   <div className="flex items-start space-x-2">
552 |                     <div className="text-blue-600 mt-0.5">
553 |                       <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
554 |                         <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
555 |                       </svg>
556 |                     </div>
557 |                     <div>
558 |                       <h4 className="text-blue-800 font-medium text-sm">配置说明</h4>
559 |                       <p className="text-blue-700 text-xs mt-1">
560 |                         身份映射用于从OIDC令牌中提取用户信息。如果不填写,系统将使用OIDC标准字段。
561 |                       </p>
562 |                     </div>
563 |                   </div>
564 |                 </div>
565 |               </div>
566 |             )
567 |           }
568 |         ]}
569 |       />
570 |       </div>
571 |     </div>
572 |   )
573 | 
574 |   // 渲染OAuth2配置表单
575 |   const renderOAuth2Form = () => (
576 |     <div className="space-y-6">
577 |       <Form.Item
578 |         name="grantType"
579 |         label="授权模式"
580 |         initialValue={GrantType.JWT_BEARER}
581 |         rules={[{required: true}]}
582 |       >
583 |         <Select disabled>
584 |           <Select.Option value={GrantType.JWT_BEARER}>JWT断言</Select.Option>
585 |         </Select>
586 |       </Form.Item>
587 | 
588 |       <Form.List name="publicKeys">
589 |         {(fields, { add, remove }) => (
590 |           <div className="space-y-4">
591 |             {fields.length > 0 && (
592 |               <Collapse
593 |                 size="small"
594 |                 items={fields.map(({ key, name, ...restField }) => ({
595 |                   key: key,
596 |                   label: (
597 |                     <div className="flex items-center">
598 |                       <KeyOutlined className="mr-2" />
599 |                       <span>公钥 {name + 1}</span>
600 |                     </div>
601 |                   ),
602 |                   extra: (
603 |                     <Button
604 |                       type="link"
605 |                       danger
606 |                       size="small"
607 |                       icon={<MinusCircleOutlined />}
608 |                       onClick={(e) => {
609 |                         e.stopPropagation()
610 |                         remove(name)
611 |                       }}
612 |                     >
613 |                       删除
614 |                     </Button>
615 |                   ),
616 |                   children: (
617 |                     <div className="space-y-4 px-4">
618 |                       <div className="grid grid-cols-3 gap-4">
619 |                         <Form.Item
620 |                           {...restField}
621 |                           name={[name, 'kid']}
622 |                           label="Key ID"
623 |                           rules={[{ required: true, message: '请输入Key ID' }]}
624 |                         >
625 |                           <Input placeholder="公钥标识符" size="small" />
626 |                         </Form.Item>
627 |                         <Form.Item
628 |                           {...restField}
629 |                           name={[name, 'algorithm']}
630 |                           label="签名算法"
631 |                           rules={[{ required: true, message: '请选择签名算法' }]}
632 |                         >
633 |                           <Select placeholder="选择签名算法" size="small">
634 |                             <Select.Option value="RS256">RS256</Select.Option>
635 |                             <Select.Option value="RS384">RS384</Select.Option>
636 |                             <Select.Option value="RS512">RS512</Select.Option>
637 |                             <Select.Option value="ES256">ES256</Select.Option>
638 |                             <Select.Option value="ES384">ES384</Select.Option>
639 |                             <Select.Option value="ES512">ES512</Select.Option>
640 |                           </Select>
641 |                         </Form.Item>
642 |                         <Form.Item
643 |                           {...restField}
644 |                           name={[name, 'format']}
645 |                           label="公钥格式"
646 |                           rules={[{ required: true, message: '请选择公钥格式' }]}
647 |                         >
648 |                           <Select placeholder="选择公钥格式" size="small">
649 |                             <Select.Option value={PublicKeyFormat.PEM}>PEM</Select.Option>
650 |                             <Select.Option value={PublicKeyFormat.JWK}>JWK</Select.Option>
651 |                           </Select>
652 |                         </Form.Item>
653 |                       </div>
654 | 
655 |                       <Form.Item
656 |                         noStyle
657 |                         shouldUpdate={(prevValues, curValues) => {
658 |                           const prevFormat = prevValues?.publicKeys?.[name]?.format
659 |                           const curFormat = curValues?.publicKeys?.[name]?.format
660 |                           return prevFormat !== curFormat
661 |                         }}
662 |                       >
663 |                         {({ getFieldValue }) => {
664 |                           const format = getFieldValue(['publicKeys', name, 'format'])
665 |                           return (
666 |                             <Form.Item
667 |                               {...restField}
668 |                               name={[name, 'value']}
669 |                               label="公钥内容"
670 |                               rules={[{ required: true, message: '请输入公钥内容' }]}
671 |                             >
672 |                               <Input.TextArea
673 |                                 rows={6}
674 |                                 placeholder={
675 |                                   format === PublicKeyFormat.JWK
676 |                                     ? 'JWK格式公钥,例如:\n{\n  "kty": "RSA",\n  "kid": "key1",\n  "n": "...",\n  "e": "AQAB"\n}'
677 |                                     : 'PEM格式公钥,例如:\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----'
678 |                                 }
679 |                                 style={{ fontFamily: 'monospace', fontSize: '12px' }}
680 |                               />
681 |                             </Form.Item>
682 |                           )
683 |                         }}
684 |                       </Form.Item>
685 |                     </div>
686 |                   )
687 |                 }))}
688 |               />
689 |             )}
690 |             <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />} size="small">
691 |               添加公钥
692 |             </Button>
693 |           </div>
694 |         )}
695 |       </Form.List>
696 | 
697 |       <div className="-ml-3">
698 |         <Collapse
699 |           size="small"
700 |           ghost
701 |           expandIcon={({ isActive }) => (
702 |             <svg 
703 |               className={`w-4 h-4 transition-transform ${isActive ? 'rotate-90' : ''}`}
704 |               fill="currentColor" 
705 |               viewBox="0 0 20 20"
706 |             >
707 |               <path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
708 |             </svg>
709 |           )}
710 |           items={[
711 |             {
712 |               key: 'advanced',
713 |               label: (
714 |                 <div className="flex items-center text-gray-600">
715 |                   <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
716 |                     <path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947z" clipRule="evenodd" />
717 |                     <path fillRule="evenodd" d="M10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
718 |                   </svg>
719 |                   <span className="ml-2">高级配置</span>
720 |                   <span className="text-xs text-gray-400 ml-2">身份映射</span>
721 |                 </div>
722 |               ),
723 |               children: (
724 |                 <div className="space-y-4 pt-2 ml-3">
725 |                   <div className="grid grid-cols-3 gap-4">
726 |                     <Form.Item
727 |                       name="userIdField"
728 |                       label="开发者ID"
729 |                     >
730 |                       <Input placeholder="默认: userId"/>
731 |                     </Form.Item>
732 |                     <Form.Item
733 |                       name="userNameField"
734 |                       label="开发者名称"
735 |                     >
736 |                       <Input placeholder="默认: name"/>
737 |                     </Form.Item>
738 |                     <Form.Item
739 |                       name="emailField"
740 |                       label="邮箱"
741 |                     >
742 |                       <Input placeholder="默认: email"/>
743 |                     </Form.Item>
744 |                   </div>
745 | 
746 |                 <div className="bg-blue-50 p-3 rounded-lg">
747 |                   <div className="flex items-start space-x-2">
748 |                     <div className="text-blue-600 mt-0.5">
749 |                       <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
750 |                         <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
751 |                       </svg>
752 |                     </div>
753 |                     <div>
754 |                       <h4 className="text-blue-800 font-medium text-sm">配置说明</h4>
755 |                       <p className="text-blue-700 text-xs mt-1">
756 |                         身份映射用于从JWT载荷中提取用户信息。如果不填写,系统将使用默认字段名。
757 |                       </p>
758 |                     </div>
759 |                   </div>
760 |                 </div>
761 |               </div>
762 |             )
763 |           }
764 |         ]}
765 |       />
766 |       </div>
767 |     </div>
768 |   )
769 | 
770 |   // 按类型分组配置
771 |   const oidcConfigs = configs.filter(config => config.type === AuthenticationType.OIDC)
772 |   const oauth2Configs = configs.filter(config => config.type === AuthenticationType.OAUTH2)
773 | 
774 |   return (
775 |     <div className="space-y-6">
776 |       <div className="flex justify-between items-center">
777 |         <div>
778 |           <h3 className="text-lg font-medium">第三方认证</h3>
779 |           <p className="text-sm text-gray-500">管理外部身份认证配置</p>
780 |         </div>
781 |         <Button
782 |           type="primary"
783 |           icon={<PlusOutlined/>}
784 |           onClick={handleAdd}
785 |         >
786 |           添加配置
787 |         </Button>
788 |       </div>
789 | 
790 |       <Tabs
791 |         defaultActiveKey="oidc"
792 |         items={[
793 |           {
794 |             key: 'oidc',
795 |             label: 'OIDC配置',
796 |             children: (
797 |               <div className="bg-white rounded-lg">
798 |                 <div className="py-4 border-b border-gray-200">
799 |                   <h4 className="text-lg font-medium text-gray-900">OIDC配置</h4>
800 |                   <p className="text-sm text-gray-500 mt-1">支持OpenID Connect标准协议的身份提供商</p>
801 |                 </div>
802 |                 <Table
803 |                   columns={oidcColumns}
804 |                   dataSource={oidcConfigs}
805 |                   rowKey="provider"
806 |                   pagination={false}
807 |                   size="small"
808 |                   locale={{
809 |                     emptyText: '暂无OIDC配置'
810 |                   }}
811 |                 />
812 |               </div>
813 |             ),
814 |           },
815 |           {
816 |             key: 'oauth2',
817 |             label: 'OAuth2配置',
818 |             children: (
819 |               <div className="bg-white rounded-lg">
820 |                 <div className="py-4 border-b border-gray-200">
821 |                   <h4 className="text-lg font-medium text-gray-900">OAuth2配置</h4>
822 |                   <p className="text-sm text-gray-500 mt-1">支持OAuth 2.0标准协议的身份提供商</p>
823 |                 </div>
824 |                 <Table
825 |                   columns={oauth2Columns}
826 |                   dataSource={oauth2Configs}
827 |                   rowKey="provider"
828 |                   pagination={false}
829 |                   size="small"
830 |                   locale={{
831 |                     emptyText: '暂无OAuth2配置'
832 |                   }}
833 |                 />
834 |               </div>
835 |             ),
836 |           },
837 |         ]}
838 |       />
839 | 
840 |       {/* 添加/编辑配置模态框 */}
841 |       <Modal
842 |         title={editingConfig ? '编辑第三方认证配置' : '添加第三方认证配置'}
843 |         open={modalVisible}
844 |         onCancel={handleCancel}
845 |         width={800}
846 |         footer={null}
847 |       >
848 |         <Steps
849 |           current={currentStep}
850 |           className="mb-6"
851 |           items={[
852 |             {
853 |               title: '选择类型',
854 |               description: '选择认证协议类型'
855 |             },
856 |             {
857 |               title: '配置认证',
858 |               description: '填写认证参数'
859 |             }
860 |           ]}
861 |         />
862 | 
863 |         <Form
864 |           form={form}
865 |           layout="vertical"
866 |         >
867 |           {currentStep === 0 ? (
868 |             // 第一步:选择类型
869 |             <Card>
870 |               <Form.Item
871 |                 name="type"
872 |                 label="认证类型"
873 |                 rules={[{required: true, message: '请选择认证类型'}]}
874 |               >
875 |                 <Select placeholder="请选择认证方式" size="large">
876 |                   <Select.Option value={AuthenticationType.OIDC}>
877 |                     <div className="py-2">
878 |                       <div className="font-medium">OIDC(适用于支持OpenID Connect的身份提供商认证)</div>
879 |                     </div>
880 |                   </Select.Option>
881 |                   <Select.Option value={AuthenticationType.OAUTH2}>
882 |                     <div className="py-2">
883 |                       <div className="font-medium">OAuth2(适用于服务间集成)</div>
884 |                     </div>
885 |                   </Select.Option>
886 |                 </Select>
887 |               </Form.Item>
888 |               
889 |               <div className="flex justify-end">
890 |                 <Button type="primary" onClick={handleNext}>
891 |                   下一步
892 |                 </Button>
893 |               </div>
894 |             </Card>
895 |           ) : (
896 |             // 第二步:配置详情
897 |             <div>
898 |               <div className="grid grid-cols-2 gap-4">
899 |                 <Form.Item
900 |                   name="provider"
901 |                   label="提供商标识"
902 |                   rules={[
903 |                     {required: true, message: '请输入提供商标识'},
904 |                     {
905 |                       validator: (_, value) => {
906 |                         if (!value) return Promise.resolve()
907 |                         
908 |                         // 检查provider唯一性
909 |                         const isDuplicate = configs.some(config => 
910 |                           config.provider === value && 
911 |                           (!editingConfig || editingConfig.provider !== value)
912 |                         )
913 |                         
914 |                         if (isDuplicate) {
915 |                           return Promise.reject(new Error('该提供商标识已存在,请使用不同的标识'))
916 |                         }
917 |                         
918 |                         return Promise.resolve()
919 |                       }
920 |                     }
921 |                   ]}
922 |                 >
923 |                   <Input
924 |                     placeholder="如: google, company-sso"
925 |                     disabled={editingConfig !== null}
926 |                   />
927 |                 </Form.Item>
928 |                 <Form.Item
929 |                   name="name"
930 |                   label="显示名称"
931 |                   rules={[{required: true, message: '请输入显示名称'}]}
932 |                 >
933 |                   <Input placeholder="如: Google登录、公司SSO"/>
934 |                 </Form.Item>
935 |               </div>
936 | 
937 |               <Form.Item
938 |                 name="enabled"
939 |                 label="启用状态"
940 |                 valuePropName="checked"
941 |               >
942 |                 <Switch/>
943 |               </Form.Item>
944 | 
945 |               <Divider />
946 | 
947 |               {/* 根据类型显示不同的配置表单 */}
948 |               {selectedType === AuthenticationType.OIDC ? renderOidcForm() : renderOAuth2Form()}
949 | 
950 |               <div className="flex justify-between mt-6">
951 |                 <Button onClick={handlePrevious}>
952 |                   上一步
953 |                 </Button>
954 |                 <Space>
955 |                   <Button onClick={handleCancel}>
956 |                     取消
957 |                   </Button>
958 |                   <Button type="primary" loading={loading} onClick={handleSave}>
959 |                     {editingConfig ? '更新' : '添加'}
960 |                   </Button>
961 |                 </Space>
962 |               </div>
963 |             </div>
964 |           )}
965 |         </Form>
966 |       </Modal>
967 | 
968 |     </div>
969 |   )
970 | }
971 | 
```

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

```java
  1 | package com.alibaba.apiopenplatform.service.gateway;
  2 | 
  3 | import com.alibaba.apiopenplatform.core.exception.BusinessException;
  4 | import com.alibaba.apiopenplatform.core.exception.ErrorCode;
  5 | import com.alibaba.apiopenplatform.dto.params.gateway.QueryAdpAIGatewayParam;
  6 | import com.alibaba.apiopenplatform.dto.result.*;
  7 | import com.alibaba.apiopenplatform.dto.result.AdpGatewayInstanceResult;
  8 | import com.alibaba.apiopenplatform.entity.Consumer;
  9 | import com.alibaba.apiopenplatform.entity.ConsumerCredential;
 10 | import com.alibaba.apiopenplatform.entity.Gateway;
 11 | import com.alibaba.apiopenplatform.support.consumer.AdpAIAuthConfig;
 12 | import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
 13 | import com.alibaba.apiopenplatform.support.enums.GatewayType;
 14 | import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
 15 | import com.alibaba.apiopenplatform.service.gateway.client.AdpAIGatewayClient;
 16 | import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
 17 | import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
 18 | import com.alibaba.apiopenplatform.dto.result.MCPConfigResult;
 19 | import cn.hutool.json.JSONUtil;
 20 | import lombok.Data;
 21 | import lombok.extern.slf4j.Slf4j;
 22 | import org.springframework.http.HttpEntity;
 23 | import org.springframework.http.HttpMethod;
 24 | import org.springframework.http.ResponseEntity;
 25 | import org.springframework.stereotype.Service;
 26 | 
 27 | import java.time.LocalDateTime;
 28 | import java.time.format.DateTimeFormatter;
 29 | import java.util.ArrayList;
 30 | import java.util.Collections;
 31 | import java.util.List;
 32 | import java.util.stream.Collectors;
 33 | 
 34 | /**
 35 |  * ADP AI网关操作器
 36 |  */
 37 | @Service
 38 | @Slf4j
 39 | public class AdpAIGatewayOperator extends GatewayOperator {
 40 | 
 41 |     @Override
 42 |     public PageResult<APIResult> fetchHTTPAPIs(Gateway gateway, int page, int size) {
 43 |         return null;
 44 |     }
 45 | 
 46 |     @Override
 47 |     public PageResult<APIResult> fetchRESTAPIs(Gateway gateway, int page, int size) {
 48 |         return null;
 49 |     }
 50 | 
 51 |     @Override
 52 |     public PageResult<? extends GatewayMCPServerResult> fetchMcpServers(Gateway gateway, int page, int size) {
 53 |         AdpAIGatewayConfig config = gateway.getAdpAIGatewayConfig();
 54 |         if (config == null) {
 55 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway 配置缺失");
 56 |         }
 57 | 
 58 |         AdpAIGatewayClient client = new AdpAIGatewayClient(config);
 59 |         try {
 60 |             String url = client.getFullUrl("/mcpServer/listMcpServers");
 61 |             // 修复:添加必需的 gwInstanceId 参数
 62 |             String requestBody = String.format(
 63 |                 "{\"current\": %d, \"size\": %d, \"gwInstanceId\": \"%s\"}", 
 64 |                 page, 
 65 |                 size, 
 66 |                 gateway.getGatewayId()
 67 |             );
 68 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
 69 | 
 70 |             ResponseEntity<AdpMcpServerListResult> response = client.getRestTemplate().exchange(
 71 |                     url, HttpMethod.POST, requestEntity, AdpMcpServerListResult.class);
 72 | 
 73 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
 74 |                 AdpMcpServerListResult result = response.getBody();
 75 |                 if (result.getCode() != null && result.getCode() == 200 && result.getData() != null) {
 76 |                     List<GatewayMCPServerResult> items = new ArrayList<>();
 77 |                     if (result.getData().getRecords() != null) {
 78 |                         items.addAll(result.getData().getRecords());
 79 |                     }
 80 |                     int total = result.getData().getTotal() != null ? result.getData().getTotal() : 0;
 81 |                     return PageResult.of(items, page, size, total);
 82 |                 }
 83 |                 String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
 84 |                 throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
 85 |             }
 86 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /mcpServer/listMcpServers 失败");
 87 |         } catch (Exception e) {
 88 |             log.error("Error fetching ADP MCP servers", e);
 89 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
 90 |         } finally {
 91 |             client.close();
 92 |         }
 93 |     }
 94 | 
 95 |     @Override
 96 |     public String fetchAPIConfig(Gateway gateway, Object config) {
 97 |         return "";
 98 |     }
 99 | 
100 |     @Override
101 |     public String fetchMcpConfig(Gateway gateway, Object conf) {
102 |         AdpAIGatewayConfig config = gateway.getAdpAIGatewayConfig();
103 |         if (config == null) {
104 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway 配置缺失");
105 |         }
106 | 
107 |         // 从 conf 参数中获取 APIGRefConfig
108 |         APIGRefConfig apigRefConfig = (APIGRefConfig) conf;
109 |         if (apigRefConfig == null || apigRefConfig.getMcpServerName() == null) {
110 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "MCP Server 名称缺失");
111 |         }
112 | 
113 |         AdpAIGatewayClient client = new AdpAIGatewayClient(config);
114 |         try {
115 |             String url = client.getFullUrl("/mcpServer/getMcpServer");
116 |             
117 |             // 构建请求体,包含 gwInstanceId 和 mcpServerName
118 |             String requestBody = String.format(
119 |                 "{\"gwInstanceId\": \"%s\", \"mcpServerName\": \"%s\"}", 
120 |                 gateway.getGatewayId(), 
121 |                 apigRefConfig.getMcpServerName()
122 |             );
123 |             
124 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
125 | 
126 |             ResponseEntity<AdpMcpServerDetailResult> response = client.getRestTemplate().exchange(
127 |                     url, HttpMethod.POST, requestEntity, AdpMcpServerDetailResult.class);
128 | 
129 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
130 |                 AdpMcpServerDetailResult result = response.getBody();
131 |                 if (result.getCode() != null && result.getCode() == 200 && result.getData() != null) {
132 |                     return convertToMCPConfig(result.getData(), config);
133 |                 }
134 |                 String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
135 |                 throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
136 |             }
137 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /mcpServer/getMcpServer 失败");
138 |         } catch (Exception e) {
139 |             log.error("Error fetching ADP MCP config for server: {}", apigRefConfig.getMcpServerName(), e);
140 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
141 |         } finally {
142 |             client.close();
143 |         }
144 |     }
145 | 
146 |     /**
147 |      * 将 ADP MCP Server 详情转换为 MCPConfigResult 格式
148 |      */
149 |     private String convertToMCPConfig(AdpMcpServerDetailResult.AdpMcpServerDetail data, AdpAIGatewayConfig config) {
150 |         MCPConfigResult mcpConfig = new MCPConfigResult();
151 |         mcpConfig.setMcpServerName(data.getName());
152 | 
153 |         // 设置 MCP Server 配置
154 |         MCPConfigResult.MCPServerConfig serverConfig = new MCPConfigResult.MCPServerConfig();
155 |         serverConfig.setPath("/" + data.getName());
156 |         
157 |         // 获取网关实例访问信息并设置域名信息
158 |         List<MCPConfigResult.Domain> domains = getGatewayAccessDomains(data.getGwInstanceId(), config);
159 |         if (domains != null && !domains.isEmpty()) {
160 |             serverConfig.setDomains(domains);
161 |         } else {
162 |             // 如果无法获取网关访问信息,则使用原有的services信息作为备选
163 |             if (data.getServices() != null && !data.getServices().isEmpty()) {
164 |                 List<MCPConfigResult.Domain> fallbackDomains = data.getServices().stream()
165 |                         .map(domain -> MCPConfigResult.Domain.builder()
166 |                                 .domain(domain.getName() + ":" + domain.getPort())
167 |                                 .protocol("http")
168 |                                 .build())
169 |                         .collect(Collectors.toList());
170 |                 serverConfig.setDomains(fallbackDomains);
171 |             }
172 |         }
173 |         
174 |         mcpConfig.setMcpServerConfig(serverConfig);
175 | 
176 |         // 设置工具配置
177 |         mcpConfig.setTools(data.getRawConfigurations());
178 | 
179 |         // 设置元数据
180 |         MCPConfigResult.McpMetadata meta = new MCPConfigResult.McpMetadata();
181 |         meta.setSource(GatewayType.ADP_AI_GATEWAY.name());
182 |         meta.setCreateFromType(data.getType());
183 |         mcpConfig.setMeta(meta);
184 | 
185 |         return JSONUtil.toJsonStr(mcpConfig);
186 |     }
187 | 
188 |     /**
189 |      * 获取网关实例的访问信息并构建域名列表
190 |      */
191 |     private List<MCPConfigResult.Domain> getGatewayAccessDomains(String gwInstanceId, AdpAIGatewayConfig config) {
192 |         AdpAIGatewayClient client = new AdpAIGatewayClient(config);
193 |         try {
194 |             String url = client.getFullUrl("/gatewayInstance/getInstanceInfo");
195 |             String requestBody = String.format("{\"gwInstanceId\": \"%s\"}", gwInstanceId);
196 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
197 | 
198 |             // 注意:getInstanceInfo 返回的 data 是单个实例对象(无 records 字段),直接从 data.accessMode 读取
199 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
200 |                     url, HttpMethod.POST, requestEntity, String.class);
201 | 
202 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
203 |                 cn.hutool.json.JSONObject root = JSONUtil.parseObj(response.getBody());
204 |                 Integer code = root.getInt("code");
205 |                 if (code != null && code == 200 && root.containsKey("data")) {
206 |                     cn.hutool.json.JSONObject dataObj = root.getJSONObject("data");
207 |                     if (dataObj != null && dataObj.containsKey("accessMode")) {
208 |                         cn.hutool.json.JSONArray arr = dataObj.getJSONArray("accessMode");
209 |                         List<AdpGatewayInstanceResult.AccessMode> accessModes = JSONUtil.toList(arr, AdpGatewayInstanceResult.AccessMode.class);
210 |                         return buildDomainsFromAccessModes(accessModes);
211 |                     }
212 |                     log.warn("Gateway instance has no accessMode, instanceId={}", gwInstanceId);
213 |                     return null;
214 |                 }
215 |                 String message = root.getStr("message", root.getStr("msg"));
216 |                 log.warn("Failed to get gateway instance access info: {}", message);
217 |                 return null;
218 |             }
219 |             log.warn("Failed to call gateway instance access API");
220 |             return null;
221 |         } catch (Exception e) {
222 |             log.error("Error fetching gateway access info for instance: {}", gwInstanceId, e);
223 |             return null;
224 |         } finally {
225 |             client.close();
226 |         }
227 |     }
228 | 
229 |     /**
230 |      * 根据网关实例访问信息构建域名列表
231 |      */
232 |     private List<MCPConfigResult.Domain> buildDomainsFromAccessInfo(AdpGatewayInstanceResult.AdpGatewayInstanceData data) {
233 |         // 兼容 listInstances 调用:取第一条记录的 accessMode
234 |         if (data != null && data.getRecords() != null && !data.getRecords().isEmpty()) {
235 |             AdpGatewayInstanceResult.AdpGatewayInstance instance = data.getRecords().get(0);
236 |             if (instance.getAccessMode() != null) {
237 |                 return buildDomainsFromAccessModes(instance.getAccessMode());
238 |             }
239 |         }
240 |         return new ArrayList<>();
241 |     }
242 | 
243 |     private List<MCPConfigResult.Domain> buildDomainsFromAccessModes(List<AdpGatewayInstanceResult.AccessMode> accessModes) {
244 |         List<MCPConfigResult.Domain> domains = new ArrayList<>();
245 |         if (accessModes == null || accessModes.isEmpty()) { return domains; }
246 |         AdpGatewayInstanceResult.AccessMode accessMode = accessModes.get(0);
247 | 
248 |         // 1) LoadBalancer: externalIps:80
249 |         if ("LoadBalancer".equalsIgnoreCase(accessMode.getAccessModeType())) {
250 |             if (accessMode.getExternalIps() != null && !accessMode.getExternalIps().isEmpty()) {
251 |                 for (String externalIp : accessMode.getExternalIps()) {
252 |                     if (externalIp == null || externalIp.isEmpty()) { continue; }
253 |                     MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
254 |                             .domain(externalIp + ":80")
255 |                             .protocol("http")
256 |                             .build();
257 |                     domains.add(domain);
258 |                 }
259 |             }
260 |         }
261 | 
262 |         // 2) NodePort: ips + ports → ip:nodePort
263 |         if (domains.isEmpty() && "NodePort".equalsIgnoreCase(accessMode.getAccessModeType())) {
264 |             List<String> ips = accessMode.getIps();
265 |             List<String> ports = accessMode.getPorts();
266 |             if (ips != null && !ips.isEmpty() && ports != null && !ports.isEmpty()) {
267 |                 for (String ip : ips) {
268 |                     if (ip == null || ip.isEmpty()) { continue; }
269 |                     for (String portMapping : ports) {
270 |                         if (portMapping == null || portMapping.isEmpty()) { continue; }
271 |                         String[] parts = portMapping.split(":");
272 |                         if (parts.length >= 2) {
273 |                             String nodePort = parts[1].split("/")[0];
274 |                             MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
275 |                                     .domain(ip + ":" + nodePort)
276 |                                     .protocol("http")
277 |                                     .build();
278 |                             domains.add(domain);
279 |                         }
280 |                     }
281 |                 }
282 |             }
283 |         }
284 | 
285 |         // 3) fallback: only externalIps → :80
286 |         if (domains.isEmpty() && accessMode.getExternalIps() != null && !accessMode.getExternalIps().isEmpty()) {
287 |             for (String externalIp : accessMode.getExternalIps()) {
288 |                 if (externalIp == null || externalIp.isEmpty()) { continue; }
289 |                 MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
290 |                         .domain(externalIp + ":80")
291 |                         .protocol("http")
292 |                         .build();
293 |                 domains.add(domain);
294 |             }
295 |         }
296 | 
297 |         return domains;
298 |     }
299 | 
300 |     @Override
301 |     public String createConsumer(Consumer consumer, ConsumerCredential credential, GatewayConfig config) {
302 |         AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
303 |         if (adpConfig == null) {
304 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
305 |         }
306 | 
307 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
308 |         try {
309 |             // 构建请求参数
310 |             cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
311 |             requestData.set("authType", 5);
312 | 
313 |             // 从凭证中获取key
314 |             if (credential.getApiKeyConfig() != null && 
315 |                 credential.getApiKeyConfig().getCredentials() != null &&
316 |                 !credential.getApiKeyConfig().getCredentials().isEmpty()) {
317 |                 String key = credential.getApiKeyConfig().getCredentials().get(0).getApiKey();
318 |                 requestData.set("key", key);
319 |             }
320 | 
321 |             requestData.set("appName", consumer.getName());
322 |             
323 |             // 从 GatewayConfig 中获取 Gateway 实体,与 fetchMcpConfig 方法保持一致
324 |             Gateway gateway = config.getGateway();
325 |             if (gateway == null || gateway.getGatewayId() == null) {
326 |                 throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
327 |             }
328 |             requestData.set("gwInstanceId", gateway.getGatewayId());
329 | 
330 |             String url = client.getFullUrl("/application/createApp");
331 |             String requestBody = requestData.toString();
332 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
333 | 
334 |             log.info("Creating consumer in ADP gateway: url={}, requestBody={}", url, requestBody);
335 | 
336 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
337 |                     url, HttpMethod.POST, requestEntity, String.class);
338 | 
339 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
340 |                 log.info("ADP gateway response: {}", response.getBody());
341 |                 // 对于ADP AI网关,返回的data就是appName,可以直接用于后续的MCP授权
342 |                 return extractConsumerIdFromResponse(response.getBody(), consumer.getName());
343 |             }
344 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to create consumer in ADP gateway");
345 |         } catch (BusinessException e) {
346 |             log.error("Business error creating consumer in ADP gateway", e);
347 |             throw e;
348 |         } catch (Exception e) {
349 |             log.error("Error creating consumer in ADP gateway", e);
350 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, 
351 |                 "Error creating consumer in ADP gateway: " + e.getMessage());
352 |         } finally {
353 |             client.close();
354 |         }
355 |     }
356 | 
357 |     /**
358 |      * 从响应中提取消费者ID
359 |      * 对于ADP AI网关,/application/createApp接口返回的data就是appName(应用名称)
360 |      * 我们直接返回appName,这样在授权时可以直接使用
361 |      */
362 |     private String extractConsumerIdFromResponse(String responseBody, String defaultConsumerId) {
363 |         try {
364 |             cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(responseBody);
365 |             // ADP AI网关的/application/createApp接口,成功时返回格式: {"code": 200, "data": "appName"}
366 |             if (responseJson.getInt("code", 0) == 200 && responseJson.containsKey("data")) {
367 |                 Object dataObj = responseJson.get("data");
368 |                 if (dataObj != null) {
369 |                     // data字段就是appName,直接返回
370 |                     if (dataObj instanceof String) {
371 |                         return (String) dataObj;
372 |                     }
373 |                     // 如果data是对象类型,则按原逻辑处理(兼容性考虑)
374 |                     if (dataObj instanceof cn.hutool.json.JSONObject) {
375 |                         cn.hutool.json.JSONObject data = (cn.hutool.json.JSONObject) dataObj;
376 |                         if (data.containsKey("applicationId")) {
377 |                             return data.getStr("applicationId");
378 |                         }
379 |                         // 如果没有applicationId字段,将整个data对象转为字符串
380 |                         return data.toString();
381 |                     }
382 |                     // 其他类型直接转为字符串
383 |                     return dataObj.toString();
384 |                 }
385 |             }
386 |             // 如果无法解析,使用应用名称作为fallback
387 |             return defaultConsumerId; // 这里传入的是consumer.getName()
388 |         } catch (Exception e) {
389 |             log.warn("Failed to parse response body, using consumer name as fallback", e);
390 |             return defaultConsumerId;
391 |         }
392 |     }
393 | 
394 |     @Override
395 |     public void updateConsumer(String consumerId, ConsumerCredential credential, GatewayConfig config) {
396 |         AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
397 |         if (adpConfig == null) {
398 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
399 |         }
400 | 
401 |         Gateway gateway = config.getGateway();
402 |         if (gateway == null || gateway.getGatewayId() == null) {
403 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
404 |         }
405 | 
406 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
407 |         try {
408 | 
409 |             // 从凭据中提取API Key
410 |             String apiKey = null;
411 |             if (credential != null
412 |                     && credential.getApiKeyConfig() != null
413 |                     && credential.getApiKeyConfig().getCredentials() != null
414 |                     && !credential.getApiKeyConfig().getCredentials().isEmpty()) {
415 |                 apiKey = credential.getApiKeyConfig().getCredentials().get(0).getApiKey();
416 |             }
417 | 
418 |             String url = client.getFullUrl("/application/modifyApp");
419 | 
420 |             // 构建请求体
421 |             cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
422 |             requestData.set("appId", consumerId);
423 |             requestData.set("appName", consumerId);
424 |             requestData.set("authType", 5);                 // 固定参数
425 |             requestData.set("authTypeName", "API_KEY");
426 |             requestData.set("description", consumerId);
427 |             requestData.set("enable", true);                // 固定参数
428 |             if (apiKey != null) {
429 |                 requestData.set("key", apiKey);
430 |             }
431 |             requestData.set("groups", Collections.singletonList("true")); // 固定参数
432 |             requestData.set("gwInstanceId", gateway.getGatewayId());
433 | 
434 |             String requestBody = requestData.toString();
435 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
436 | 
437 |             log.info("Updating consumer in ADP gateway: url={}, requestBody={}", url, requestBody);
438 | 
439 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
440 |                     url, HttpMethod.POST, requestEntity, String.class);
441 | 
442 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
443 |                 cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
444 |                 Integer code = responseJson.getInt("code", 0);
445 |                 if (code != null && code == 200) {
446 |                     log.info("Successfully updated consumer {} in ADP gateway instance {}", consumerId, gateway.getGatewayId());
447 |                     return;
448 |                 }
449 |                 String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
450 |                 throw new BusinessException(ErrorCode.GATEWAY_ERROR, "更新ADP网关消费者失败: " + message);
451 |             }
452 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /application/modifyApp 失败");
453 |         } catch (BusinessException e) {
454 |             throw e;
455 |         } catch (Exception e) {
456 |             log.error("Error updating consumer {} in ADP gateway instance {}", consumerId, 
457 |                     gateway != null ? gateway.getGatewayId() : "unknown", e);
458 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, "更新ADP网关消费者异常: " + e.getMessage());
459 |         } finally {
460 |             client.close();
461 |         }
462 |     }
463 | 
464 |     @Override
465 |     public void deleteConsumer(String consumerId, GatewayConfig config) {
466 |         AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
467 |         if (adpConfig == null) {
468 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
469 |         }
470 | 
471 |         Gateway gateway = config.getGateway();
472 |         if (gateway == null || gateway.getGatewayId() == null) {
473 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
474 |         }
475 | 
476 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
477 |         try {
478 | 
479 |             String url = client.getFullUrl("/application/deleteApp");
480 |             String requestBody = String.format(
481 |                 "{\"appId\": \"%s\", \"gwInstanceId\": \"%s\"}",
482 |                 consumerId, gateway.getGatewayId()
483 |             );
484 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
485 | 
486 |             log.info("Deleting consumer in ADP gateway: url={}, requestBody={}", url, requestBody);
487 | 
488 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
489 |                     url, HttpMethod.POST, requestEntity, String.class);
490 | 
491 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
492 |                 cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
493 |                 Integer code = responseJson.getInt("code", 0);
494 |                 if (code != null && code == 200) {
495 |                     log.info("Successfully deleted consumer {} from ADP gateway instance {}", 
496 |                              consumerId, gateway.getGatewayId());
497 |                     return;
498 |                 }
499 |                 String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
500 |                 throw new BusinessException(ErrorCode.GATEWAY_ERROR, "删除ADP网关消费者失败: " + message);
501 |             }
502 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /application/deleteApp 失败");
503 |         } catch (BusinessException e) {
504 |             throw e;
505 |         } catch (Exception e) {
506 |             log.error("Error deleting consumer {} from ADP gateway instance {}", 
507 |                       consumerId, gateway != null ? gateway.getGatewayId() : "unknown", e);
508 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, "删除ADP网关消费者异常: " + e.getMessage());
509 |         } finally {
510 |             client.close();
511 |         }
512 |     }
513 | 
514 |     @Override
515 |     public boolean isConsumerExists(String consumerId, GatewayConfig config) {
516 |         AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
517 |         if (adpConfig == null) {
518 |             log.warn("ADP AI Gateway配置缺失,无法检查消费者存在性");
519 |             return false;
520 |         }
521 | 
522 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
523 |         try {
524 |             // 从 GatewayConfig 中获取 Gateway 实体
525 |             Gateway gateway = config.getGateway();
526 |             if (gateway == null || gateway.getGatewayId() == null) {
527 |                 log.warn("网关实例ID缺失,无法检查消费者存在性");
528 |                 return false;
529 |             }
530 | 
531 |             String url = client.getFullUrl("/application/getApp");
532 |             String requestBody = String.format(
533 |                 "{\"%s\": \"%s\", \"%s\": \"%s\"}", 
534 |                 "gwInstanceId", gateway.getGatewayId(),
535 |                 "appId", consumerId
536 |             );
537 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
538 | 
539 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
540 |                     url, HttpMethod.POST, requestEntity, String.class);
541 | 
542 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
543 |                 cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
544 |                 Integer code = responseJson.getInt("code", 0);
545 |                 // 如果返回200且有data,说明消费者存在
546 |                 return code == 200 && responseJson.containsKey("data") && responseJson.get("data") != null;
547 |             }
548 |             return false;
549 |         } catch (Exception e) {
550 |             log.warn("检查ADP网关消费者存在性失败: consumerId={}", consumerId, e);
551 |             return false;
552 |         } finally {
553 |             client.close();
554 |         }
555 |     }
556 | 
557 |     @Override
558 |     public ConsumerAuthConfig authorizeConsumer(Gateway gateway, String consumerId, Object refConfig) {
559 |         AdpAIGatewayConfig adpConfig = gateway.getAdpAIGatewayConfig();
560 |         if (adpConfig == null) {
561 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
562 |         }
563 | 
564 |         // 解析MCP Server配置
565 |         APIGRefConfig apigRefConfig = (APIGRefConfig) refConfig;
566 |         if (apigRefConfig == null || apigRefConfig.getMcpServerName() == null) {
567 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "MCP Server名称缺失");
568 |         }
569 | 
570 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
571 |         try {
572 |             // 构建授权请求参数
573 |             // 由于createConsumer返回的就是appName,所以consumerId就是应用名称
574 |             cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
575 |             requestData.set("mcpServerName", apigRefConfig.getMcpServerName());
576 |             requestData.set("consumers", Collections.singletonList(consumerId)); // consumerId就是appName
577 |             requestData.set("gwInstanceId", gateway.getGatewayId());
578 | 
579 |             String url = client.getFullUrl("/mcpServer/addMcpServerConsumers");
580 |             String requestBody = requestData.toString();
581 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
582 | 
583 |             log.info("Authorizing consumer to MCP server: url={}, requestBody={}", url, requestBody);
584 | 
585 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
586 |                     url, HttpMethod.POST, requestEntity, String.class);
587 | 
588 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
589 |                 cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
590 |                 Integer code = responseJson.getInt("code", 0);
591 |                 
592 |                 if (code == 200) {
593 |                     log.info("Successfully authorized consumer {} to MCP server {}", 
594 |                         consumerId, apigRefConfig.getMcpServerName());
595 |                     
596 |                     // 构建授权配置返回结果
597 |                     AdpAIAuthConfig authConfig = AdpAIAuthConfig.builder()
598 |                             .mcpServerName(apigRefConfig.getMcpServerName())
599 |                             .consumerId(consumerId)
600 |                             .gwInstanceId(gateway.getGatewayId())
601 |                             .build();
602 |                     
603 |                     return ConsumerAuthConfig.builder()
604 |                             .adpAIAuthConfig(authConfig)
605 |                             .build();
606 |                 } else {
607 |                     String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
608 |                     throw new BusinessException(ErrorCode.GATEWAY_ERROR, 
609 |                         "Failed to authorize consumer to MCP server: " + message);
610 |                 }
611 |             }
612 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to authorize consumer to MCP server");
613 |         } catch (BusinessException e) {
614 |             log.error("Business error authorizing consumer to MCP server", e);
615 |             throw e;
616 |         } catch (Exception e) {
617 |             log.error("Error authorizing consumer {} to MCP server {}", 
618 |                 consumerId, apigRefConfig.getMcpServerName(), e);
619 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, 
620 |                 "Error authorizing consumer to MCP server: " + e.getMessage());
621 |         } finally {
622 |             client.close();
623 |         }
624 |     }
625 | 
626 |     @Override
627 |     public void revokeConsumerAuthorization(Gateway gateway, String consumerId, ConsumerAuthConfig authConfig) {
628 |         AdpAIAuthConfig adpAIAuthConfig = authConfig.getAdpAIAuthConfig();
629 |         if (adpAIAuthConfig == null) {
630 |             log.warn("ADP AI 授权配置为空,无法撤销授权");
631 |             return;
632 |         }
633 | 
634 |         AdpAIGatewayConfig adpConfig = gateway.getAdpAIGatewayConfig();
635 |         if (adpConfig == null) {
636 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
637 |         }
638 | 
639 |         AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
640 |         try {
641 |             // 构建撤销授权请求参数
642 |             // 由于createConsumer返回的就是appName,所以consumerId就是应用名称
643 |             cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
644 |             requestData.set("mcpServerName", adpAIAuthConfig.getMcpServerName());
645 |             requestData.set("consumers", Collections.singletonList(consumerId)); // consumerId就是appName
646 |             requestData.set("gwInstanceId", gateway.getGatewayId());
647 | 
648 |             String url = client.getFullUrl("/mcpServer/deleteMcpServerConsumers");
649 |             String requestBody = requestData.toString();
650 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
651 | 
652 |             log.info("Revoking consumer authorization from MCP server: url={}, requestBody={}", url, requestBody);
653 | 
654 |             ResponseEntity<String> response = client.getRestTemplate().exchange(
655 |                     url, HttpMethod.POST, requestEntity, String.class);
656 | 
657 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
658 |                 cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
659 |                 Integer code = responseJson.getInt("code", 0);
660 |                 
661 |                 if (code == 200) {
662 |                     log.info("Successfully revoked consumer {} authorization from MCP server {}", 
663 |                         consumerId, adpAIAuthConfig.getMcpServerName());
664 |                 } else {
665 |                     String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
666 |                     log.warn("Failed to revoke consumer authorization from MCP server: {}", message);
667 |                     // 撤销授权失败不抛异常,只记录日志
668 |                 }
669 |             } else {
670 |                 log.warn("Failed to revoke consumer authorization from MCP server, HTTP status: {}", 
671 |                     response.getStatusCode());
672 |             }
673 |         } catch (Exception e) {
674 |             log.error("Error revoking consumer {} authorization from MCP server {}", 
675 |                 consumerId, adpAIAuthConfig.getMcpServerName(), e);
676 |             // 撤销授权失败不抛异常,只记录日志
677 |         } finally {
678 |             client.close();
679 |         }
680 |     }
681 | 
682 |     @Override
683 |     public APIResult fetchAPI(Gateway gateway, String apiId) {
684 |         return null;
685 |     }
686 | 
687 |     @Override
688 |     public GatewayType getGatewayType() {
689 |         return GatewayType.ADP_AI_GATEWAY;
690 |     }
691 | 
692 |     @Override
693 |     public String getDashboard(Gateway gateway,String type) {
694 |         return null;
695 |     }
696 | 
697 |     @Override
698 |     public PageResult<GatewayResult> fetchGateways(Object param, int page, int size) {
699 |         if (!(param instanceof QueryAdpAIGatewayParam)) {
700 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "param");
701 |         }
702 |         return fetchGateways((QueryAdpAIGatewayParam) param, page, size);
703 |     }
704 | 
705 |     public PageResult<GatewayResult> fetchGateways(QueryAdpAIGatewayParam param, int page, int size) {
706 |         AdpAIGatewayConfig config = new AdpAIGatewayConfig();
707 |         config.setBaseUrl(param.getBaseUrl());
708 |         config.setPort(param.getPort());
709 |         
710 |         // 根据认证类型设置不同的认证信息
711 |         if ("Seed".equals(param.getAuthType())) {
712 |             if (param.getAuthSeed() == null || param.getAuthSeed().trim().isEmpty()) {
713 |                 throw new BusinessException(ErrorCode.INVALID_PARAMETER, "Seed认证方式下authSeed不能为空");
714 |             }
715 |             config.setAuthSeed(param.getAuthSeed());
716 |         } else if ("Header".equals(param.getAuthType())) {
717 |             if (param.getAuthHeaders() == null || param.getAuthHeaders().isEmpty()) {
718 |                 throw new BusinessException(ErrorCode.INVALID_PARAMETER, "Header认证方式下authHeaders不能为空");
719 |             }
720 |             // 将authHeaders转换为配置
721 |             List<AdpAIGatewayConfig.AuthHeader> configHeaders = new ArrayList<>();
722 |             for (QueryAdpAIGatewayParam.AuthHeader paramHeader : param.getAuthHeaders()) {
723 |                 AdpAIGatewayConfig.AuthHeader configHeader = new AdpAIGatewayConfig.AuthHeader();
724 |                 configHeader.setKey(paramHeader.getKey());
725 |                 configHeader.setValue(paramHeader.getValue());
726 |                 configHeaders.add(configHeader);
727 |             }
728 |             config.setAuthHeaders(configHeaders);
729 |         } else {
730 |             throw new BusinessException(ErrorCode.INVALID_PARAMETER, "不支持的认证类型: " + param.getAuthType());
731 |         }
732 | 
733 |         AdpAIGatewayClient client = new AdpAIGatewayClient(config);
734 |         try {
735 |             String url = client.getFullUrl("/gatewayInstance/listInstances");
736 |             String requestBody = String.format("{\"current\": %d, \"size\": %d}", page, size);
737 |             HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);
738 | 
739 |             ResponseEntity<AdpGatewayInstanceResult> response = client.getRestTemplate().exchange(
740 |                     url, HttpMethod.POST, requestEntity, AdpGatewayInstanceResult.class);
741 | 
742 |             if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
743 |                 AdpGatewayInstanceResult result = response.getBody();
744 |                 if (result.getCode() == 200 && result.getData() != null) {
745 |                     return convertToGatewayResult(result.getData(), page, size);
746 |                 }
747 |                 String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
748 |                 throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
749 |             }
750 |             throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to call ADP gateway API");
751 |         } catch (Exception e) {
752 |             log.error("Error fetching ADP gateways", e);
753 |             throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
754 |         } finally {
755 |             client.close();
756 |         }
757 |     }
758 | 
759 |     private PageResult<GatewayResult> convertToGatewayResult(AdpGatewayInstanceResult.AdpGatewayInstanceData data, int page, int size) {
760 |         List<GatewayResult> gateways = new ArrayList<>();
761 |         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
762 |         if (data.getRecords() != null) {
763 |             for (AdpGatewayInstanceResult.AdpGatewayInstance instance : data.getRecords()) {
764 |                 LocalDateTime createTime = null;
765 |                 try {
766 |                     if (instance.getCreateTime() != null) {
767 |                         createTime = LocalDateTime.parse(instance.getCreateTime(), formatter);
768 |                     }
769 |                 } catch (Exception e) {
770 |                     log.warn("Failed to parse create time: {}", instance.getCreateTime(), e);
771 |                 }
772 |                 GatewayResult gateway = GatewayResult.builder()
773 |                         .gatewayId(instance.getGwInstanceId())
774 |                         .gatewayName(instance.getName())
775 |                         .gatewayType(GatewayType.ADP_AI_GATEWAY)
776 |                         .createAt(createTime)
777 |                         .build();
778 |                 gateways.add(gateway);
779 |             }
780 |         }
781 |         return PageResult.of(gateways, page, size, data.getTotal() != null ? data.getTotal() : 0);
782 |     }
783 | 
784 |     @Data
785 |     public static class AdpMcpServerDetailResult {
786 |         private Integer code;
787 |         private String msg;
788 |         private String message;
789 |         private AdpMcpServerDetail data;
790 | 
791 |         @Data
792 |         public static class AdpMcpServerDetail {
793 |             private String gwInstanceId;
794 |             private String name;
795 |             private String description;
796 |             private List<String> domains;
797 |             private List<Service> services;
798 |             private ConsumerAuthInfo consumerAuthInfo;
799 |             private String rawConfigurations;
800 |             private String type;
801 |             private String dsn;
802 |             private String dbType;
803 |             private String upstreamPathPrefix;
804 | 
805 |             @Data
806 |             public static class Service {
807 |                 private String name;
808 |                 private Integer port;
809 |                 private String version;
810 |                 private Integer weight;
811 |             }
812 | 
813 |             @Data
814 |             public static class ConsumerAuthInfo {
815 |                 private String type;
816 |                 private Boolean enable;
817 |                 private List<String> allowedConsumers;
818 |             }
819 |         }
820 |     }
821 | }
822 | 
```
Page 9/9FirstPrevNextLast