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

# Directory Structure

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

# Files

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

```typescript
import {useState} from 'react'
import {Button, Form, Input, Select, Switch, Table, Modal, Space, message, Divider, Steps, Card, Tabs, Collapse, Radio} from 'antd'
import {PlusOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined, MinusCircleOutlined, KeyOutlined, CheckCircleFilled, MinusCircleFilled} from '@ant-design/icons'
import {ThirdPartyAuthConfig, AuthenticationType, GrantType, AuthCodeConfig, OAuth2Config, OidcConfig, PublicKeyFormat} from '@/types'

interface ThirdPartyAuthManagerProps {
  configs: ThirdPartyAuthConfig[]
  onSave: (configs: ThirdPartyAuthConfig[]) => Promise<void>
}

export function ThirdPartyAuthManager({configs, onSave}: ThirdPartyAuthManagerProps) {
  const [form] = Form.useForm()
  const [modalVisible, setModalVisible] = useState(false)
  const [loading, setLoading] = useState(false)
  const [editingConfig, setEditingConfig] = useState<ThirdPartyAuthConfig | null>(null)
  const [currentStep, setCurrentStep] = useState(0)
  const [selectedType, setSelectedType] = useState<AuthenticationType | null>(null)


  // 添加新配置
  const handleAdd = () => {
    setEditingConfig(null)
    setSelectedType(null)
    setCurrentStep(0)
    setModalVisible(true)
    form.resetFields()
  }

  // 编辑配置
  const handleEdit = (config: ThirdPartyAuthConfig) => {
    setEditingConfig(config)
    setSelectedType(config.type)
    setCurrentStep(1) // 直接进入配置步骤
    setModalVisible(true)
    
    // 根据类型设置表单值
    if (config.type === AuthenticationType.OIDC) {
      // OIDC配置:直接使用OidcConfig的字段
      const oidcConfig = config as (OidcConfig & { type: AuthenticationType.OIDC })
      
      // 检查是否是手动配置模式(有具体的端点地址)
      const hasManualEndpoints = !!(oidcConfig.authCodeConfig?.authorizationEndpoint && 
                                   oidcConfig.authCodeConfig?.tokenEndpoint && 
                                   oidcConfig.authCodeConfig?.userInfoEndpoint)
      
      form.setFieldsValue({
        provider: oidcConfig.provider,
        name: oidcConfig.name,
        enabled: oidcConfig.enabled,
        type: oidcConfig.type,
        configMode: hasManualEndpoints ? 'manual' : 'auto',
        ...oidcConfig.authCodeConfig,
        // 身份映射字段可能在根级别或authCodeConfig中
        userIdField: oidcConfig.identityMapping?.userIdField || oidcConfig.authCodeConfig?.identityMapping?.userIdField,
        userNameField: oidcConfig.identityMapping?.userNameField || oidcConfig.authCodeConfig?.identityMapping?.userNameField,
        emailField: oidcConfig.identityMapping?.emailField || oidcConfig.authCodeConfig?.identityMapping?.emailField
      })
    } else if (config.type === AuthenticationType.OAUTH2) {
      // OAuth2配置:直接使用OAuth2Config的字段
      const oauth2Config = config as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
      form.setFieldsValue({
        provider: oauth2Config.provider,
        name: oauth2Config.name,
        enabled: oauth2Config.enabled,
        type: oauth2Config.type,
        grantType: oauth2Config.grantType || GrantType.JWT_BEARER, // 确保有默认值
        userIdField: oauth2Config.identityMapping?.userIdField,
        userNameField: oauth2Config.identityMapping?.userNameField,
        emailField: oauth2Config.identityMapping?.emailField,
        publicKeys: oauth2Config.jwtBearerConfig?.publicKeys || []
      })
    }
  }

  // 删除配置
  const handleDelete = async (provider: string, name: string) => {
    Modal.confirm({
      title: '确认删除',
      icon: <ExclamationCircleOutlined/>,
      content: `确定要删除第三方认证配置 "${name}" 吗?此操作不可恢复。`,
      okText: '确认删除',
      okType: 'danger',
      cancelText: '取消',
      async onOk() {
        try {
          const updatedConfigs = configs.filter(config => config.provider !== provider)
          await onSave(updatedConfigs)
          message.success('第三方认证配置删除成功')
        } catch (error) {
          message.error('删除第三方认证配置失败')
        }
      },
    })
  }


  // 下一步
  const handleNext = async () => {
    if (currentStep === 0) {
      try {
        const values = await form.validateFields(['type'])
        setSelectedType(values.type)
        setCurrentStep(1)
        
        // 为不同类型设置默认值
        if (values.type === AuthenticationType.OAUTH2) {
          form.setFieldsValue({
            grantType: GrantType.JWT_BEARER,
            enabled: true
          })
        } else if (values.type === AuthenticationType.OIDC) {
          form.setFieldsValue({
            enabled: true,
            configMode: 'auto'
          })
        }
      } catch (error) {
        // 验证失败
      }
    }
  }

  // 上一步
  const handlePrevious = () => {
    setCurrentStep(0)
  }

  // 保存配置
  const handleSave = async () => {
    try {
      setLoading(true)
      
      const values = await form.validateFields()

      let newConfig: ThirdPartyAuthConfig

      if (selectedType === AuthenticationType.OIDC) {
        // OIDC配置:根据配置模式创建不同的authCodeConfig
        let authCodeConfig: AuthCodeConfig
        
        if (values.configMode === 'auto') {
          // 自动发现模式:只保存issuer,端点置空(后端会通过issuer自动发现)
          authCodeConfig = {
            clientId: values.clientId,
            clientSecret: values.clientSecret,
            scopes: values.scopes,
            issuer: values.issuer,
            authorizationEndpoint: '',  // 自动发现模式下端点为空
            tokenEndpoint: '',
            userInfoEndpoint: '',
            jwkSetUri: '',
            // 可选的身份映射配置
            identityMapping: (values.userIdField || values.userNameField || values.emailField) ? {
              userIdField: values.userIdField || null,
              userNameField: values.userNameField || null,
              emailField: values.emailField || null
            } : undefined
          }
        } else {
          // 手动配置模式:保存具体的端点地址
          authCodeConfig = {
            clientId: values.clientId,
            clientSecret: values.clientSecret,
            scopes: values.scopes,
            issuer: values.issuer || '',  // 手动配置模式下issuer可选
            authorizationEndpoint: values.authorizationEndpoint,
            tokenEndpoint: values.tokenEndpoint,
            userInfoEndpoint: values.userInfoEndpoint,
            jwkSetUri: values.jwkSetUri || '',
            // 可选的身份映射配置
            identityMapping: (values.userIdField || values.userNameField || values.emailField) ? {
              userIdField: values.userIdField || null,
              userNameField: values.userNameField || null,
              emailField: values.emailField || null
            } : undefined
          }
        }

        newConfig = {
          provider: values.provider,
          name: values.name,
          logoUrl: null,
          enabled: values.enabled ?? true,
          grantType: 'AUTHORIZATION_CODE' as const,
          authCodeConfig,
          // 根级别的身份映射(为兼容后端格式)
          identityMapping: authCodeConfig.identityMapping,
          type: AuthenticationType.OIDC
        } as (OidcConfig & { type: AuthenticationType.OIDC })
      } else {
        // OAuth2配置:直接创建OAuth2Config格式
        const grantType = values.grantType || GrantType.JWT_BEARER // 确保有默认值
        newConfig = {
          provider: values.provider,
          name: values.name,
          enabled: values.enabled ?? true,
          grantType: grantType,
          jwtBearerConfig: grantType === GrantType.JWT_BEARER ? {
            publicKeys: values.publicKeys || []
          } : undefined,
          identityMapping: {
            userIdField: values.userIdField || null,
            userNameField: values.userNameField || null,
            emailField: values.emailField || null
          },
          type: AuthenticationType.OAUTH2
        } as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
      }

      let updatedConfigs
      if (editingConfig) {
        updatedConfigs = configs.map(config => 
          config.provider === editingConfig.provider ? newConfig : config
        )
      } else {
        updatedConfigs = [...configs, newConfig]
      }

      await onSave(updatedConfigs)
      
      message.success(editingConfig ? '第三方认证配置更新成功' : '第三方认证配置添加成功')
      setModalVisible(false)
    } catch (error) {
      message.error('保存第三方认证配置失败')
    } finally {
      setLoading(false)
    }
  }

  // 取消
  const handleCancel = () => {
    setModalVisible(false)
    setEditingConfig(null)
    setSelectedType(null)
    setCurrentStep(0)
    form.resetFields()
  }

  // OIDC表格列定义(不包含类型列)
  const oidcColumns = [
    {
      title: '提供商',
      dataIndex: 'provider',
      key: 'provider',
      width: 120,
      render: (provider: string) => (
        <span className="font-medium text-gray-700">{provider}</span>
      )
    },
    {
      title: '名称',
      dataIndex: 'name',
      key: 'name',
      width: 150,
    },
    {
      title: '授权模式',
      key: 'grantType',
      width: 120,
      render: () => <span className="text-gray-600">授权码模式</span>
    },
    {
      title: '状态',
      dataIndex: 'enabled',
      key: 'enabled',
      width: 80,
      render: (enabled: boolean) => (
        <div className="flex items-center">
          {enabled ? (
            <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '12px'}} />
          ) : (
            <MinusCircleFilled className="text-gray-500 mr-2" style={{fontSize: '12px'}} />
          )}
          <span className="text-gray-700">
            {enabled ? '已启用' : '已停用'}
          </span>
        </div>
      )
    },
    {
      title: '操作',
      key: 'action',
      width: 150,
      render: (_: any, record: ThirdPartyAuthConfig) => (
        <Space>
          <Button
            type="link"
            icon={<EditOutlined/>}
            onClick={() => handleEdit(record)}
          >
            编辑
          </Button>
          <Button
            type="link"
            danger
            icon={<DeleteOutlined/>}
            onClick={() => handleDelete(record.provider, record.name)}
          >
            删除
          </Button>
        </Space>
      )
    }
  ]

  // OAuth2表格列定义(不包含类型列)
  const oauth2Columns = [
    {
      title: '提供商',
      dataIndex: 'provider',
      key: 'provider',
      width: 120,
      render: (provider: string) => (
        <span className="font-medium text-gray-700">{provider}</span>
      )
    },
    {
      title: '名称',
      dataIndex: 'name',
      key: 'name',
      width: 150,
    },
    {
      title: '授权模式',
      key: 'grantType',
      width: 120,
      render: (record: ThirdPartyAuthConfig) => {
        if (record.type === AuthenticationType.OAUTH2) {
          const oauth2Config = record as (OAuth2Config & { type: AuthenticationType.OAUTH2 })
          return (
            <span className="text-gray-600">
              {oauth2Config.grantType === GrantType.JWT_BEARER ? 'JWT断言' : '授权码模式'}
            </span>
          )
        }
        return <span className="text-gray-600">授权码模式</span>
      }
    },
    {
      title: '状态',
      dataIndex: 'enabled',
      key: 'enabled',
      width: 80,
      render: (enabled: boolean) => (
        <div className="flex items-center">
          {enabled ? (
            <CheckCircleFilled className="text-green-500 mr-2" style={{fontSize: '12px'}} />
          ) : (
            <MinusCircleFilled className="text-gray-500 mr-2" style={{fontSize: '12px'}} />
          )}
          <span className="text-gray-700">
            {enabled ? '已启用' : '已停用'}
          </span>
        </div>
      )
    },
    {
      title: '操作',
      key: 'action',
      width: 150,
      render: (_: any, record: ThirdPartyAuthConfig) => (
        <Space>
          <Button
            type="link"
            icon={<EditOutlined/>}
            onClick={() => handleEdit(record)}
          >
            编辑
          </Button>
          <Button
            type="link"
            danger
            icon={<DeleteOutlined/>}
            onClick={() => handleDelete(record.provider, record.name)}
          >
            删除
          </Button>
        </Space>
      )
    }
  ]

  // 渲染OIDC配置表单
  const renderOidcForm = () => (
    <div className="space-y-6">
      <Form.Item
        name="grantType"
        label="授权模式"
        initialValue="AUTHORIZATION_CODE"
      >
        <Select disabled>
          <Select.Option value="AUTHORIZATION_CODE">授权码模式</Select.Option>
        </Select>
      </Form.Item>

      <div className="grid grid-cols-2 gap-4">
        <Form.Item
          name="clientId"
          label="Client ID"
          rules={[{required: true, message: '请输入 Client ID'}]}
        >
          <Input placeholder="Client ID"/>
        </Form.Item>
        <Form.Item
          name="clientSecret"
          label="Client Secret"
          rules={[{required: true, message: '请输入 Client Secret'}]}
        >
          <Input.Password placeholder="Client Secret"/>
        </Form.Item>
      </div>

      <div className="grid grid-cols-2 gap-4">
        <Form.Item
          name="scopes"
          label="授权范围"
          rules={[{required: true, message: '请输入授权范围'}]}
        >
          <Input placeholder="如: openid profile email"/>
        </Form.Item>
        <div></div>
      </div>

      <Divider />

      {/* 配置模式选择 */}
      <Form.Item
        name="configMode"
        label="端点配置"
        initialValue="auto"
      >
        <Radio.Group>
          <Radio value="auto">自动发现</Radio>
          <Radio value="manual">手动配置</Radio>
        </Radio.Group>
      </Form.Item>

      {/* 根据配置模式显示不同字段 */}
      <Form.Item
        noStyle
        shouldUpdate={(prevValues, curValues) => prevValues.configMode !== curValues.configMode}
      >
        {({ getFieldValue }) => {
          const configMode = getFieldValue('configMode') || 'auto'
          
          if (configMode === 'auto') {
            // 自动发现模式:只需要Issuer地址
            return (
              <Form.Item
                name="issuer"
                label="Issuer"
                rules={[
                  { required: true, message: '请输入Issuer地址' },
                  { type: 'url', message: '请输入有效的URL' }
                ]}
              >
                <Input placeholder="如: https://accounts.google.com" />
              </Form.Item>
            )
          } else {
            // 手动配置模式:需要各个端点
            return (
              <div className="space-y-4">
                <div className="grid grid-cols-2 gap-4">
                  <Form.Item
                    name="authorizationEndpoint"
                    label="授权端点"
                    rules={[{ required: true, message: '请输入授权端点' }]}
                  >
                    <Input placeholder="Authorization 授权端点"/>
                  </Form.Item>
                  <Form.Item
                    name="tokenEndpoint"
                    label="令牌端点"
                    rules={[{ required: true, message: '请输入令牌端点' }]}
                  >
                    <Input placeholder="Token 令牌端点"/>
                  </Form.Item>
                </div>
                <div className="grid grid-cols-2 gap-4">
                  <Form.Item
                    name="userInfoEndpoint"
                    label="用户信息端点"
                    rules={[{ required: true, message: '请输入用户信息端点' }]}
                  >
                    <Input placeholder="UserInfo 端点"/>
                  </Form.Item>
                  <Form.Item
                    name="jwkSetUri"
                    label="公钥端点"
                  >
                    <Input placeholder="可选"/>
                  </Form.Item>
                </div>
              </div>
            )
          }
        }}
      </Form.Item>

      <div className="-ml-3">
        <Collapse
          size="small"
          ghost
          expandIcon={({ isActive }) => (
            <svg 
              className={`w-4 h-4 transition-transform ${isActive ? 'rotate-90' : ''}`}
              fill="currentColor" 
              viewBox="0 0 20 20"
            >
              <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" />
            </svg>
          )}
          items={[
            {
              key: 'advanced',
              label: (
                <div className="flex items-center text-gray-600">
                  <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                    <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" />
                    <path fillRule="evenodd" d="M10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
                  </svg>
                  <span className="ml-2">高级配置</span>
                  <span className="text-xs text-gray-400 ml-2">身份映射</span>
                </div>
              ),
              children: (
                <div className="space-y-4 pt-2 ml-3">
                  <div className="grid grid-cols-3 gap-4">
                    <Form.Item
                      name="userIdField"
                      label="开发者ID"
                    >
                      <Input placeholder="默认: sub"/>
                    </Form.Item>
                    <Form.Item
                      name="userNameField"
                      label="开发者名称"
                    >
                      <Input placeholder="默认: name"/>
                    </Form.Item>
                    <Form.Item
                      name="emailField"
                      label="邮箱"
                    >
                      <Input placeholder="默认: email"/>
                    </Form.Item>
                  </div>

                <div className="bg-blue-50 p-3 rounded-lg">
                  <div className="flex items-start space-x-2">
                    <div className="text-blue-600 mt-0.5">
                      <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                        <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" />
                      </svg>
                    </div>
                    <div>
                      <h4 className="text-blue-800 font-medium text-sm">配置说明</h4>
                      <p className="text-blue-700 text-xs mt-1">
                        身份映射用于从OIDC令牌中提取用户信息。如果不填写,系统将使用OIDC标准字段。
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            )
          }
        ]}
      />
      </div>
    </div>
  )

  // 渲染OAuth2配置表单
  const renderOAuth2Form = () => (
    <div className="space-y-6">
      <Form.Item
        name="grantType"
        label="授权模式"
        initialValue={GrantType.JWT_BEARER}
        rules={[{required: true}]}
      >
        <Select disabled>
          <Select.Option value={GrantType.JWT_BEARER}>JWT断言</Select.Option>
        </Select>
      </Form.Item>

      <Form.List name="publicKeys">
        {(fields, { add, remove }) => (
          <div className="space-y-4">
            {fields.length > 0 && (
              <Collapse
                size="small"
                items={fields.map(({ key, name, ...restField }) => ({
                  key: key,
                  label: (
                    <div className="flex items-center">
                      <KeyOutlined className="mr-2" />
                      <span>公钥 {name + 1}</span>
                    </div>
                  ),
                  extra: (
                    <Button
                      type="link"
                      danger
                      size="small"
                      icon={<MinusCircleOutlined />}
                      onClick={(e) => {
                        e.stopPropagation()
                        remove(name)
                      }}
                    >
                      删除
                    </Button>
                  ),
                  children: (
                    <div className="space-y-4 px-4">
                      <div className="grid grid-cols-3 gap-4">
                        <Form.Item
                          {...restField}
                          name={[name, 'kid']}
                          label="Key ID"
                          rules={[{ required: true, message: '请输入Key ID' }]}
                        >
                          <Input placeholder="公钥标识符" size="small" />
                        </Form.Item>
                        <Form.Item
                          {...restField}
                          name={[name, 'algorithm']}
                          label="签名算法"
                          rules={[{ required: true, message: '请选择签名算法' }]}
                        >
                          <Select placeholder="选择签名算法" size="small">
                            <Select.Option value="RS256">RS256</Select.Option>
                            <Select.Option value="RS384">RS384</Select.Option>
                            <Select.Option value="RS512">RS512</Select.Option>
                            <Select.Option value="ES256">ES256</Select.Option>
                            <Select.Option value="ES384">ES384</Select.Option>
                            <Select.Option value="ES512">ES512</Select.Option>
                          </Select>
                        </Form.Item>
                        <Form.Item
                          {...restField}
                          name={[name, 'format']}
                          label="公钥格式"
                          rules={[{ required: true, message: '请选择公钥格式' }]}
                        >
                          <Select placeholder="选择公钥格式" size="small">
                            <Select.Option value={PublicKeyFormat.PEM}>PEM</Select.Option>
                            <Select.Option value={PublicKeyFormat.JWK}>JWK</Select.Option>
                          </Select>
                        </Form.Item>
                      </div>

                      <Form.Item
                        noStyle
                        shouldUpdate={(prevValues, curValues) => {
                          const prevFormat = prevValues?.publicKeys?.[name]?.format
                          const curFormat = curValues?.publicKeys?.[name]?.format
                          return prevFormat !== curFormat
                        }}
                      >
                        {({ getFieldValue }) => {
                          const format = getFieldValue(['publicKeys', name, 'format'])
                          return (
                            <Form.Item
                              {...restField}
                              name={[name, 'value']}
                              label="公钥内容"
                              rules={[{ required: true, message: '请输入公钥内容' }]}
                            >
                              <Input.TextArea
                                rows={6}
                                placeholder={
                                  format === PublicKeyFormat.JWK
                                    ? 'JWK格式公钥,例如:\n{\n  "kty": "RSA",\n  "kid": "key1",\n  "n": "...",\n  "e": "AQAB"\n}'
                                    : 'PEM格式公钥,例如:\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----'
                                }
                                style={{ fontFamily: 'monospace', fontSize: '12px' }}
                              />
                            </Form.Item>
                          )
                        }}
                      </Form.Item>
                    </div>
                  )
                }))}
              />
            )}
            <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />} size="small">
              添加公钥
            </Button>
          </div>
        )}
      </Form.List>

      <div className="-ml-3">
        <Collapse
          size="small"
          ghost
          expandIcon={({ isActive }) => (
            <svg 
              className={`w-4 h-4 transition-transform ${isActive ? 'rotate-90' : ''}`}
              fill="currentColor" 
              viewBox="0 0 20 20"
            >
              <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" />
            </svg>
          )}
          items={[
            {
              key: 'advanced',
              label: (
                <div className="flex items-center text-gray-600">
                  <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                    <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" />
                    <path fillRule="evenodd" d="M10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
                  </svg>
                  <span className="ml-2">高级配置</span>
                  <span className="text-xs text-gray-400 ml-2">身份映射</span>
                </div>
              ),
              children: (
                <div className="space-y-4 pt-2 ml-3">
                  <div className="grid grid-cols-3 gap-4">
                    <Form.Item
                      name="userIdField"
                      label="开发者ID"
                    >
                      <Input placeholder="默认: userId"/>
                    </Form.Item>
                    <Form.Item
                      name="userNameField"
                      label="开发者名称"
                    >
                      <Input placeholder="默认: name"/>
                    </Form.Item>
                    <Form.Item
                      name="emailField"
                      label="邮箱"
                    >
                      <Input placeholder="默认: email"/>
                    </Form.Item>
                  </div>

                <div className="bg-blue-50 p-3 rounded-lg">
                  <div className="flex items-start space-x-2">
                    <div className="text-blue-600 mt-0.5">
                      <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
                        <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" />
                      </svg>
                    </div>
                    <div>
                      <h4 className="text-blue-800 font-medium text-sm">配置说明</h4>
                      <p className="text-blue-700 text-xs mt-1">
                        身份映射用于从JWT载荷中提取用户信息。如果不填写,系统将使用默认字段名。
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            )
          }
        ]}
      />
      </div>
    </div>
  )

  // 按类型分组配置
  const oidcConfigs = configs.filter(config => config.type === AuthenticationType.OIDC)
  const oauth2Configs = configs.filter(config => config.type === AuthenticationType.OAUTH2)

  return (
    <div className="space-y-6">
      <div className="flex justify-between items-center">
        <div>
          <h3 className="text-lg font-medium">第三方认证</h3>
          <p className="text-sm text-gray-500">管理外部身份认证配置</p>
        </div>
        <Button
          type="primary"
          icon={<PlusOutlined/>}
          onClick={handleAdd}
        >
          添加配置
        </Button>
      </div>

      <Tabs
        defaultActiveKey="oidc"
        items={[
          {
            key: 'oidc',
            label: 'OIDC配置',
            children: (
              <div className="bg-white rounded-lg">
                <div className="py-4 border-b border-gray-200">
                  <h4 className="text-lg font-medium text-gray-900">OIDC配置</h4>
                  <p className="text-sm text-gray-500 mt-1">支持OpenID Connect标准协议的身份提供商</p>
                </div>
                <Table
                  columns={oidcColumns}
                  dataSource={oidcConfigs}
                  rowKey="provider"
                  pagination={false}
                  size="small"
                  locale={{
                    emptyText: '暂无OIDC配置'
                  }}
                />
              </div>
            ),
          },
          {
            key: 'oauth2',
            label: 'OAuth2配置',
            children: (
              <div className="bg-white rounded-lg">
                <div className="py-4 border-b border-gray-200">
                  <h4 className="text-lg font-medium text-gray-900">OAuth2配置</h4>
                  <p className="text-sm text-gray-500 mt-1">支持OAuth 2.0标准协议的身份提供商</p>
                </div>
                <Table
                  columns={oauth2Columns}
                  dataSource={oauth2Configs}
                  rowKey="provider"
                  pagination={false}
                  size="small"
                  locale={{
                    emptyText: '暂无OAuth2配置'
                  }}
                />
              </div>
            ),
          },
        ]}
      />

      {/* 添加/编辑配置模态框 */}
      <Modal
        title={editingConfig ? '编辑第三方认证配置' : '添加第三方认证配置'}
        open={modalVisible}
        onCancel={handleCancel}
        width={800}
        footer={null}
      >
        <Steps
          current={currentStep}
          className="mb-6"
          items={[
            {
              title: '选择类型',
              description: '选择认证协议类型'
            },
            {
              title: '配置认证',
              description: '填写认证参数'
            }
          ]}
        />

        <Form
          form={form}
          layout="vertical"
        >
          {currentStep === 0 ? (
            // 第一步:选择类型
            <Card>
              <Form.Item
                name="type"
                label="认证类型"
                rules={[{required: true, message: '请选择认证类型'}]}
              >
                <Select placeholder="请选择认证方式" size="large">
                  <Select.Option value={AuthenticationType.OIDC}>
                    <div className="py-2">
                      <div className="font-medium">OIDC(适用于支持OpenID Connect的身份提供商认证)</div>
                    </div>
                  </Select.Option>
                  <Select.Option value={AuthenticationType.OAUTH2}>
                    <div className="py-2">
                      <div className="font-medium">OAuth2(适用于服务间集成)</div>
                    </div>
                  </Select.Option>
                </Select>
              </Form.Item>
              
              <div className="flex justify-end">
                <Button type="primary" onClick={handleNext}>
                  下一步
                </Button>
              </div>
            </Card>
          ) : (
            // 第二步:配置详情
            <div>
              <div className="grid grid-cols-2 gap-4">
                <Form.Item
                  name="provider"
                  label="提供商标识"
                  rules={[
                    {required: true, message: '请输入提供商标识'},
                    {
                      validator: (_, value) => {
                        if (!value) return Promise.resolve()
                        
                        // 检查provider唯一性
                        const isDuplicate = configs.some(config => 
                          config.provider === value && 
                          (!editingConfig || editingConfig.provider !== value)
                        )
                        
                        if (isDuplicate) {
                          return Promise.reject(new Error('该提供商标识已存在,请使用不同的标识'))
                        }
                        
                        return Promise.resolve()
                      }
                    }
                  ]}
                >
                  <Input
                    placeholder="如: google, company-sso"
                    disabled={editingConfig !== null}
                  />
                </Form.Item>
                <Form.Item
                  name="name"
                  label="显示名称"
                  rules={[{required: true, message: '请输入显示名称'}]}
                >
                  <Input placeholder="如: Google登录、公司SSO"/>
                </Form.Item>
              </div>

              <Form.Item
                name="enabled"
                label="启用状态"
                valuePropName="checked"
              >
                <Switch/>
              </Form.Item>

              <Divider />

              {/* 根据类型显示不同的配置表单 */}
              {selectedType === AuthenticationType.OIDC ? renderOidcForm() : renderOAuth2Form()}

              <div className="flex justify-between mt-6">
                <Button onClick={handlePrevious}>
                  上一步
                </Button>
                <Space>
                  <Button onClick={handleCancel}>
                    取消
                  </Button>
                  <Button type="primary" loading={loading} onClick={handleSave}>
                    {editingConfig ? '更新' : '添加'}
                  </Button>
                </Space>
              </div>
            </div>
          )}
        </Form>
      </Modal>

    </div>
  )
}

```

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

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

