|
|
|
|
@ -1,8 +1,8 @@
|
|
|
|
|
# services/network_service.py
|
|
|
|
|
import requests
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import time
|
|
|
|
|
import os
|
|
|
|
|
import json
|
|
|
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
|
|
|
|
class NetworkService:
|
|
|
|
|
@ -10,12 +10,21 @@ class NetworkService:
|
|
|
|
|
|
|
|
|
|
# 实现构造函数逻辑
|
|
|
|
|
self.api_key = None
|
|
|
|
|
self.ai_provider = "DeepSeek" # 默认AI服务提供商
|
|
|
|
|
self.ai_api_key = None # AI API密钥
|
|
|
|
|
self.cache = {}
|
|
|
|
|
# 设置默认headers以避免被服务器拒绝
|
|
|
|
|
self.session = requests.Session()
|
|
|
|
|
self.session.headers.update({
|
|
|
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
|
|
|
|
})
|
|
|
|
|
# 天气缓存相关属性
|
|
|
|
|
self._cached_weather_data = None # 缓存的天气数据
|
|
|
|
|
self._cached_location = None # 缓存的定位信息
|
|
|
|
|
self._weather_cache_timestamp = None # 缓存时间戳
|
|
|
|
|
|
|
|
|
|
# 加载AI配置
|
|
|
|
|
self.load_ai_config()
|
|
|
|
|
|
|
|
|
|
def get_cached_weather_data(self):
|
|
|
|
|
"""获取缓存的天气数据"""
|
|
|
|
|
@ -25,6 +34,94 @@ class NetworkService:
|
|
|
|
|
"""获取缓存的定位信息"""
|
|
|
|
|
return self._cached_location
|
|
|
|
|
|
|
|
|
|
def load_ai_config(self):
|
|
|
|
|
"""加载AI配置"""
|
|
|
|
|
try:
|
|
|
|
|
config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'resources', 'config', 'ai_config.json')
|
|
|
|
|
if os.path.exists(config_file):
|
|
|
|
|
with open(config_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
config = json.load(f)
|
|
|
|
|
self.ai_provider = config.get("provider", "DeepSeek")
|
|
|
|
|
self.ai_api_key = config.get("api_key", "")
|
|
|
|
|
print(f"AI配置已加载: 提供商={self.ai_provider}, API密钥={'已设置' if self.ai_api_key else '未设置'}")
|
|
|
|
|
else:
|
|
|
|
|
# 尝试加载旧的DeepSeek配置
|
|
|
|
|
old_config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'resources', 'config', 'deepseek_api.json')
|
|
|
|
|
if os.path.exists(old_config_file):
|
|
|
|
|
with open(old_config_file, 'r', encoding='utf-8') as f:
|
|
|
|
|
old_config = json.load(f)
|
|
|
|
|
self.ai_provider = "DeepSeek"
|
|
|
|
|
self.ai_api_key = old_config.get("api_key", "")
|
|
|
|
|
print(f"旧的DeepSeek配置已加载: API密钥={'已设置' if self.ai_api_key else '未设置'}")
|
|
|
|
|
else:
|
|
|
|
|
print("未找到AI配置文件,使用默认配置")
|
|
|
|
|
self.ai_provider = "DeepSeek"
|
|
|
|
|
self.ai_api_key = None
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"加载AI配置失败: {e}")
|
|
|
|
|
self.ai_provider = "DeepSeek"
|
|
|
|
|
self.ai_api_key = None
|
|
|
|
|
|
|
|
|
|
def get_ai_provider_info(self, provider_name):
|
|
|
|
|
"""获取AI提供商的详细信息"""
|
|
|
|
|
provider_info = {
|
|
|
|
|
'deepseek': {
|
|
|
|
|
'name': 'DeepSeek',
|
|
|
|
|
'base_url': 'https://api.deepseek.com',
|
|
|
|
|
'model': 'deepseek-chat',
|
|
|
|
|
'description': '深度求索 - 中国AI公司,提供高质量对话模型'
|
|
|
|
|
},
|
|
|
|
|
'openai': {
|
|
|
|
|
'name': 'OpenAI',
|
|
|
|
|
'base_url': 'https://api.openai.com',
|
|
|
|
|
'model': 'gpt-3.5-turbo',
|
|
|
|
|
'description': 'OpenAI - GPT系列模型提供商'
|
|
|
|
|
},
|
|
|
|
|
'kimi': {
|
|
|
|
|
'name': 'Kimi',
|
|
|
|
|
'base_url': 'https://api.moonshot.cn',
|
|
|
|
|
'model': 'moonshot-v1-8k',
|
|
|
|
|
'description': '月之暗面 - 支持长文本处理的AI助手'
|
|
|
|
|
},
|
|
|
|
|
'siliconflow': {
|
|
|
|
|
'name': '硅基流动',
|
|
|
|
|
'base_url': 'https://api.siliconflow.cn',
|
|
|
|
|
'model': 'deepseek-ai/deepseek-llm-67b-chat',
|
|
|
|
|
'description': '硅基流动 - 提供多种开源模型服务'
|
|
|
|
|
},
|
|
|
|
|
'claude': {
|
|
|
|
|
'name': 'Claude',
|
|
|
|
|
'base_url': 'https://api.anthropic.com',
|
|
|
|
|
'model': 'claude-3-sonnet-20240229',
|
|
|
|
|
'description': 'Anthropic - Claude系列对话AI'
|
|
|
|
|
},
|
|
|
|
|
'gemini': {
|
|
|
|
|
'name': 'Gemini',
|
|
|
|
|
'base_url': 'https://generativelanguage.googleapis.com',
|
|
|
|
|
'model': 'gemini-pro',
|
|
|
|
|
'description': 'Google - Gemini系列多模态AI模型'
|
|
|
|
|
},
|
|
|
|
|
'qwen': {
|
|
|
|
|
'name': '通义千问',
|
|
|
|
|
'base_url': 'https://dashscope.aliyuncs.com',
|
|
|
|
|
'model': 'qwen-turbo',
|
|
|
|
|
'description': '阿里云 - 通义千问大模型系列'
|
|
|
|
|
},
|
|
|
|
|
'ernie': {
|
|
|
|
|
'name': '文心一言',
|
|
|
|
|
'base_url': 'https://aip.baidubce.com',
|
|
|
|
|
'model': 'ernie-bot-turbo',
|
|
|
|
|
'description': '百度 - 文心一言大语言模型'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 如果提供商不存在,返回默认的DeepSeek信息
|
|
|
|
|
return provider_info.get(provider_name.lower(), provider_info['deepseek'])
|
|
|
|
|
|
|
|
|
|
def get_current_ai_provider_info(self):
|
|
|
|
|
"""获取当前AI提供商的详细信息"""
|
|
|
|
|
return self.get_ai_provider_info(self.ai_provider)
|
|
|
|
|
|
|
|
|
|
def set_weather_cache(self, weather_data, location):
|
|
|
|
|
"""设置天气缓存"""
|
|
|
|
|
self._cached_weather_data = weather_data
|
|
|
|
|
@ -43,39 +140,42 @@ class NetworkService:
|
|
|
|
|
return False
|
|
|
|
|
return (time.time() - self._weather_cache_timestamp) < 1800 # 30分钟
|
|
|
|
|
|
|
|
|
|
def get_user_ip(self):
|
|
|
|
|
"""获取用户IP地址 - 使用多个备用服务"""
|
|
|
|
|
# 首先尝试获取本地IP
|
|
|
|
|
try:
|
|
|
|
|
import socket
|
|
|
|
|
hostname = socket.gethostname()
|
|
|
|
|
local_ip = socket.gethostbyname(hostname)
|
|
|
|
|
if local_ip and not local_ip.startswith("127."):
|
|
|
|
|
print(f"获取到本地IP: {local_ip}")
|
|
|
|
|
return local_ip
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"获取本地IP失败: {e}")
|
|
|
|
|
|
|
|
|
|
# 如果本地IP获取失败,使用备用外部服务
|
|
|
|
|
def get_user_ip(self) -> Optional[str]:
|
|
|
|
|
"""获取用户公网IP地址,使用多个备用服务"""
|
|
|
|
|
# 备用IP获取服务列表
|
|
|
|
|
ip_services = [
|
|
|
|
|
"https://httpbin.org/ip",
|
|
|
|
|
"https://api.ipify.org?format=json",
|
|
|
|
|
"https://ipapi.co/json/"
|
|
|
|
|
"https://ident.me/.json"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for service in ip_services:
|
|
|
|
|
for service_url in ip_services:
|
|
|
|
|
try:
|
|
|
|
|
print(f"尝试从 {service} 获取IP地址...")
|
|
|
|
|
response = self.session.get(service, timeout=3, verify=False)
|
|
|
|
|
if response.status_code == 200:
|
|
|
|
|
data = response.json()
|
|
|
|
|
ip = data.get("origin") or data.get("ip") or data.get("ip_address")
|
|
|
|
|
if ip:
|
|
|
|
|
print(f"成功从 {service} 获取IP: {ip}")
|
|
|
|
|
return ip
|
|
|
|
|
print(f"尝试获取IP地址: {service_url}")
|
|
|
|
|
response = self.session.get(service_url, timeout=10)
|
|
|
|
|
response.raise_for_status() # 检查HTTP错误
|
|
|
|
|
|
|
|
|
|
data = response.json()
|
|
|
|
|
# 不同服务返回的IP字段可能不同
|
|
|
|
|
ip = (data.get('origin') or
|
|
|
|
|
data.get('ip') or
|
|
|
|
|
data.get('IPAddress') or
|
|
|
|
|
data.get('query'))
|
|
|
|
|
|
|
|
|
|
if ip:
|
|
|
|
|
# 如果IP包含逗号,取第一个(可能有代理)
|
|
|
|
|
if ',' in ip:
|
|
|
|
|
ip = ip.split(',')[0].strip()
|
|
|
|
|
print(f"成功获取IP地址: {ip}")
|
|
|
|
|
return ip
|
|
|
|
|
else:
|
|
|
|
|
print(f"无法从响应中提取IP地址: {data}")
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
print(f"网络请求错误 ({service_url}): {e}")
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
print(f"JSON解析错误 ({service_url}): {e}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"从 {service} 获取IP失败: {e}")
|
|
|
|
|
continue
|
|
|
|
|
print(f"获取IP时发生未知错误 ({service_url}): {e}")
|
|
|
|
|
|
|
|
|
|
print("所有IP获取服务都失败了,使用默认IP")
|
|
|
|
|
return "8.8.8.8" # 使用Google DNS作为默认IP
|
|
|
|
|
@ -134,22 +234,44 @@ class NetworkService:
|
|
|
|
|
print("无法获取IP地址,使用默认天气数据")
|
|
|
|
|
return self.get_default_weather()
|
|
|
|
|
|
|
|
|
|
# 2. 根据IP获取地理位置
|
|
|
|
|
# 注意:这里使用免费的IP地理位置API,实际应用中可能需要更精确的服务
|
|
|
|
|
location_url = f"http://ip-api.com/json/{ip}"
|
|
|
|
|
print(f"请求地理位置: {location_url}")
|
|
|
|
|
location_response = self.session.get(location_url, timeout=5, verify=False)
|
|
|
|
|
location_data = location_response.json()
|
|
|
|
|
print(f"地理位置响应: {location_data}")
|
|
|
|
|
# 2. 根据IP获取地理位置 - 增加重试机制并使用更稳定的API
|
|
|
|
|
location_success = False
|
|
|
|
|
location_data = None
|
|
|
|
|
# 使用ipapi.co API,它比ip-api.com更稳定
|
|
|
|
|
location_url = f"https://ipapi.co/{ip}/json/"
|
|
|
|
|
|
|
|
|
|
for attempt in range(3): # 最多尝试3次
|
|
|
|
|
try:
|
|
|
|
|
print(f"请求地理位置 (尝试 {attempt + 1}/3): {location_url}")
|
|
|
|
|
location_response = self.session.get(location_url, timeout=10)
|
|
|
|
|
location_response.raise_for_status() # 检查HTTP错误
|
|
|
|
|
location_data = location_response.json()
|
|
|
|
|
print(f"地理位置响应: {location_data}")
|
|
|
|
|
|
|
|
|
|
# ipapi.co 使用不同的状态字段
|
|
|
|
|
if 'error' not in location_data:
|
|
|
|
|
location_success = True
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
error_msg = location_data.get('reason', 'Unknown error')
|
|
|
|
|
print(f"地理位置获取失败,错误: {error_msg}")
|
|
|
|
|
time.sleep(1) # 等待1秒后重试
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
print(f"网络请求错误 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"获取地理位置时出错 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
|
|
|
|
|
if location_data.get("status") != "success":
|
|
|
|
|
if not location_success:
|
|
|
|
|
print("地理位置获取失败,使用默认天气数据")
|
|
|
|
|
return self.get_default_weather()
|
|
|
|
|
|
|
|
|
|
# ipapi.co 使用不同的字段名
|
|
|
|
|
city = location_data.get("city", "Unknown")
|
|
|
|
|
print(f"获取到的城市: {city}")
|
|
|
|
|
|
|
|
|
|
if not city:
|
|
|
|
|
if not city or city == "Unknown":
|
|
|
|
|
print("无法获取城市名称,使用默认天气数据")
|
|
|
|
|
return self.get_default_weather()
|
|
|
|
|
|
|
|
|
|
@ -157,29 +279,47 @@ class NetworkService:
|
|
|
|
|
self._cached_location = {
|
|
|
|
|
"ip": ip,
|
|
|
|
|
"city": city,
|
|
|
|
|
"country": location_data.get("country", "Unknown"),
|
|
|
|
|
"region": location_data.get("regionName", "Unknown")
|
|
|
|
|
"country": location_data.get("country_name", "Unknown"),
|
|
|
|
|
"region": location_data.get("region", "Unknown")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 3. 调用天气API获取天气数据
|
|
|
|
|
# 注意:这里使用OpenWeatherMap API作为示例,需要API密钥
|
|
|
|
|
# 在实际应用中,需要设置有效的API密钥
|
|
|
|
|
if self.api_key:
|
|
|
|
|
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
|
|
|
|
|
weather_response = self.session.get(weather_url, timeout=5, verify=False)
|
|
|
|
|
weather_data = weather_response.json()
|
|
|
|
|
weather_success = False
|
|
|
|
|
weather_data = None
|
|
|
|
|
for attempt in range(3): # 最多尝试3次
|
|
|
|
|
try:
|
|
|
|
|
weather_url = f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={self.api_key}&units=metric&lang=zh_cn"
|
|
|
|
|
print(f"请求天气数据 (尝试 {attempt + 1}/3): {weather_url}")
|
|
|
|
|
weather_response = self.session.get(weather_url, timeout=10)
|
|
|
|
|
weather_response.raise_for_status() # 检查HTTP错误
|
|
|
|
|
weather_data = weather_response.json()
|
|
|
|
|
print(f"天气响应状态码: {weather_response.status_code}")
|
|
|
|
|
|
|
|
|
|
# 4. 解析并格式化数据
|
|
|
|
|
if weather_response.status_code == 200:
|
|
|
|
|
formatted_weather = {
|
|
|
|
|
"city": city,
|
|
|
|
|
"temperature": weather_data["main"]["temp"],
|
|
|
|
|
"description": weather_data["weather"][0]["description"],
|
|
|
|
|
"humidity": weather_data["main"]["humidity"],
|
|
|
|
|
"wind_speed": weather_data["wind"]["speed"]
|
|
|
|
|
}
|
|
|
|
|
# 5. 返回天气信息字典
|
|
|
|
|
return formatted_weather
|
|
|
|
|
else:
|
|
|
|
|
print(f"天气API返回错误状态码: {weather_response.status_code}")
|
|
|
|
|
time.sleep(1) # 等待1秒后重试
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
print(f"网络请求错误 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"获取天气数据时出错 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
|
|
|
|
|
# 4. 解析并格式化数据
|
|
|
|
|
if weather_response.status_code == 200:
|
|
|
|
|
formatted_weather = {
|
|
|
|
|
"city": city,
|
|
|
|
|
"temperature": weather_data["main"]["temp"],
|
|
|
|
|
"description": weather_data["weather"][0]["description"],
|
|
|
|
|
"humidity": weather_data["main"]["humidity"],
|
|
|
|
|
"wind_speed": weather_data["wind"]["speed"]
|
|
|
|
|
}
|
|
|
|
|
# 5. 返回天气信息字典
|
|
|
|
|
return formatted_weather
|
|
|
|
|
print("所有天气API请求都失败了")
|
|
|
|
|
else:
|
|
|
|
|
# 当没有API密钥时,使用免费的天气API获取真实数据
|
|
|
|
|
# 首先尝试获取城市ID(需要映射城市名到ID)
|
|
|
|
|
@ -239,74 +379,87 @@ class NetworkService:
|
|
|
|
|
weather_city_id = "101010100" # 默认北京ID
|
|
|
|
|
print(f"使用默认城市ID获取天气: {city} -> {weather_city_id}")
|
|
|
|
|
|
|
|
|
|
# 使用免费天气API获取天气数据
|
|
|
|
|
try:
|
|
|
|
|
# 使用和风天气免费API的替代方案 - sojson天气API
|
|
|
|
|
weather_url = f"http://t.weather.sojson.com/api/weather/city/{weather_city_id}"
|
|
|
|
|
print(f"请求天气数据: {weather_url}")
|
|
|
|
|
weather_response = self.session.get(weather_url, timeout=5, verify=False)
|
|
|
|
|
print(f"天气响应状态码: {weather_response.status_code}")
|
|
|
|
|
weather_data = weather_response.json()
|
|
|
|
|
print(f"天气数据响应: {weather_data}")
|
|
|
|
|
|
|
|
|
|
if weather_data.get("status") == 200:
|
|
|
|
|
# 解析天气数据
|
|
|
|
|
current_data = weather_data.get("data", {})
|
|
|
|
|
wendu = current_data.get("wendu", "N/A")
|
|
|
|
|
shidu = current_data.get("shidu", "N/A")
|
|
|
|
|
forecast = current_data.get("forecast", [])
|
|
|
|
|
|
|
|
|
|
# 获取第一个预报项作为当前天气
|
|
|
|
|
current_weather = forecast[0] if forecast else {}
|
|
|
|
|
weather_type = current_weather.get("type", "晴")
|
|
|
|
|
|
|
|
|
|
# 获取生活指数信息
|
|
|
|
|
lifetips = []
|
|
|
|
|
if current_weather:
|
|
|
|
|
# 从预报数据中提取生活提示
|
|
|
|
|
ganmao = current_weather.get("ganmao", "")
|
|
|
|
|
if ganmao:
|
|
|
|
|
lifetips.append(f"感冒指数: {ganmao}")
|
|
|
|
|
|
|
|
|
|
# 添加其他生活指数(基于天气类型推断)
|
|
|
|
|
if "雨" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 记得带伞")
|
|
|
|
|
elif "晴" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 适合户外活动")
|
|
|
|
|
elif "雪" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 注意防滑保暖")
|
|
|
|
|
elif "雾" in weather_type or "霾" in weather_type:
|
|
|
|
|
lifetips.append("健康提醒: 减少户外运动")
|
|
|
|
|
|
|
|
|
|
# 温度相关建议
|
|
|
|
|
temp = float(wendu) if wendu != "N/A" else 20
|
|
|
|
|
if temp > 30:
|
|
|
|
|
lifetips.append("穿衣建议: 注意防暑降温")
|
|
|
|
|
elif temp < 5:
|
|
|
|
|
lifetips.append("穿衣建议: 注意保暖防寒")
|
|
|
|
|
elif temp < 15:
|
|
|
|
|
lifetips.append("穿衣建议: 适当添加衣物")
|
|
|
|
|
else:
|
|
|
|
|
lifetips.append("穿衣建议: 天气舒适")
|
|
|
|
|
# 使用免费天气API获取天气数据 - 增加重试机制
|
|
|
|
|
weather_success = False
|
|
|
|
|
weather_data = None
|
|
|
|
|
for attempt in range(3): # 最多尝试3次
|
|
|
|
|
try:
|
|
|
|
|
# 使用和风天气免费API的替代方案 - sojson天气API
|
|
|
|
|
weather_url = f"http://t.weather.sojson.com/api/weather/city/{weather_city_id}"
|
|
|
|
|
print(f"请求天气数据 (尝试 {attempt + 1}/3): {weather_url}")
|
|
|
|
|
weather_response = self.session.get(weather_url, timeout=10)
|
|
|
|
|
weather_response.raise_for_status() # 检查HTTP错误
|
|
|
|
|
weather_data = weather_response.json()
|
|
|
|
|
print(f"天气数据响应: {weather_data}")
|
|
|
|
|
|
|
|
|
|
formatted_weather = {
|
|
|
|
|
"city": city,
|
|
|
|
|
"temperature": float(wendu) if wendu != "N/A" else 20,
|
|
|
|
|
"description": weather_type,
|
|
|
|
|
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
|
|
|
|
|
"wind_speed": "3.5", # 默认风速
|
|
|
|
|
"lifetips": lifetips # 生活提示列表
|
|
|
|
|
}
|
|
|
|
|
print(f"成功获取天气数据: {formatted_weather}")
|
|
|
|
|
if weather_data.get("status") == 200:
|
|
|
|
|
weather_success = True
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
print(f"天气API返回错误状态: {weather_data.get('status')}")
|
|
|
|
|
time.sleep(1) # 等待1秒后重试
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
|
|
print(f"网络请求错误 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"获取免费天气数据时出错 (尝试 {attempt + 1}/3): {e}")
|
|
|
|
|
time.sleep(2) # 等待2秒后重试
|
|
|
|
|
|
|
|
|
|
if weather_success:
|
|
|
|
|
# 解析天气数据
|
|
|
|
|
current_data = weather_data.get("data", {})
|
|
|
|
|
wendu = current_data.get("wendu", "N/A")
|
|
|
|
|
shidu = current_data.get("shidu", "N/A")
|
|
|
|
|
forecast = current_data.get("forecast", [])
|
|
|
|
|
|
|
|
|
|
# 获取第一个预报项作为当前天气
|
|
|
|
|
current_weather = forecast[0] if forecast else {}
|
|
|
|
|
weather_type = current_weather.get("type", "晴")
|
|
|
|
|
|
|
|
|
|
# 获取生活指数信息
|
|
|
|
|
lifetips = []
|
|
|
|
|
if current_weather:
|
|
|
|
|
# 从预报数据中提取生活提示
|
|
|
|
|
ganmao = current_weather.get("ganmao", "")
|
|
|
|
|
if ganmao:
|
|
|
|
|
lifetips.append(f"感冒指数: {ganmao}")
|
|
|
|
|
|
|
|
|
|
# 缓存天气数据
|
|
|
|
|
self.set_weather_cache(formatted_weather, self._cached_location)
|
|
|
|
|
return formatted_weather
|
|
|
|
|
else:
|
|
|
|
|
print(f"天气API返回错误状态: {weather_data.get('status')}")
|
|
|
|
|
# 添加其他生活指数(基于天气类型推断)
|
|
|
|
|
if "雨" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 记得带伞")
|
|
|
|
|
elif "晴" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 适合户外活动")
|
|
|
|
|
elif "雪" in weather_type:
|
|
|
|
|
lifetips.append("出行建议: 注意防滑保暖")
|
|
|
|
|
elif "雾" in weather_type or "霾" in weather_type:
|
|
|
|
|
lifetips.append("健康提醒: 减少户外运动")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"获取免费天气数据时出错: {e}")
|
|
|
|
|
# 温度相关建议
|
|
|
|
|
temp = float(wendu) if wendu != "N/A" else 20
|
|
|
|
|
if temp > 30:
|
|
|
|
|
lifetips.append("穿衣建议: 注意防暑降温")
|
|
|
|
|
elif temp < 5:
|
|
|
|
|
lifetips.append("穿衣建议: 注意保暖防寒")
|
|
|
|
|
elif temp < 15:
|
|
|
|
|
lifetips.append("穿衣建议: 适当添加衣物")
|
|
|
|
|
else:
|
|
|
|
|
lifetips.append("穿衣建议: 天气舒适")
|
|
|
|
|
|
|
|
|
|
formatted_weather = {
|
|
|
|
|
"city": city,
|
|
|
|
|
"temperature": float(wendu) if wendu != "N/A" else 20,
|
|
|
|
|
"description": weather_type,
|
|
|
|
|
"humidity": shidu.replace("%", "") if shidu != "N/A" else "60",
|
|
|
|
|
"wind_speed": "3.5", # 默认风速
|
|
|
|
|
"lifetips": lifetips # 生活提示列表
|
|
|
|
|
}
|
|
|
|
|
print(f"成功获取天气数据: {formatted_weather}")
|
|
|
|
|
|
|
|
|
|
# 缓存天气数据
|
|
|
|
|
self.set_weather_cache(formatted_weather, self._cached_location)
|
|
|
|
|
return formatted_weather
|
|
|
|
|
else:
|
|
|
|
|
print("所有免费天气API请求都失败了")
|
|
|
|
|
|
|
|
|
|
# 如果以上都失败,返回默认数据
|
|
|
|
|
default_weather = {
|
|
|
|
|
|