Skip to content

Commit 7ca8ba9

Browse files
committed
好的
feat(geoip): 简化地理位置信息获取逻辑并优化数据解析 - 删除冗余的多个位置解析方法和映射逻辑,简化为单一 IP 查询实现 - 更新新的 IP 接口地址并调整相关超时和错误处理策略 - 丰富地理位置信息字段,新增 ISP 和 IP 属性支持
1 parent cdfa49d commit 7ca8ba9

2 files changed

Lines changed: 38 additions & 257 deletions

File tree

app/tools/config.py

Lines changed: 37 additions & 256 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import platform
1111
import sys
1212
import time
13-
from functools import lru_cache
1413
import psutil
1514
import requests
1615

@@ -164,279 +163,61 @@ def track_event(event_name: str):
164163
)
165164

166165

167-
def get_geoip_properties_zh_cn(timeout_seconds: float = 1.5) -> dict:
168-
providers = (
169-
("系统位置信息", _get_system_location_properties_zh_cn),
170-
("IP33 位置信息", _get_ip33_location_properties),
171-
)
172-
for provider_name, provider in providers:
173-
properties = provider(timeout_seconds=timeout_seconds)
174-
if properties:
175-
logger.debug(f"获取到{provider_name}: {properties}")
176-
return properties
177-
return {}
178-
179-
180-
_GEOIP_COUNTRY_KEY = "$geoip_country_name"
181-
_GEOIP_CITY_KEY = "$geoip_city_name"
182-
_COUNTRY_NAME_ZH_CN_MAP = {
183-
"China": "中国",
184-
"People's Republic of China": "中国",
185-
"PRC": "中国",
186-
"中华人民共和国": "中国",
187-
}
188-
_COUNTRY_REGION_ZH_CN_MAP = {
189-
"CN": "中国",
190-
"HK": "中国香港",
191-
"MO": "中国澳门",
192-
"TW": "中国台湾",
193-
}
194-
_IP33_AREA_CITY_RE = re.compile(
195-
r"^(?P<prefix>.+?(?:省|自治区|特别行政区|市))(?P<rest>.*)$"
196-
)
197-
198-
199-
def _build_geoip_properties(country: str = "", city: str = "") -> dict:
200-
country = (country or "").strip()
201-
city = (city or "").strip()
202-
203-
country = _COUNTRY_NAME_ZH_CN_MAP.get(country, country)
204-
205-
properties = {}
206-
if country:
207-
properties[_GEOIP_COUNTRY_KEY] = country
208-
if city:
209-
properties[_GEOIP_CITY_KEY] = city
210-
return properties
211-
212-
213-
@lru_cache(maxsize=1)
214-
def _map_country_region_to_zh_cn(country_region: str) -> str:
215-
country_region = (country_region or "").strip().upper()
216-
if not country_region:
217-
return ""
218-
219-
return _COUNTRY_REGION_ZH_CN_MAP.get(country_region, country_region)
220-
221-
222-
def _get_windows_geo_country_region() -> tuple[str, str]:
223-
try:
224-
import ctypes
225-
from ctypes import wintypes
226-
227-
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
228-
229-
GEOCLASS_NATION = 16
230-
GEOID_NOT_AVAILABLE = 0
231-
GEO_ISO2 = 4
232-
GEO_FRIENDLYNAME = 8
233-
234-
kernel32.GetUserGeoID.argtypes = [wintypes.DWORD]
235-
kernel32.GetUserGeoID.restype = wintypes.INT
236-
kernel32.GetGeoInfoW.argtypes = [
237-
wintypes.INT,
238-
wintypes.DWORD,
239-
wintypes.LPWSTR,
240-
wintypes.INT,
241-
wintypes.LANGID,
242-
]
243-
kernel32.GetGeoInfoW.restype = wintypes.INT
244-
245-
geo_id = int(kernel32.GetUserGeoID(GEOCLASS_NATION))
246-
if geo_id in (GEOID_NOT_AVAILABLE, -1):
247-
return "", ""
248-
249-
def _get_geoinfo(geo_type: int) -> str:
250-
buf = ctypes.create_unicode_buffer(256)
251-
size = int(kernel32.GetGeoInfoW(geo_id, geo_type, buf, len(buf), 0))
252-
if size <= 0:
253-
return ""
254-
return (buf.value or "").strip()
255-
256-
iso2 = _get_geoinfo(GEO_ISO2).upper()
257-
friendly = _get_geoinfo(GEO_FRIENDLYNAME)
258-
return iso2, friendly
259-
except Exception:
260-
return "", ""
261-
166+
_GEOIP_COUNTRY_NAME_KEY = "$geoip_country_name"
167+
_GEOIP_SUBDIVISION_1_NAME_KEY = "$geoip_subdivision_1_name"
168+
_GEOIP_CITY_NAME_KEY = "$geoip_city_name"
169+
_GEOIP_IP_KEY = "$geoip_ip"
170+
_GEOIP_ISP_KEY = "$geoip_isp"
262171

