"""ServiceNow 厂商集成元数据。 定义 ``IntegrationMetadata(key="servicenow", ...)``,由 ``__init__.py`` 自注册到 ``IntegrationRegistry``。元数据为内存态,不持久化到数据库。 同时定义 ``ServiceNowConnectionConfig`` 连接配置 Schema 与 ``resolve_oauth_endpoints`` OAuth 端点拼接函数,供 use_cases 层 / 前端在系统创建 与配置阶段调用(见设计文档 §3.2 / §3.3)。 """ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field, model_validator from yuxi.external_systems.integrations.schemas import IntegrationMetadata class ServiceNowConnectionConfig(BaseModel): """ServiceNow 连接配置 Schema。 ``base_url`` 优先级高于 ``instance``,支持私有部署场景。OAuth 端点由 ``resolve_oauth_endpoints`` 在 handler 外动态拼接,注入到 ``auth_config`` 后传给框架级 OAuth2 插件。 """ model_config = ConfigDict(populate_by_name=True, extra="ignore") instance: str | None = Field( default=None, description="ServiceNow 实例名,如 acme(对应 https://acme.service-now.com)", ) base_url: str | None = Field( default=None, description="完整 base_url,优先级高于 instance;用于私有部署或自定义域名", ) api_version: str | None = Field( default=None, description="Table API 版本,如 v1 / v2;None 表示非版本化路径 /api/now/table/...", ) @model_validator(mode="after") def _ensure_url(self) -> ServiceNowConnectionConfig: if not self.base_url and not self.instance: raise ValueError("instance 与 base_url 至少填写一项") if not self.base_url: self.base_url = f"https://{self.instance}.service-now.com" return self def resolve_oauth_endpoints(base_url: str) -> dict[str, str]: """根据 base_url 拼接 ServiceNow 实例级 OAuth 端点。 由 use_cases 层 / 前端在系统创建与配置阶段调用,根据 ``connection_config.base_url`` 拼接 ``token_url`` / ``authorize_url``, 写入 ``auth_config`` 后持久化。handler 执行时 token 已由 use_cases 层通过 ``token_manager.get_token`` 预解析并注入到 ``system_config["_resolved_token"]``, handler 不再调用本函数(与 dynamics365 一致)。 """ return { "authorize_url": f"{base_url}/oauth_auth.do", "token_url": f"{base_url}/oauth_token.do", } SERVICENOW_METADATA = IntegrationMetadata( key="servicenow", display_name="ServiceNow", description="ITSM / 工作流平台,基于 Table REST API 集成", adapter_type="http", source_types=["servicenow"], supported_auth_types=[ "oauth2_authorization_code", "oauth2_password", "oauth2_client_credentials", "basic", ], tags=["itsm", "workflow", "ticketing"], icon="servicenow", docs_url="https://developer.servicenow.com/dev.do#!/learn/learning-plans/yokohama/rest_api", connection_extra_schema={ "instance": { "type": "string", "description": "ServiceNow 实例名,如 acme(对应 https://acme.service-now.com)", }, "base_url": { "type": "string", "description": "完整 base_url(优先级高于 instance),用于私有部署或自定义域名", }, "api_version": { "type": "string", "enum": ["v1", "v2"], "description": "Table API 版本;None 表示非版本化路径 /api/now/table/...", }, }, example_config={ "instance": "acme", "api_version": None, "auth_type": "oauth2_client_credentials", "token_url": "https://acme.service-now.com/oauth_token.do", "client_id": "example-client-id", "client_secret": "ref://servicenow/acme/client_secret", "scope": None, }, dependency_note=( "1. 调用账号需对 sys_db_object / sys_dictionary / sys_documentation / sys_choice / sys_glide_object 具备 READ 权限;" "2. OAuth2 应用需在目标实例中注册并配置回调地址;" "3. oauth2_client_credentials 需 Washington DC 及以上版本;" "4. client_secret 建议使用 ref:// 密钥引用。" ), )