import com.alibaba.apiopenplatform.core.exception.BusinessException;
import com.alibaba.apiopenplatform.core.exception.ErrorCode;
import com.alibaba.apiopenplatform.dto.params.gateway.QueryAdpAIGatewayParam;
import com.alibaba.apiopenplatform.dto.result.*;
import com.alibaba.apiopenplatform.dto.result.AdpGatewayInstanceResult;
import com.alibaba.apiopenplatform.entity.Consumer;
import com.alibaba.apiopenplatform.entity.ConsumerCredential;
import com.alibaba.apiopenplatform.entity.Gateway;
import com.alibaba.apiopenplatform.support.consumer.AdpAIAuthConfig;
import com.alibaba.apiopenplatform.support.consumer.ConsumerAuthConfig;
import com.alibaba.apiopenplatform.support.enums.GatewayType;
import com.alibaba.apiopenplatform.support.gateway.AdpAIGatewayConfig;
import com.alibaba.apiopenplatform.service.gateway.client.AdpAIGatewayClient;
import com.alibaba.apiopenplatform.support.gateway.GatewayConfig;
import com.alibaba.apiopenplatform.support.product.APIGRefConfig;
import com.alibaba.apiopenplatform.dto.result.MCPConfigResult;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * ADP AI网关操作器
 */