263-
def _get_windows_location_properties_via_winrt_zh_cn(timeout_seconds: float) -> dict:
264-
try:
265-
import concurrent.futures
266-
import asyncio
267-
268-
import winrt.windows.devices.geolocation as geolocation
269-
except Exception:
270-
return {}
271172

173+
def get_geoip_properties_zh_cn(timeout_seconds: float = 1.5) -> dict:
174+
headers = {"User-Agent": f"SecRandom/{SPECIAL_VERSION}"}
272175
timeout_seconds = max(0.2, float(timeout_seconds or 0))
273176

274-
def _worker() -> dict:
275-
async def _get() -> dict:
276-
locator = geolocation.Geolocator()
277-
position = await asyncio.wait_for(
278-
locator.get_geoposition_async(), timeout_seconds
279-
)
280-
address = getattr(position, "civic_address", None)
281-
if address is None:
282-
return {}
283-
284-
city = (getattr(address, "city", "") or "").strip()
285-
state_province = (getattr(address, "state", "") or "").strip()
286-
if not state_province:
287-
state_province = (getattr(address, "state_province", "") or "").strip()
288-
289-
country_region = (
290-
(getattr(address, "country_code", "") or "").strip().upper()
291-
)
292-
country = (getattr(address, "country", "") or "").strip()
293-
294-
if not country and country_region:
295-
country = _map_country_region_to_zh_cn(country_region)
296-
else:
297-
country = _COUNTRY_NAME_ZH_CN_MAP.get(country, country)
298-
299-
if not city:
300-
city = state_province
301-
302-
return _build_geoip_properties(country=country, city=city)
303-
304-
return asyncio.run(_get())
305-
306-
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
307-
future = executor.submit(_worker)
308-
try:
309-
return future.result(timeout=timeout_seconds + 0.3)
310-
except Exception:
311-
return {}
312-
313-
314-
def _get_windows_timezone_key_name() -> str:
315177
try:
316-
import ctypes
317-
from ctypes import wintypes
318-
319-
class _SYSTEMTIME(ctypes.Structure):
320-
_fields_ = [
321-
("wYear", wintypes.WORD),
322-
("wMonth", wintypes.WORD),
323-
("wDayOfWeek", wintypes.WORD),
324-
("wDay", wintypes.WORD),
325-
("wHour", wintypes.WORD),
326-
("wMinute", wintypes.WORD),
327-
("wSecond", wintypes.WORD),
328-
("wMilliseconds", wintypes.WORD),
329-
]
330-
331-
class _TIME_DYNAMIC_ZONE_INFORMATION(ctypes.Structure):
332-
_fields_ = [
333-
("Bias", wintypes.LONG),
334-
("StandardName", wintypes.WCHAR * 32),
335-
("StandardDate", _SYSTEMTIME),
336-
("StandardBias", wintypes.LONG),
337-
("DaylightName", wintypes.WCHAR * 32),
338-
("DaylightDate", _SYSTEMTIME),
339-
("DaylightBias", wintypes.LONG),
340-
("TimeZoneKeyName", wintypes.WCHAR * 128),
341-
("DynamicDaylightTimeDisabled", wintypes.BOOL),
342-
]
343-
344-
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
345-
kernel32.GetDynamicTimeZoneInformation.argtypes = [
346-
ctypes.POINTER(_TIME_DYNAMIC_ZONE_INFORMATION)
347-
]
348-
kernel32.GetDynamicTimeZoneInformation.restype = wintypes.DWORD
349-
350-
info = _TIME_DYNAMIC_ZONE_INFORMATION()
351-
kernel32.GetDynamicTimeZoneInformation(ctypes.byref(info))
352-
return (info.TimeZoneKeyName or "").strip()
178+
response = requests.get(
179+
"https://ip9.com.cn/get",
180+
headers=headers,
181+
timeout=timeout_seconds,
182+
)
183+
response.raise_for_status()
184+
data = response.json()
353185
except Exception:
354-
return ""
355-
356-
357-
def _guess_city_from_timezone(country_region: str, tz_key_name: str) -> str:
358-
country_region = (country_region or "").strip().upper()
359-
tz_key_name = (tz_key_name or "").strip()
360-
if not country_region or not tz_key_name:
361-
return ""
362-
363-
if country_region == "TW" and tz_key_name == "Taipei Standard Time":
364-
return "台北"
365-
366-
if country_region in {"CN", "HK", "MO"} and tz_key_name == "China Standard Time":
367-
return "北京"
368-
369-
return ""
370-
371-
372-
def _get_system_location_properties_zh_cn(timeout_seconds: float = 1.5) -> dict:
373-
if sys.platform != "win32":
374186
return {}
375187

