guzhi/app/utils/universal_api_manager.py
2025-10-01 20:27:20 +08:00

312 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
通用第三方API管理器
支持多种不同类型的第三方API包括需要签名和不需要签名的API
"""
import os
import hashlib
import requests
import logging
from datetime import datetime
from typing import Dict, Any, Optional, Union
import json
import time
import urllib.parse
from .api_config import api_config
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class UniversalAPIManager:
"""通用第三方API管理器"""
def __init__(self):
"""初始化API管理器"""
self.config = api_config
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Universal-API-Manager/1.0',
'Accept': 'application/json',
'Content-Type': 'application/json'
})
def _get_provider_config(self, provider: str) -> Dict[str, Any]:
"""获取API提供商配置"""
config = self.config.get_api_config(provider)
if not config:
raise ValueError(f"未找到API提供商配置: {provider}")
return config
def _get_endpoint_config(self, provider: str, endpoint: str) -> Dict[str, Any]:
"""获取端点配置"""
endpoint_config = self.config.get_endpoint_config(provider, endpoint)
if not endpoint_config:
raise ValueError(f"未找到端点配置: {provider}.{endpoint}")
return endpoint_config
def _generate_chinaz_sign(self, date: datetime = None) -> str:
"""
生成站长之家API签名
Args:
date: 指定日期,默认为当前日期
Returns:
str: 32位MD5签名
"""
if date is None:
date = datetime.now()
# 格式化日期为YYYYMMDD
date_str = date.strftime("%Y%m%d")
# 构建签名字符串
sign_str = f"634xz{date_str}"
# 生成MD5哈希
md5_hash = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
return md5_hash
def _build_url(self, provider: str, endpoint: str, params: Dict[str, Any]) -> str:
"""
构建完整的API请求URL
Args:
provider: API提供商
endpoint: 端点名称
params: 请求参数
Returns:
str: 完整的URL
"""
provider_config = self._get_provider_config(provider)
endpoint_config = self._get_endpoint_config(provider, endpoint)
base_url = provider_config['base_url']
path = endpoint_config['path']
method = endpoint_config['method']
# 构建完整URL
full_url = f"{base_url}{path}"
# 处理参数
if method.upper() == 'GET' and params:
# 对于GET请求将参数添加到URL中
query_string = urllib.parse.urlencode(params)
full_url = f"{full_url}?{query_string}"
return full_url
def _validate_params(self, provider: str, endpoint: str, params: Dict[str, Any]) -> None:
"""
验证请求参数
Args:
provider: API提供商
endpoint: 端点名称
params: 请求参数
Raises:
ValueError: 缺少必需参数时抛出异常
"""
endpoint_config = self._get_endpoint_config(provider, endpoint)
required_params = endpoint_config.get('required_params', [])
missing_params = []
for param in required_params:
if param not in params or params[param] is None:
missing_params.append(param)
if missing_params:
raise ValueError(f"缺少必需参数: {', '.join(missing_params)}")
def _prepare_params(self, provider: str, endpoint: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""
准备请求参数根据不同的API类型添加必要的参数
Args:
provider: API提供商
endpoint: 端点名称
params: 原始参数
Returns:
Dict[str, Any]: 处理后的参数
"""
provider_config = self._get_provider_config(provider)
prepared_params = params.copy()
# 根据不同的API提供商添加特定参数
if provider == 'chinaz':
# 站长之家API需要签名
if 'sign' not in prepared_params:
prepared_params['sign'] = self._generate_chinaz_sign()
elif provider == 'justoneapi':
# JustOneAPI需要token参数
if 'token' not in prepared_params or not prepared_params['token']:
api_key = provider_config.get('api_key')
if api_key:
prepared_params['token'] = api_key
return prepared_params
def make_request(self,
provider: str,
endpoint: str,
params: Dict[str, Any] = None,
timeout: int = 60,
retries: int = 3) -> Dict[str, Any]:
"""
发送API请求
Args:
provider: API提供商名称
endpoint: 端点名称
params: 请求参数
timeout: 超时时间(秒)
retries: 重试次数
Returns:
Dict[str, Any]: API响应数据
"""
params = params or {}
# 验证参数
self._validate_params(provider, endpoint, params)
# 准备参数
prepared_params = self._prepare_params(provider, endpoint, params)
# 获取端点配置
endpoint_config = self._get_endpoint_config(provider, endpoint)
method = endpoint_config['method'].upper()
# 构建URL
url = self._build_url(provider, endpoint, prepared_params)
logger.info(f"发送{method}请求到: {url}")
# 发送请求
for attempt in range(retries + 1):
try:
if method == 'GET':
response = self.session.get(url, timeout=timeout)
elif method == 'POST':
response = self.session.post(url, json=prepared_params, timeout=timeout)
else:
raise ValueError(f"不支持的HTTP方法: {method}")
# 检查响应状态
response.raise_for_status()
# 解析响应
try:
result = response.json()
except json.JSONDecodeError:
result = {'raw_response': response.text}
logger.info(f"API请求成功: {provider}.{endpoint}")
return result
except requests.exceptions.RequestException as e:
logger.warning(f"API请求失败 (尝试 {attempt + 1}/{retries + 1}): {e}")
if attempt == retries:
logger.error(f"API请求最终失败: {provider}.{endpoint}")
return {
'error': True,
'message': f"API请求失败: {str(e)}",
'provider': provider,
'endpoint': endpoint
}
time.sleep(2 ** attempt) # 指数退避
# 站长之家API的便捷方法
def query_copyright_software(self, company_name: str, chinaz_ver: str = "1") -> Dict[str, Any]:
"""查询企业软件著作权"""
params = {
'entName': company_name,
'pageNo': '1',
'range': '100',
'ChinazVer': chinaz_ver
}
return self.make_request('chinaz', 'copyright_software', params)
def query_patent_info(self, company_name: str, chinaz_ver: str = "1") -> Dict[str, Any]:
"""查询企业专利信息"""
params = {
'searchKey': company_name,
'pageNo': '1',
'range': '100',
'searchType': '0',
'ChinazVer': chinaz_ver
}
return self.make_request('chinaz', 'patent', params)
def query_judicial_data(self, company_name: str, chinaz_ver: str = "1") -> Dict[str, Any]:
"""查询司法综合数据"""
params = {
'q': company_name,
'pageNo': '1',
'ChinazVer': chinaz_ver
}
return self.make_request('chinaz', 'judgement', params)
# JustOneAPI的便捷方法
def get_xiaohongshu_note_detail(self, note_id: str) -> Dict[str, Any]:
"""
获取小红书笔记详情
Args:
note_id: 笔记ID
Returns:
Dict[str, Any]: 笔记详情数据
"""
params = {'noteId': note_id}
return self.make_request('justoneapi', 'xiaohongshu_note_detail', params)
# 通用方法
def list_providers(self) -> list:
"""列出所有可用的API提供商"""
return self.config.list_providers()
def list_endpoints(self, provider: str) -> list:
"""列出指定提供商的所有端点"""
return self.config.list_endpoints(provider)
def get_endpoint_info(self, provider: str, endpoint: str) -> Dict[str, Any]:
"""获取端点详细信息"""
return self._get_endpoint_config(provider, endpoint)
def set_api_key(self, provider: str, api_key: str) -> None:
"""设置API密钥"""
self.config.set_api_key(provider, api_key)
def add_endpoint(self,
provider: str,
endpoint_name: str,
path: str,
method: str = 'GET',
description: str = '',
required_params: list = None,
optional_params: list = None) -> None:
"""添加新的API端点"""
self.config.add_endpoint(
provider=provider,
endpoint_name=endpoint_name,
path=path,
method=method,
description=description,
required_params=required_params,
optional_params=optional_params
)
# 创建全局实例
universal_api = UniversalAPIManager()