@Service
@Slf4j
public class AdpAIGatewayOperator extends GatewayOperator {

    @Override
    public PageResult<APIResult> fetchHTTPAPIs(Gateway gateway, int page, int size) {
        return null;
    }

    @Override
    public PageResult<APIResult> fetchRESTAPIs(Gateway gateway, int page, int size) {
        return null;
    }

    @Override
    public PageResult<? extends GatewayMCPServerResult> fetchMcpServers(Gateway gateway, int page, int size) {
        AdpAIGatewayConfig config = gateway.getAdpAIGatewayConfig();
        if (config == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway 配置缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(config);
        try {
            String url = client.getFullUrl("/mcpServer/listMcpServers");
            // 修复:添加必需的 gwInstanceId 参数
            String requestBody = String.format(
                "{\"current\": %d, \"size\": %d, \"gwInstanceId\": \"%s\"}", 
                page, 
                size, 
                gateway.getGatewayId()
            );
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            ResponseEntity<AdpMcpServerListResult> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, AdpMcpServerListResult.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                AdpMcpServerListResult result = response.getBody();
                if (result.getCode() != null && result.getCode() == 200 && result.getData() != null) {
                    List<GatewayMCPServerResult> items = new ArrayList<>();
                    if (result.getData().getRecords() != null) {
                        items.addAll(result.getData().getRecords());
                    }
                    int total = result.getData().getTotal() != null ? result.getData().getTotal() : 0;
                    return PageResult.of(items, page, size, total);
                }
                String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
                throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /mcpServer/listMcpServers 失败");
        } catch (Exception e) {
            log.error("Error fetching ADP MCP servers", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
        } finally {
            client.close();
        }
    }

    @Override
    public String fetchAPIConfig(Gateway gateway, Object config) {
        return "";
    }

    @Override
    public String fetchMcpConfig(Gateway gateway, Object conf) {
        AdpAIGatewayConfig config = gateway.getAdpAIGatewayConfig();
        if (config == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway 配置缺失");
        }

        // 从 conf 参数中获取 APIGRefConfig
        APIGRefConfig apigRefConfig = (APIGRefConfig) conf;
        if (apigRefConfig == null || apigRefConfig.getMcpServerName() == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "MCP Server 名称缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(config);
        try {
            String url = client.getFullUrl("/mcpServer/getMcpServer");
            
            // 构建请求体,包含 gwInstanceId 和 mcpServerName
            String requestBody = String.format(
                "{\"gwInstanceId\": \"%s\", \"mcpServerName\": \"%s\"}", 
                gateway.getGatewayId(), 
                apigRefConfig.getMcpServerName()
            );
            
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            ResponseEntity<AdpMcpServerDetailResult> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, AdpMcpServerDetailResult.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                AdpMcpServerDetailResult result = response.getBody();
                if (result.getCode() != null && result.getCode() == 200 && result.getData() != null) {
                    return convertToMCPConfig(result.getData(), config);
                }
                String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
                throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /mcpServer/getMcpServer 失败");
        } catch (Exception e) {
            log.error("Error fetching ADP MCP config for server: {}", apigRefConfig.getMcpServerName(), e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
        } finally {
            client.close();
        }
    }

    /**
     * 将 ADP MCP Server 详情转换为 MCPConfigResult 格式
     */
    private String convertToMCPConfig(AdpMcpServerDetailResult.AdpMcpServerDetail data, AdpAIGatewayConfig config) {
        MCPConfigResult mcpConfig = new MCPConfigResult();
        mcpConfig.setMcpServerName(data.getName());

        // 设置 MCP Server 配置
        MCPConfigResult.MCPServerConfig serverConfig = new MCPConfigResult.MCPServerConfig();
        serverConfig.setPath("/" + data.getName());
        
        // 获取网关实例访问信息并设置域名信息
        List<MCPConfigResult.Domain> domains = getGatewayAccessDomains(data.getGwInstanceId(), config);
        if (domains != null && !domains.isEmpty()) {
            serverConfig.setDomains(domains);
        } else {
            // 如果无法获取网关访问信息,则使用原有的services信息作为备选
            if (data.getServices() != null && !data.getServices().isEmpty()) {
                List<MCPConfigResult.Domain> fallbackDomains = data.getServices().stream()
                        .map(domain -> MCPConfigResult.Domain.builder()
                                .domain(domain.getName() + ":" + domain.getPort())
                                .protocol("http")
                                .build())
                        .collect(Collectors.toList());
                serverConfig.setDomains(fallbackDomains);
            }
        }
        
        mcpConfig.setMcpServerConfig(serverConfig);

        // 设置工具配置
        mcpConfig.setTools(data.getRawConfigurations());

        // 设置元数据
        MCPConfigResult.McpMetadata meta = new MCPConfigResult.McpMetadata();
        meta.setSource(GatewayType.ADP_AI_GATEWAY.name());
        meta.setCreateFromType(data.getType());
        mcpConfig.setMeta(meta);

        return JSONUtil.toJsonStr(mcpConfig);
    }

    /**
     * 获取网关实例的访问信息并构建域名列表
     */
    private List<MCPConfigResult.Domain> getGatewayAccessDomains(String gwInstanceId, AdpAIGatewayConfig config) {
        AdpAIGatewayClient client = new AdpAIGatewayClient(config);
        try {
            String url = client.getFullUrl("/gatewayInstance/getInstanceInfo");
            String requestBody = String.format("{\"gwInstanceId\": \"%s\"}", gwInstanceId);
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            // 注意:getInstanceInfo 返回的 data 是单个实例对象(无 records 字段),直接从 data.accessMode 读取
            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject root = JSONUtil.parseObj(response.getBody());
                Integer code = root.getInt("code");
                if (code != null && code == 200 && root.containsKey("data")) {
                    cn.hutool.json.JSONObject dataObj = root.getJSONObject("data");
                    if (dataObj != null && dataObj.containsKey("accessMode")) {
                        cn.hutool.json.JSONArray arr = dataObj.getJSONArray("accessMode");
                        List<AdpGatewayInstanceResult.AccessMode> accessModes = JSONUtil.toList(arr, AdpGatewayInstanceResult.AccessMode.class);
                        return buildDomainsFromAccessModes(accessModes);
                    }
                    log.warn("Gateway instance has no accessMode, instanceId={}", gwInstanceId);
                    return null;
                }
                String message = root.getStr("message", root.getStr("msg"));
                log.warn("Failed to get gateway instance access info: {}", message);
                return null;
            }
            log.warn("Failed to call gateway instance access API");
            return null;
        } catch (Exception e) {
            log.error("Error fetching gateway access info for instance: {}", gwInstanceId, e);
            return null;
        } finally {
            client.close();
        }
    }

    /**
     * 根据网关实例访问信息构建域名列表
     */
    private List<MCPConfigResult.Domain> buildDomainsFromAccessInfo(AdpGatewayInstanceResult.AdpGatewayInstanceData data) {
        // 兼容 listInstances 调用:取第一条记录的 accessMode
        if (data != null && data.getRecords() != null && !data.getRecords().isEmpty()) {
            AdpGatewayInstanceResult.AdpGatewayInstance instance = data.getRecords().get(0);
            if (instance.getAccessMode() != null) {
                return buildDomainsFromAccessModes(instance.getAccessMode());
            }
        }
        return new ArrayList<>();
    }

    private List<MCPConfigResult.Domain> buildDomainsFromAccessModes(List<AdpGatewayInstanceResult.AccessMode> accessModes) {
        List<MCPConfigResult.Domain> domains = new ArrayList<>();
        if (accessModes == null || accessModes.isEmpty()) { return domains; }
        AdpGatewayInstanceResult.AccessMode accessMode = accessModes.get(0);

        // 1) LoadBalancer: externalIps:80
        if ("LoadBalancer".equalsIgnoreCase(accessMode.getAccessModeType())) {
            if (accessMode.getExternalIps() != null && !accessMode.getExternalIps().isEmpty()) {
                for (String externalIp : accessMode.getExternalIps()) {
                    if (externalIp == null || externalIp.isEmpty()) { continue; }
                    MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
                            .domain(externalIp + ":80")
                            .protocol("http")
                            .build();
                    domains.add(domain);
                }
            }
        }

        // 2) NodePort: ips + ports → ip:nodePort
        if (domains.isEmpty() && "NodePort".equalsIgnoreCase(accessMode.getAccessModeType())) {
            List<String> ips = accessMode.getIps();
            List<String> ports = accessMode.getPorts();
            if (ips != null && !ips.isEmpty() && ports != null && !ports.isEmpty()) {
                for (String ip : ips) {
                    if (ip == null || ip.isEmpty()) { continue; }
                    for (String portMapping : ports) {
                        if (portMapping == null || portMapping.isEmpty()) { continue; }
                        String[] parts = portMapping.split(":");
                        if (parts.length >= 2) {
                            String nodePort = parts[1].split("/")[0];
                            MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
                                    .domain(ip + ":" + nodePort)
                                    .protocol("http")
                                    .build();
                            domains.add(domain);
                        }
                    }
                }
            }
        }

        // 3) fallback: only externalIps → :80
        if (domains.isEmpty() && accessMode.getExternalIps() != null && !accessMode.getExternalIps().isEmpty()) {
            for (String externalIp : accessMode.getExternalIps()) {
                if (externalIp == null || externalIp.isEmpty()) { continue; }
                MCPConfigResult.Domain domain = MCPConfigResult.Domain.builder()
                        .domain(externalIp + ":80")
                        .protocol("http")
                        .build();
                domains.add(domain);
            }
        }

        return domains;
    }

    @Override
    public String createConsumer(Consumer consumer, ConsumerCredential credential, GatewayConfig config) {
        AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {
            // 构建请求参数
            cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
            requestData.set("authType", 5);

            // 从凭证中获取key
            if (credential.getApiKeyConfig() != null && 
                credential.getApiKeyConfig().getCredentials() != null &&
                !credential.getApiKeyConfig().getCredentials().isEmpty()) {
                String key = credential.getApiKeyConfig().getCredentials().get(0).getApiKey();
                requestData.set("key", key);
            }

            requestData.set("appName", consumer.getName());
            
            // 从 GatewayConfig 中获取 Gateway 实体,与 fetchMcpConfig 方法保持一致
            Gateway gateway = config.getGateway();
            if (gateway == null || gateway.getGatewayId() == null) {
                throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
            }
            requestData.set("gwInstanceId", gateway.getGatewayId());

            String url = client.getFullUrl("/application/createApp");
            String requestBody = requestData.toString();
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            log.info("Creating consumer in ADP gateway: url={}, requestBody={}", url, requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                log.info("ADP gateway response: {}", response.getBody());
                // 对于ADP AI网关,返回的data就是appName,可以直接用于后续的MCP授权
                return extractConsumerIdFromResponse(response.getBody(), consumer.getName());
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to create consumer in ADP gateway");
        } catch (BusinessException e) {
            log.error("Business error creating consumer in ADP gateway", e);
            throw e;
        } catch (Exception e) {
            log.error("Error creating consumer in ADP gateway", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, 
                "Error creating consumer in ADP gateway: " + e.getMessage());
        } finally {
            client.close();
        }
    }

    /**
     * 从响应中提取消费者ID
     * 对于ADP AI网关,/application/createApp接口返回的data就是appName(应用名称)
     * 我们直接返回appName,这样在授权时可以直接使用
     */
    private String extractConsumerIdFromResponse(String responseBody, String defaultConsumerId) {
        try {
            cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(responseBody);
            // ADP AI网关的/application/createApp接口,成功时返回格式: {"code": 200, "data": "appName"}
            if (responseJson.getInt("code", 0) == 200 && responseJson.containsKey("data")) {
                Object dataObj = responseJson.get("data");
                if (dataObj != null) {
                    // data字段就是appName,直接返回
                    if (dataObj instanceof String) {
                        return (String) dataObj;
                    }
                    // 如果data是对象类型,则按原逻辑处理(兼容性考虑)
                    if (dataObj instanceof cn.hutool.json.JSONObject) {
                        cn.hutool.json.JSONObject data = (cn.hutool.json.JSONObject) dataObj;
                        if (data.containsKey("applicationId")) {
                            return data.getStr("applicationId");
                        }
                        // 如果没有applicationId字段,将整个data对象转为字符串
                        return data.toString();
                    }
                    // 其他类型直接转为字符串
                    return dataObj.toString();
                }
            }
            // 如果无法解析,使用应用名称作为fallback
            return defaultConsumerId; // 这里传入的是consumer.getName()
        } catch (Exception e) {
            log.warn("Failed to parse response body, using consumer name as fallback", e);
            return defaultConsumerId;
        }
    }

    @Override
    public void updateConsumer(String consumerId, ConsumerCredential credential, GatewayConfig config) {
        AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
        }

        Gateway gateway = config.getGateway();
        if (gateway == null || gateway.getGatewayId() == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {

            // 从凭据中提取API Key
            String apiKey = null;
            if (credential != null
                    && credential.getApiKeyConfig() != null
                    && credential.getApiKeyConfig().getCredentials() != null
                    && !credential.getApiKeyConfig().getCredentials().isEmpty()) {
                apiKey = credential.getApiKeyConfig().getCredentials().get(0).getApiKey();
            }

            String url = client.getFullUrl("/application/modifyApp");

            // 构建请求体
            cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
            requestData.set("appId", consumerId);
            requestData.set("appName", consumerId);
            requestData.set("authType", 5);                 // 固定参数
            requestData.set("authTypeName", "API_KEY");
            requestData.set("description", consumerId);
            requestData.set("enable", true);                // 固定参数
            if (apiKey != null) {
                requestData.set("key", apiKey);
            }
            requestData.set("groups", Collections.singletonList("true")); // 固定参数
            requestData.set("gwInstanceId", gateway.getGatewayId());

            String requestBody = requestData.toString();
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            log.info("Updating consumer in ADP gateway: url={}, requestBody={}", url, requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
                Integer code = responseJson.getInt("code", 0);
                if (code != null && code == 200) {
                    log.info("Successfully updated consumer {} in ADP gateway instance {}", consumerId, gateway.getGatewayId());
                    return;
                }
                String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
                throw new BusinessException(ErrorCode.GATEWAY_ERROR, "更新ADP网关消费者失败: " + message);
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /application/modifyApp 失败");
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Error updating consumer {} in ADP gateway instance {}", consumerId, 
                    gateway != null ? gateway.getGatewayId() : "unknown", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "更新ADP网关消费者异常: " + e.getMessage());
        } finally {
            client.close();
        }
    }

    @Override
    public void deleteConsumer(String consumerId, GatewayConfig config) {
        AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
        }

        Gateway gateway = config.getGateway();
        if (gateway == null || gateway.getGatewayId() == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "网关实例ID缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {

            String url = client.getFullUrl("/application/deleteApp");
            String requestBody = String.format(
                "{\"appId\": \"%s\", \"gwInstanceId\": \"%s\"}",
                consumerId, gateway.getGatewayId()
            );
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            log.info("Deleting consumer in ADP gateway: url={}, requestBody={}", url, requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
                Integer code = responseJson.getInt("code", 0);
                if (code != null && code == 200) {
                    log.info("Successfully deleted consumer {} from ADP gateway instance {}", 
                             consumerId, gateway.getGatewayId());
                    return;
                }
                String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
                throw new BusinessException(ErrorCode.GATEWAY_ERROR, "删除ADP网关消费者失败: " + message);
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "调用 ADP /application/deleteApp 失败");
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("Error deleting consumer {} from ADP gateway instance {}", 
                      consumerId, gateway != null ? gateway.getGatewayId() : "unknown", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, "删除ADP网关消费者异常: " + e.getMessage());
        } finally {
            client.close();
        }
    }

    @Override
    public boolean isConsumerExists(String consumerId, GatewayConfig config) {
        AdpAIGatewayConfig adpConfig = config.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            log.warn("ADP AI Gateway配置缺失,无法检查消费者存在性");
            return false;
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {
            // 从 GatewayConfig 中获取 Gateway 实体
            Gateway gateway = config.getGateway();
            if (gateway == null || gateway.getGatewayId() == null) {
                log.warn("网关实例ID缺失,无法检查消费者存在性");
                return false;
            }

            String url = client.getFullUrl("/application/getApp");
            String requestBody = String.format(
                "{\"%s\": \"%s\", \"%s\": \"%s\"}", 
                "gwInstanceId", gateway.getGatewayId(),
                "appId", consumerId
            );
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
                Integer code = responseJson.getInt("code", 0);
                // 如果返回200且有data,说明消费者存在
                return code == 200 && responseJson.containsKey("data") && responseJson.get("data") != null;
            }
            return false;
        } catch (Exception e) {
            log.warn("检查ADP网关消费者存在性失败: consumerId={}", consumerId, e);
            return false;
        } finally {
            client.close();
        }
    }

    @Override
    public ConsumerAuthConfig authorizeConsumer(Gateway gateway, String consumerId, Object refConfig) {
        AdpAIGatewayConfig adpConfig = gateway.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
        }

        // 解析MCP Server配置
        APIGRefConfig apigRefConfig = (APIGRefConfig) refConfig;
        if (apigRefConfig == null || apigRefConfig.getMcpServerName() == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "MCP Server名称缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {
            // 构建授权请求参数
            // 由于createConsumer返回的就是appName,所以consumerId就是应用名称
            cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
            requestData.set("mcpServerName", apigRefConfig.getMcpServerName());
            requestData.set("consumers", Collections.singletonList(consumerId)); // consumerId就是appName
            requestData.set("gwInstanceId", gateway.getGatewayId());

            String url = client.getFullUrl("/mcpServer/addMcpServerConsumers");
            String requestBody = requestData.toString();
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            log.info("Authorizing consumer to MCP server: url={}, requestBody={}", url, requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
                Integer code = responseJson.getInt("code", 0);
                
                if (code == 200) {
                    log.info("Successfully authorized consumer {} to MCP server {}", 
                        consumerId, apigRefConfig.getMcpServerName());
                    
                    // 构建授权配置返回结果
                    AdpAIAuthConfig authConfig = AdpAIAuthConfig.builder()
                            .mcpServerName(apigRefConfig.getMcpServerName())
                            .consumerId(consumerId)
                            .gwInstanceId(gateway.getGatewayId())
                            .build();
                    
                    return ConsumerAuthConfig.builder()
                            .adpAIAuthConfig(authConfig)
                            .build();
                } else {
                    String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
                    throw new BusinessException(ErrorCode.GATEWAY_ERROR, 
                        "Failed to authorize consumer to MCP server: " + message);
                }
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to authorize consumer to MCP server");
        } catch (BusinessException e) {
            log.error("Business error authorizing consumer to MCP server", e);
            throw e;
        } catch (Exception e) {
            log.error("Error authorizing consumer {} to MCP server {}", 
                consumerId, apigRefConfig.getMcpServerName(), e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, 
                "Error authorizing consumer to MCP server: " + e.getMessage());
        } finally {
            client.close();
        }
    }

    @Override
    public void revokeConsumerAuthorization(Gateway gateway, String consumerId, ConsumerAuthConfig authConfig) {
        AdpAIAuthConfig adpAIAuthConfig = authConfig.getAdpAIAuthConfig();
        if (adpAIAuthConfig == null) {
            log.warn("ADP AI 授权配置为空,无法撤销授权");
            return;
        }

        AdpAIGatewayConfig adpConfig = gateway.getAdpAIGatewayConfig();
        if (adpConfig == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "ADP AI Gateway配置缺失");
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(adpConfig);
        try {
            // 构建撤销授权请求参数
            // 由于createConsumer返回的就是appName,所以consumerId就是应用名称
            cn.hutool.json.JSONObject requestData = JSONUtil.createObj();
            requestData.set("mcpServerName", adpAIAuthConfig.getMcpServerName());
            requestData.set("consumers", Collections.singletonList(consumerId)); // consumerId就是appName
            requestData.set("gwInstanceId", gateway.getGatewayId());

            String url = client.getFullUrl("/mcpServer/deleteMcpServerConsumers");
            String requestBody = requestData.toString();
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            log.info("Revoking consumer authorization from MCP server: url={}, requestBody={}", url, requestBody);

            ResponseEntity<String> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, String.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                cn.hutool.json.JSONObject responseJson = JSONUtil.parseObj(response.getBody());
                Integer code = responseJson.getInt("code", 0);
                
                if (code == 200) {
                    log.info("Successfully revoked consumer {} authorization from MCP server {}", 
                        consumerId, adpAIAuthConfig.getMcpServerName());
                } else {
                    String message = responseJson.getStr("message", responseJson.getStr("msg", "Unknown error"));
                    log.warn("Failed to revoke consumer authorization from MCP server: {}", message);
                    // 撤销授权失败不抛异常,只记录日志
                }
            } else {
                log.warn("Failed to revoke consumer authorization from MCP server, HTTP status: {}", 
                    response.getStatusCode());
            }
        } catch (Exception e) {
            log.error("Error revoking consumer {} authorization from MCP server {}", 
                consumerId, adpAIAuthConfig.getMcpServerName(), e);
            // 撤销授权失败不抛异常,只记录日志
        } finally {
            client.close();
        }
    }

    @Override
    public APIResult fetchAPI(Gateway gateway, String apiId) {
        return null;
    }

    @Override
    public GatewayType getGatewayType() {
        return GatewayType.ADP_AI_GATEWAY;
    }

    @Override
    public String getDashboard(Gateway gateway,String type) {
        return null;
    }

    @Override
    public PageResult<GatewayResult> fetchGateways(Object param, int page, int size) {
        if (!(param instanceof QueryAdpAIGatewayParam)) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "param");
        }
        return fetchGateways((QueryAdpAIGatewayParam) param, page, size);
    }

    public PageResult<GatewayResult> fetchGateways(QueryAdpAIGatewayParam param, int page, int size) {
        AdpAIGatewayConfig config = new AdpAIGatewayConfig();
        config.setBaseUrl(param.getBaseUrl());
        config.setPort(param.getPort());
        
        // 根据认证类型设置不同的认证信息
        if ("Seed".equals(param.getAuthType())) {
            if (param.getAuthSeed() == null || param.getAuthSeed().trim().isEmpty()) {
                throw new BusinessException(ErrorCode.INVALID_PARAMETER, "Seed认证方式下authSeed不能为空");
            }
            config.setAuthSeed(param.getAuthSeed());
        } else if ("Header".equals(param.getAuthType())) {
            if (param.getAuthHeaders() == null || param.getAuthHeaders().isEmpty()) {
                throw new BusinessException(ErrorCode.INVALID_PARAMETER, "Header认证方式下authHeaders不能为空");
            }
            // 将authHeaders转换为配置
            List<AdpAIGatewayConfig.AuthHeader> configHeaders = new ArrayList<>();
            for (QueryAdpAIGatewayParam.AuthHeader paramHeader : param.getAuthHeaders()) {
                AdpAIGatewayConfig.AuthHeader configHeader = new AdpAIGatewayConfig.AuthHeader();
                configHeader.setKey(paramHeader.getKey());
                configHeader.setValue(paramHeader.getValue());
                configHeaders.add(configHeader);
            }
            config.setAuthHeaders(configHeaders);
        } else {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "不支持的认证类型: " + param.getAuthType());
        }

        AdpAIGatewayClient client = new AdpAIGatewayClient(config);
        try {
            String url = client.getFullUrl("/gatewayInstance/listInstances");
            String requestBody = String.format("{\"current\": %d, \"size\": %d}", page, size);
            HttpEntity<String> requestEntity = client.createRequestEntity(requestBody);

            ResponseEntity<AdpGatewayInstanceResult> response = client.getRestTemplate().exchange(
                    url, HttpMethod.POST, requestEntity, AdpGatewayInstanceResult.class);

            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                AdpGatewayInstanceResult result = response.getBody();
                if (result.getCode() == 200 && result.getData() != null) {
                    return convertToGatewayResult(result.getData(), page, size);
                }
                String msg = result.getMessage() != null ? result.getMessage() : result.getMsg();
                throw new BusinessException(ErrorCode.GATEWAY_ERROR, msg);
            }
            throw new BusinessException(ErrorCode.GATEWAY_ERROR, "Failed to call ADP gateway API");
        } catch (Exception e) {
            log.error("Error fetching ADP gateways", e);
            throw new BusinessException(ErrorCode.INTERNAL_ERROR, e.getMessage());
        } finally {
            client.close();
        }
    }

    private PageResult<GatewayResult> convertToGatewayResult(AdpGatewayInstanceResult.AdpGatewayInstanceData data, int page, int size) {
        List<GatewayResult> gateways = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        if (data.getRecords() != null) {
            for (AdpGatewayInstanceResult.AdpGatewayInstance instance : data.getRecords()) {
                LocalDateTime createTime = null;
                try {
                    if (instance.getCreateTime() != null) {
                        createTime = LocalDateTime.parse(instance.getCreateTime(), formatter);
                    }
                } catch (Exception e) {
                    log.warn("Failed to parse create time: {}", instance.getCreateTime(), e);
                }
                GatewayResult gateway = GatewayResult.builder()
                        .gatewayId(instance.getGwInstanceId())
                        .gatewayName(instance.getName())
                        .gatewayType(GatewayType.ADP_AI_GATEWAY)
                        .createAt(createTime)
                        .build();
                gateways.add(gateway);
            }
        }
        return PageResult.of(gateways, page, size, data.getTotal() != null ? data.getTotal() : 0);
    }

    @Data
    public static class AdpMcpServerDetailResult {
        private Integer code;
        private String msg;
        private String message;
        private AdpMcpServerDetail data;

        @Data
        public static class AdpMcpServerDetail {
            private String gwInstanceId;
            private String name;
            private String description;
            private List<String> domains;
            private List<Service> services;
            private ConsumerAuthInfo consumerAuthInfo;
            private String rawConfigurations;
            private String type;
            private String dsn;
            private String dbType;
            private String upstreamPathPrefix;

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

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

```
Page 7/7FirstPrevNextLast