Skip to content

IP/UA 解析

说明

提供客户端 IP 获取、IP 地理位置查询和 User-Agent 解析三大能力,位于 utils/common.py。三者组合使用,为登录日志、操作日志等场景提供客户端身份信息。

架构总览

请求进入


┌──────────────────────────────────────────────────────┐
│  get_client_ip(request)                              │
│  ├─ HTTP_X_FORWARDED_FOR 存在?                      │
│  │   └─ 是 → 取第一个 IP(客户端真实 IP)             │
│  └─ 否 → REMOTE_ADDR(直接连接 IP)                  │
└──────────┬───────────────────────────────────────────┘

     ┌─────┴─────┐
     ▼           ▼
┌─────────┐  ┌──────────────────────────────────┐
│ 日志记录 │  │ get_ip_location(ip)              │
│ 操作日志 │  │ ├─ 内网 IP → "内网IP"             │
└─────────┘  │ ├─ ip-api.com 在线查询            │
             │ └─ 异常降级 → "未知"               │
             └──────────┬───────────────────────┘


             ┌──────────────────────┐
             │ parse_user_agent(ua) │
             │ ├─ 操作系统(正则)   │
             │ └─ 浏览器(正则)     │
             └──────────┬───────────┘


             ┌──────────────────────┐
             │ 写入日志表            │
             │ ip / location /      │
             │ os / browser         │
             └──────────────────────┘

一、IP 获取

get_client_ip(request)

python
# utils/common.py
def get_client_ip(request) -> str:
    """
    获取客户端真实IP地址

    优先从 HTTP_X_FORWARDED_FOR 获取,其次使用 REMOTE_ADDR。
    """
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

返回值:客户端 IP 地址字符串(如 "1.2.3.4")。

代理环境说明

当应用部署在 Nginx 等反向代理之后,REMOTE_ADDR 拿到的是代理 IP 而非真实客户端 IP。此时需要从 X-Forwarded-For(XFF)头提取真实 IP。

客户端 (1.2.3.4)


Nginx 代理 (10.0.0.1)     ← 追加 XFF: 1.2.3.4


Django 应用
    REMOTE_ADDR = 10.0.0.1
    HTTP_X_FORWARDED_FOR = "1.2.3.4"

安全提示

如果应用被直接暴露在公网,客户端可伪造 XFF 首部。生产环境务必通过 Nginx 等反向代理转发请求。

二、IP 地理位置查询

get_ip_location(ip)

通过第三方 API 获取 IP 地理位置:

python
# utils/common.py
def get_ip_location(ip: str) -> str:
    """
    根据IP获取地理位置

    内网IP直接返回"内网IP",调用 ip-api.com API 获取地理位置。
    """
    # 内网IP判断
    if ip in ['127.0.0.1', 'localhost'] or ip.startswith('192.168.') or ip.startswith('10.'):
        return "内网IP"

    try:
        url = f"http://ip-api.com/json/{ip}?lang=zh-CN"
        with urllib.request.urlopen(url, timeout=3) as response:
            data = json.loads(response.read().decode('utf-8'))
            if data.get('status') == 'success':
                return f"{data.get('country', '')} {data.get('regionName', '')} {data.get('city', '')}"
    except Exception as e:
        logger.warning(f"获取IP地理位置失败: {e}")

    return "未知"

返回值示例

IP返回值
114.114.114.114中国 江苏省 南京市
8.8.8.8United States California
10.0.0.1内网IP
172.abc未知

IP 地理位置配置

config/env.py 中配置 IP 查询相关参数:

python
# IP地理位置信息来源: 'local' 或 'api'
IP_LOCATION_SOURCE = os.environ.get('IP_LOCATION_SOURCE', 'api')

# 本地IP数据库文件路径
IP_DATABASE_PATH = os.environ.get('IP_DATABASE_PATH', ...)

# IP查询API超时时间(秒)
IP_API_TIMEOUT = int(os.environ.get('IP_API_TIMEOUT', 5))

# 是否启用IP地理位置缓存
IP_CACHE_ENABLED = os.environ.get('IP_CACHE_ENABLED', 'True') == 'True'

# IP缓存时间(秒),默认1天
IP_CACHE_TIMEOUT = int(os.environ.get('IP_CACHE_TIMEOUT', 86400))

内网 IP 判断

内网/保留 IP 直接返回 "内网IP",不查询 API:

类型匹配规则示例
回环地址127.0.0.1localhost127.0.0.1
C 类私有192.168.x.x192.168.1.1
A 类私有10.x.x.x10.0.0.1

三、UA 解析

parse_user_agent(user_agent)

通过正则匹配从 User-Agent 字符串提取操作系统和浏览器信息:

python
# utils/common.py
def parse_user_agent(user_agent: str) -> dict:
    """
    解析User-Agent,获取操作系统和浏览器信息

    Returns:
        dict: {'os': '操作系统', 'browser': '浏览器'}
    """

使用示例

python
ua = request.META.get('HTTP_USER_AGENT', '')
info = parse_user_agent(ua)
print(f"操作系统: {info['os']}, 浏览器: {info['browser']}")

支持的操作系统

操作系统UA 关键字输出
Windows 10Windows NT 10.0Windows 10
Windows 7Windows NT 6.1Windows 7
macOSMac OS XmacOS
LinuxLinuxLinux
AndroidAndroidAndroid
iOSiPhone / iPadiOS

支持的浏览器

按匹配优先级排列(Edge 优先于 Chrome):

浏览器UA 关键字输出
EdgeEdgEdge
ChromeChromeChrome
SafariSafariSafari
FirefoxFirefoxFirefox
IEMSIE / TridentIE

四、应用场景

登录日志

登录/退出时自动记录 IP、地理位置、操作系统、浏览器:

python
ip = get_client_ip(request)
location = get_ip_location(ip)
ua = request.META.get('HTTP_USER_AGENT', '')
info = parse_user_agent(ua)

操作日志

写操作(add/update/delete/status)通过 @operation_log 装饰器自动记录操作者 IP 和设备信息。

五、常见问题

开发环境日志中 IP 显示为 127.0.0.1

原因:本地直连无代理,REMOTE_ADDR 就是 127.0.0.1

说明:这是正常行为。生产环境配置 Nginx 反向代理后即可获取真实客户端 IP。

地理位置显示为"未知"

可能原因

  1. ip-api.com API 请求超时(免费版限制 45 次/分钟)
  2. 网络连接问题
  3. 畸形 IP 地址

解决:生产环境建议使用本地 IP 数据库(如纯真 IP 库),配置 IP_LOCATION_SOURCE=local

六、总结

模块文件核心能力降级策略
IP 获取utils/common.py从 Request 提取真实客户端 IP优先 XFF,回退 REMOTE_ADDR
地理位置utils/common.py基于 ip-api.com 在线查询异常/内网统一降级返回 "未知""内网IP"
UA 解析utils/common.py正则匹配提取操作系统和浏览器UA 为空时返回 {'os': '未知', 'browser': '未知'}

三者组合为登录日志、操作日志等场景提供完整的客户端身份信息链路。所有接口均采用降级策略,保证日志落库等核心流程不受异常影响。

小蚂蚁云团队 · 提供技术支持