Become a sponsor

说明
提供客户端 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 │
└──────────────────────┘# 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 等反向代理转发请求。
通过第三方 API 获取 IP 地理位置:
# 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.8 | United States California |
10.0.0.1 | 内网IP |
172.abc | 未知 |
在 config/env.py 中配置 IP 查询相关参数:
# 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",不查询 API:
| 类型 | 匹配规则 | 示例 |
|---|---|---|
| 回环地址 | 127.0.0.1、localhost | 127.0.0.1 |
| C 类私有 | 192.168.x.x | 192.168.1.1 |
| A 类私有 | 10.x.x.x | 10.0.0.1 |
通过正则匹配从 User-Agent 字符串提取操作系统和浏览器信息:
# utils/common.py
def parse_user_agent(user_agent: str) -> dict:
"""
解析User-Agent,获取操作系统和浏览器信息
Returns:
dict: {'os': '操作系统', 'browser': '浏览器'}
"""使用示例:
ua = request.META.get('HTTP_USER_AGENT', '')
info = parse_user_agent(ua)
print(f"操作系统: {info['os']}, 浏览器: {info['browser']}")| 操作系统 | UA 关键字 | 输出 |
|---|---|---|
| Windows 10 | Windows NT 10.0 | Windows 10 |
| Windows 7 | Windows NT 6.1 | Windows 7 |
| macOS | Mac OS X | macOS |
| Linux | Linux | Linux |
| Android | Android | Android |
| iOS | iPhone / iPad | iOS |
按匹配优先级排列(Edge 优先于 Chrome):
| 浏览器 | UA 关键字 | 输出 |
|---|---|---|
| Edge | Edg | Edge |
| Chrome | Chrome | Chrome |
| Safari | Safari | Safari |
| Firefox | Firefox | Firefox |
| IE | MSIE / Trident | IE |
登录/退出时自动记录 IP、地理位置、操作系统、浏览器:
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 和设备信息。
原因:本地直连无代理,REMOTE_ADDR 就是 127.0.0.1。
说明:这是正常行为。生产环境配置 Nginx 反向代理后即可获取真实客户端 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': '未知'} |
三者组合为登录日志、操作日志等场景提供完整的客户端身份信息链路。所有接口均采用降级策略,保证日志落库等核心流程不受异常影响。