@@ -0,0 +1,227 @@
import requests
from datetime import datetime
from rest_framework . permissions import AllowAny
from rest_framework . views import APIView
from rest_framework . response import Response
from rest_framework import status
from drf_yasg . utils import swagger_auto_schema
from drf_yasg import openapi
from chunyu_project . common_schemas import success_response , error_response
GEOCODING_URL = " https://geocoding-api.open-meteo.com/v1/search "
FORECAST_URL = " https://api.open-meteo.com/v1/forecast "
WEATHER_CODE_MAP = {
0 : " 晴朗 " ,
1 : " 大部晴朗 " ,
2 : " 多云 " ,
3 : " 阴天 " ,
45 : " 雾 " ,
48 : " 雾凇 " ,
51 : " 小毛毛雨 " ,
53 : " 毛毛雨 " ,
55 : " 大毛毛雨 " ,
56 : " 冻毛毛雨 " ,
57 : " 强冻毛毛雨 " ,
61 : " 小雨 " ,
63 : " 中雨 " ,
65 : " 大雨 " ,
66 : " 冻雨 " ,
67 : " 强冻雨 " ,
71 : " 小雪 " ,
73 : " 中雪 " ,
75 : " 大雪 " ,
77 : " 雪粒 " ,
80 : " 阵雨 " ,
81 : " 强阵雨 " ,
82 : " 暴雨 " ,
85 : " 阵雪 " ,
86 : " 强阵雪 " ,
95 : " 雷暴 " ,
96 : " 雷暴伴小冰雹 " ,
99 : " 雷暴伴大冰雹 " ,
}
class WeatherView ( APIView ) :
"""
天气查询视图 - 基于 Open-Meteo API(免费,无需API Key)
"""
permission_classes = [ AllowAny ]
@swagger_auto_schema (
tags = [ ' Weather ' ] ,
operation_summary = ' 获取天气信息(GET方式) ' ,
operation_description = ' 通过城市名称获取实时天气信息,支持温度单位和语言设置 ' ,
manual_parameters = [
openapi . Parameter (
' city ' ,
openapi . IN_QUERY ,
description = ' 城市名称(支持中文、英文,如:北京、Beijing、上海、Shanghai) ' ,
type = openapi . TYPE_STRING ,
required = True
) ,
openapi . Parameter (
' unit ' ,
openapi . IN_QUERY ,
description = ' 温度单位:celsius(摄氏度,默认)、fahrenheit(华氏度) ' ,
type = openapi . TYPE_STRING ,
required = False ,
enum = [ ' celsius ' , ' fahrenheit ' ]
) ,
openapi . Parameter (
' lang ' ,
openapi . IN_QUERY ,
description = ' 返回语言(默认:zh_cn) ' ,
type = openapi . TYPE_STRING ,
required = False ,
enum = [ ' zh_cn ' , ' en ' ]
) ,
] ,
responses = { 200 : success_response , 400 : error_response , 404 : error_response , 502 : error_response }
)
def get ( self , request ) :
city = request . GET . get ( ' city ' , ' ' ) . strip ( )
unit = request . GET . get ( ' unit ' , ' celsius ' ) . strip ( )
lang = request . GET . get ( ' lang ' , ' zh_cn ' ) . strip ( )
if not city :
return Response (
{ " code " : 400 , " message " : " 参数错误:city不能为空 " , " data " : None } ,
status = status . HTTP_400_BAD_REQUEST
)
if unit not in [ ' celsius ' , ' fahrenheit ' ] :
return Response (
{ " code " : 400 , " message " : " 参数错误:unit必须是 celsius 或 fahrenheit " , " data " : None } ,
status = status . HTTP_400_BAD_REQUEST
)
return self . _fetch_weather ( city , unit , lang )
@swagger_auto_schema (
tags = [ ' Weather ' ] ,
operation_summary = ' 获取天气信息(POST方式) ' ,
operation_description = ' 通过城市名称获取实时天气信息,支持温度单位和语言设置 ' ,
request_body = openapi . Schema (
type = openapi . TYPE_OBJECT ,
required = [ ' city ' ] ,
properties = {
' city ' : openapi . Schema ( type = openapi . TYPE_STRING , description = ' 城市名称(支持中文、英文) ' ) ,
' unit ' : openapi . Schema ( type = openapi . TYPE_STRING , description = ' 温度单位:celsius(默认)、fahrenheit ' , enum = [ ' celsius ' , ' fahrenheit ' ] ) ,
' lang ' : openapi . Schema ( type = openapi . TYPE_STRING , description = ' 返回语言(默认:zh_cn) ' , enum = [ ' zh_cn ' , ' en ' ] ) ,
}
) ,
responses = { 200 : success_response , 400 : error_response , 404 : error_response , 502 : error_response }
)
def post ( self , request ) :
city = request . data . get ( ' city ' , ' ' ) . strip ( ) if isinstance ( request . data , dict ) else ' '
unit = request . data . get ( ' unit ' , ' celsius ' ) . strip ( ) if isinstance ( request . data , dict ) else ' celsius '
lang = request . data . get ( ' lang ' , ' zh_cn ' ) . strip ( ) if isinstance ( request . data , dict ) else ' zh_cn '
if not city :
return Response (
{ " code " : 400 , " message " : " 参数错误:city不能为空 " , " data " : None } ,
status = status . HTTP_400_BAD_REQUEST
)
if unit not in [ ' celsius ' , ' fahrenheit ' ] :
return Response (
{ " code " : 400 , " message " : " 参数错误:unit必须是 celsius 或 fahrenheit " , " data " : None } ,
status = status . HTTP_400_BAD_REQUEST
)
return self . _fetch_weather ( city , unit , lang )
def _fetch_weather ( self , city , unit , lang ) :
"""
调用 Open-Meteo API 获取天气数据
"""
try :
# 第一步:通过地理编码API获取城市坐标
geo_params = {
' name ' : city ,
' count ' : 1 ,
' language ' : ' zh ' if lang == ' zh_cn ' else ' en ' ,
' format ' : ' json '
}
geo_response = requests . get ( GEOCODING_URL , params = geo_params , timeout = 10 )
if geo_response . status_code != 200 :
return Response (
{ " code " : 502 , " message " : " 地理编码服务异常,请稍后重试 " , " data " : None } ,
status = status . HTTP_502_BAD_GATEWAY
)
geo_data = geo_response . json ( )
results = geo_data . get ( ' results ' , [ ] )
if not results :
return Response (
{ " code " : 404 , " message " : f " 未找到城市: { city } ,请检查城市名称是否正确 " , " data " : None } ,
status = status . HTTP_404_NOT_FOUND
)
location = results [ 0 ]
latitude = location . get ( ' latitude ' )
longitude = location . get ( ' longitude ' )
city_name = location . get ( ' name ' , city )
country = location . get ( ' country ' , ' ' )
# 第二步:获取天气数据
temp_unit = ' fahrenheit ' if unit == ' fahrenheit ' else ' celsius '
weather_params = {
' latitude ' : latitude ,
' longitude ' : longitude ,
' current ' : ' temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m,pressure_msl ' ,
' timezone ' : ' auto ' ,
' temperature_unit ' : temp_unit
}
weather_response = requests . get ( FORECAST_URL , params = weather_params , timeout = 10 )
if weather_response . status_code != 200 :
return Response (
{ " code " : 502 , " message " : " 天气服务异常,请稍后重试 " , " data " : None } ,
status = status . HTTP_502_BAD_GATEWAY
)
weather_data = weather_response . json ( )
current = weather_data . get ( ' current ' , { } )
weather_code = current . get ( ' weather_code ' , 0 )
# 构建响应
temp_unit_symbol = ' °C ' if unit == ' celsius ' else ' °F '
weather_info = {
' city ' : city_name ,
' country ' : country ,
' temperature ' : current . get ( ' temperature_2m ' , 0 ) ,
' temperature_unit ' : temp_unit_symbol ,
' humidity ' : current . get ( ' relative_humidity_2m ' , 0 ) ,
' weather ' : WEATHER_CODE_MAP . get ( weather_code , " 未知 " ) ,
' wind_speed ' : f " { current . get ( ' wind_speed_10m ' , 0 ) } km/h " ,
' pressure ' : f " { current . get ( ' pressure_msl ' , 0 ) } hPa " ,
' updated_at ' : datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' )
}
return Response (
{ " code " : 200 , " message " : " success " , " data " : weather_info } ,
status = status . HTTP_200_OK
)
except requests . exceptions . Timeout :
return Response (
{ " code " : 502 , " message " : " 请求超时,请稍后重试 " , " data " : None } ,
status = status . HTTP_502_BAD_GATEWAY
)
except requests . exceptions . ConnectionError :
return Response (
{ " code " : 502 , " message " : " 网络连接异常,请检查网络后重试 " , " data " : None } ,
status = status . HTTP_502_BAD_GATEWAY
)
except Exception as e :
return Response (
{ " code " : 500 , " message " : f " 服务器内部错误: { str ( e ) } " , " data " : None } ,
status = status . HTTP_500_INTERNAL_SERVER_ERROR
)