@@ -0,0 +1,188 @@
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 "
AIR_QUALITY_URL = " https://air-quality-api.open-meteo.com/v1/air-quality "
def get_air_quality_level ( pm2_5 ) :
"""
根据PM2.5浓度获取空气质量等级
"""
if pm2_5 < = 10 :
return " 优 "
elif pm2_5 < = 25 :
return " 良 "
elif pm2_5 < = 50 :
return " 轻度污染 "
elif pm2_5 < = 75 :
return " 中度污染 "
else :
return " 重度污染 "
class AirQualityView ( APIView ) :
"""
空气质量查询视图 - 基于 Open-Meteo Air Quality API(免费,无需API Key)
"""
permission_classes = [ AllowAny ]
@swagger_auto_schema (
tags = [ ' Air Quality ' ] ,
operation_summary = ' 获取空气质量信息(GET方式) ' ,
operation_description = ' 通过城市名称获取实时空气质量信息,支持PM2.5、PM10、臭氧、二氧化氮等指标 ' ,
manual_parameters = [
openapi . Parameter (
' city ' ,
openapi . IN_QUERY ,
description = ' 城市名称(支持中文、英文,如:北京、Beijing、上海、Shanghai) ' ,
type = openapi . TYPE_STRING ,
required = True
) ,
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 ( )
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
)
return self . _fetch_air_quality ( city , lang )
@swagger_auto_schema (
tags = [ ' Air Quality ' ] ,
operation_summary = ' 获取空气质量信息(POST方式) ' ,
operation_description = ' 通过城市名称获取实时空气质量信息,支持PM2.5、PM10、臭氧、二氧化氮等指标 ' ,
request_body = openapi . Schema (
type = openapi . TYPE_OBJECT ,
required = [ ' city ' ] ,
properties = {
' city ' : openapi . Schema ( type = openapi . TYPE_STRING , description = ' 城市名称(支持中文、英文) ' ) ,
' 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 ' '
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
)
return self . _fetch_air_quality ( city , lang )
def _fetch_air_quality ( self , city , lang ) :
"""
调用 Open-Meteo Air Quality 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 ' , ' ' )
# 第二步:获取空气质量数据
air_quality_params = {
' latitude ' : latitude ,
' longitude ' : longitude ,
' current ' : ' pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,sulphur_dioxide,ozone,us_aqi ' ,
' timezone ' : ' auto '
}
aq_response = requests . get ( AIR_QUALITY_URL , params = air_quality_params , timeout = 10 )
if aq_response . status_code != 200 :
return Response (
{ " code " : 502 , " message " : " 空气质量服务异常,请稍后重试 " , " data " : None } ,
status = status . HTTP_502_BAD_GATEWAY
)
aq_data = aq_response . json ( )
current = aq_data . get ( ' current ' , { } )
pm2_5 = current . get ( ' pm2_5 ' , 0 )
# 构建响应
air_quality_info = {
' city ' : city_name ,
' country ' : country ,
' pm2_5 ' : pm2_5 ,
' pm10 ' : current . get ( ' pm10 ' , 0 ) ,
' ozone ' : current . get ( ' ozone ' , 0 ) ,
' nitrogen_dioxide ' : current . get ( ' nitrogen_dioxide ' , 0 ) ,
' sulphur_dioxide ' : current . get ( ' sulphur_dioxide ' , 0 ) ,
' carbon_monoxide ' : current . get ( ' carbon_monoxide ' , 0 ) ,
' air_quality_level ' : get_air_quality_level ( pm2_5 ) ,
' updated_at ' : datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' )
}
return Response (
{ " code " : 200 , " message " : " success " , " data " : air_quality_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
)