376-
properties = _get_windows_location_properties_via_winrt_zh_cn(
377-
timeout_seconds=timeout_seconds
378-
)
379-
if properties:
380-
return properties
381-
382-
country_region, friendly = _get_windows_geo_country_region()
383-
if not country_region and not friendly:
188+
if not isinstance(data, dict) or data.get("ret") != 200:
384189
return {}
385190

386-
country = ()
387-
if not country:
388-
country = friendly
389-
390-
city = ""
391-
if country_region:
392-
city = _guess_city_from_timezone(
393-
country_region, _get_windows_timezone_key_name()
394-
)
395-
396-
return _build_geoip_properties(country=country, city=city)
397-
191+
geo_data = data.get("data")
192+
if not isinstance(geo_data, dict):
193+
return {}
398194

399-
def _get_ip33_location_properties(timeout_seconds: float = 1.5) -> dict:
400-
headers = {"User-Agent": f"SecRandom/{SPECIAL_VERSION}"}
195+
properties = {}
401196

402-
timeout_seconds = max(0.2, float(timeout_seconds or 0))
403-
for url in ("https://api.ip33.com/ip/search", "http://api.ip33.com/ip/search"):
404-
try:
405-
response = requests.get(url, headers=headers, timeout=timeout_seconds)
406-
response.raise_for_status()
407-
data = response.json()
408-
properties = _parse_ip33_response_to_properties(data)
409-
if properties:
410-
return properties
411-
except Exception:
412-
continue
413-
return {}
197+
country = (geo_data.get("country") or "").strip()
198+
if country:
199+
properties[_GEOIP_COUNTRY_NAME_KEY] = country
414200

201+
prov = (geo_data.get("prov") or "").strip()
202+
if prov:
203+
properties[_GEOIP_SUBDIVISION_1_NAME_KEY] = prov
415204

416-
def _parse_ip33_response_to_properties(data: dict) -> dict:
417-
if not isinstance(data, dict):
418-
return {}
205+
city = (geo_data.get("city") or "").strip()
206+
if city:
207+
properties[_GEOIP_CITY_NAME_KEY] = city
419208

420-
area = (data.get("area") or "").strip()
421-
if not area:
422-
return {}
209+
ip = (geo_data.get("ip") or "").strip()
210+
if ip:
211+
properties[_GEOIP_IP_KEY] = ip
423212

424-
area_main = area.split()[0].strip()
425-
if not area_main:
426-
return {}
213+
isp = (geo_data.get("isp") or "").strip()
214+
if isp:
215+
properties[_GEOIP_ISP_KEY] = isp
427216

428-
city = ""
429-
m = _IP33_AREA_CITY_RE.match(area_main)
430-
if m:
431-
rest = (m.group("rest") or "").strip()
432-
if rest:
433-
city = rest
434-
else:
435-
city = (m.group("prefix") or "").strip()
436-
else:
437-
city = area_main
217+
if properties:
218+
logger.debug(f"获取到地理位置信息: {properties}")
438219

439-
return _build_geoip_properties(country="中国", city=city)
220+
return properties
440221

441222

442223
# ==================== 通知模块 ====================

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def initialize_posthog():
9393
distinct_id=user_id,
9494
event="app_started",
9595
properties={
96-
"location": geoip_properties,
96+
**geoip_properties,
9797
"$set": {
9898
"total_draw_count": total_draw_count,
9999
"roll_call_total_count": roll_call_total,

0 commit comments

Comments
 (0)