import requests 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 django.core.cache import caches default_cache = caches['default'] IP_API_URL = "http://ip-api.com/json/{ip}?lang=zh-CN" IP_CACHE_TIMEOUT = 3600 # 1小时 REQUEST_TIMEOUT = 5 # 5秒超时 class GetIPDataView(APIView): """ IP定位视图 - 基于 ip-api.com 免费地理定位服务 """ permission_classes = [AllowAny] @swagger_auto_schema( tags=['API'], operation_summary='获取IP地理信息', operation_description='通过IP地址获取地理定位信息,支持指定IP或自动获取客户端IP', manual_parameters=[ openapi.Parameter( 'ip', openapi.IN_QUERY, description='要查询的IP地址(可选,不传则自动获取客户端IP)', type=openapi.TYPE_STRING, required=False ), ], responses={ 200: openapi.Response( description="Success", schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'code': openapi.Schema(type=openapi.TYPE_INTEGER, example=200), 'message': openapi.Schema(type=openapi.TYPE_STRING, example='success'), 'data': openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'ip': openapi.Schema(type=openapi.TYPE_STRING, description='IP地址'), 'country': openapi.Schema(type=openapi.TYPE_STRING, description='国家'), 'regionName': openapi.Schema(type=openapi.TYPE_STRING, description='省份/地区'), 'city': openapi.Schema(type=openapi.TYPE_STRING, description='城市'), 'isp': openapi.Schema(type=openapi.TYPE_STRING, description='ISP服务商'), 'lat': openapi.Schema(type=openapi.TYPE_NUMBER, description='纬度'), 'lon': openapi.Schema(type=openapi.TYPE_NUMBER, description='经度'), 'timezone': openapi.Schema(type=openapi.TYPE_STRING, description='时区'), } ), } ) ), 400: openapi.Response(description="参数错误"), 502: openapi.Response(description="第三方服务异常"), } ) def get(self, request): # 获取要查询的IP地址 ip = request.GET.get('ip', '').strip() if not ip: ip = self._get_client_ip(request) if not ip: return Response( {"code": 400, "message": "无法获取IP地址", "data": None}, status=status.HTTP_400_BAD_REQUEST ) return self._fetch_ip_location(ip) def _get_client_ip(self, request): """ 从 request.META 中获取客户端真实IP地址 """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip x_real_ip = request.META.get('HTTP_X_REAL_IP') if x_real_ip: return x_real_ip.strip() remote_addr = request.META.get('REMOTE_ADDR') if remote_addr: return remote_addr.strip() return None def _fetch_ip_location(self, ip): """ 调用 ip-api.com 获取IP地理信息,支持Redis缓存 """ cache_key = f"ip_location:{ip}" # 先尝试从缓存获取 cached_data = default_cache.get(cache_key) if cached_data: return Response( {"code": 200, "message": "success", "data": cached_data}, status=status.HTTP_200_OK ) try: url = IP_API_URL.format(ip=ip) response = requests.get(url, timeout=REQUEST_TIMEOUT) if response.status_code != 200: return Response( {"code": 502, "message": "IP定位服务异常,请稍后重试", "data": None}, status=status.HTTP_502_BAD_GATEWAY ) result = response.json() if result.get('status') != 'success': return Response( {"code": 400, "message": f"IP查询失败:{result.get('message', '未知错误')}", "data": None}, status=status.HTTP_400_BAD_REQUEST ) ip_info = { 'ip': result.get('query', ip), 'country': result.get('country', ''), 'regionName': result.get('regionName', ''), 'city': result.get('city', ''), 'isp': result.get('isp', ''), 'lat': result.get('lat', 0), 'lon': result.get('lon', 0), 'timezone': result.get('timezone', ''), } # 写入缓存 default_cache.set(cache_key, ip_info, IP_CACHE_TIMEOUT) return Response( {"code": 200, "message": "success", "data": ip_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 )