232 lines
7.0 KiB
Python
232 lines
7.0 KiB
Python
"""
|
||
IP轮换服务模块
|
||
|
||
提供IP地址轮换功能,包括:
|
||
- 从可用设备中选择未使用的IP
|
||
- 配置网关路由规则
|
||
- 执行IP轮换操作
|
||
- 查询当前状态和统计信息
|
||
|
||
使用Redis存储使用记录,支持按天统计IP使用情况。
|
||
"""
|
||
import random
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
from .config import settings, city_dict, client_infos, port_mapping
|
||
from .eip_client import client_singleton as eip
|
||
from .redis_store import store_singleton as kv
|
||
from .line_status_manager import status_manager
|
||
|
||
|
||
def _extract_device_ip_and_id(device: Dict[str, Any]) -> Tuple[Optional[str], Optional[str]]:
|
||
"""
|
||
从设备信息中提取IP地址和边缘设备ID
|
||
|
||
由于后端返回结构可能不一致,使用多种字段名进行容错提取:
|
||
- IP字段:ip, public_ip, eip, addr, address
|
||
- ID字段:id, edge, mac, device_id
|
||
|
||
Args:
|
||
device: 设备信息字典
|
||
|
||
Returns:
|
||
Tuple[Optional[str], Optional[str]]: (IP地址, 边缘设备ID)
|
||
"""
|
||
# 尝试多种可能的IP字段名
|
||
ip = (
|
||
device.get("public")
|
||
)
|
||
|
||
macaddr = device.get("macaddr")
|
||
|
||
|
||
return (str(ip), str(macaddr))
|
||
|
||
|
||
def select_unused_ip(devices: List[Dict[str, Any]]) -> Tuple[Optional[str], Optional[str]]:
|
||
"""
|
||
从设备列表中选择今天未使用的IP地址
|
||
|
||
遍历设备列表,找到第一个今天未使用的IP地址和对应的边缘设备ID
|
||
|
||
Args:
|
||
devices: 设备信息列表
|
||
|
||
Returns:
|
||
Tuple[Optional[str], Optional[str]]: (IP地址, 边缘设备ID),如果没找到则返回(None, None)
|
||
"""
|
||
for d in devices.get('edges'):
|
||
ip, edge_id = _extract_device_ip_and_id(d)
|
||
if not ip:
|
||
# 没有可识别的IP,跳过此设备
|
||
continue
|
||
if not kv.is_ip_used_today(ip):
|
||
return ip, edge_id
|
||
return None, None
|
||
|
||
|
||
def apply_gateway_route(edge_id: Optional[str], ip: str, geo: str, client_id:str) -> Dict[str, Any]:
|
||
"""
|
||
将选中的边缘设备配置到网关路由规则中
|
||
|
||
创建路由规则并应用到指定的网关设备,实现IP轮换
|
||
|
||
Args:
|
||
edge_id: 边缘设备ID
|
||
ip: IP地址(用于日志记录,实际路由基于edge_id)
|
||
|
||
Returns:
|
||
Dict[str, Any]: 网关配置设置结果
|
||
"""
|
||
lines = status_manager.get_all_lines_status()
|
||
|
||
rule = []
|
||
|
||
for line in lines:
|
||
if line['id'] == client_id:
|
||
rule.append({
|
||
"table": client_id, # 路由表ID
|
||
"enable": True, # 启用规则
|
||
"edge": [edge_id] if edge_id else [], # 边缘设备列表
|
||
"network": [client_infos[str(client_id)]], # 网络配置(当前为空)
|
||
"cityhash": geo or "", # 城市哈希值
|
||
})
|
||
else:
|
||
rule.append({
|
||
"table": line['id'], # 路由表ID
|
||
"enable": True, # 启用规则
|
||
"edge": [line['edge_device']], # 边缘设备列表
|
||
"network": [client_infos[str(line['id'])]], # 网络配置(当前为空)
|
||
"cityhash": line['geo_location'], # 城市哈希值
|
||
})
|
||
|
||
config = {"id": 1, "rules": rule} # 配置ID和规则列表
|
||
return eip.gateway_config_set(settings.eip_gateway_mac, config)
|
||
|
||
|
||
|
||
|
||
def rotate(client_id,cities) -> Dict[str, Any]:
|
||
"""
|
||
执行IP轮换操作
|
||
|
||
从指定地理位置获取设备列表,选择未使用的IP进行轮换
|
||
|
||
Args:
|
||
cityhash: 城市哈希值,用于指定地理位置
|
||
num: 获取设备数量,默认为配置值或10
|
||
|
||
Returns:
|
||
Dict[str, Any]: 轮换结果
|
||
- changed: bool - 是否成功轮换
|
||
- reason: str - 失败原因(当changed=False时)
|
||
- ip: str - 新的IP地址(当changed=True时)
|
||
- edge: str - 边缘设备ID(当changed=True时)
|
||
- status: dict - 网关状态信息(当changed=True时)
|
||
|
||
Raises:
|
||
ValueError: 当cityhash为空时
|
||
"""
|
||
# 确定地理位置参数
|
||
geo = get_random_cityhash(cities)
|
||
|
||
# 确定获取设备数量
|
||
n = 1000
|
||
|
||
# 获取设备列表
|
||
devices = eip.list_devices(geo=geo, offset=0, num=n)
|
||
|
||
# 选择未使用的IP
|
||
ip, edge_id = select_unused_ip(devices)
|
||
if not ip:
|
||
return {"changed": False, "reason": "没有可用且今天未使用的 IP"}
|
||
|
||
# 应用网关路由配置
|
||
_ = apply_gateway_route(edge_id=edge_id, ip=ip, geo= geo,client_id=client_id)
|
||
|
||
# 记录使用情况
|
||
kv.add_used_ip_today(ip) # 标记IP为今天已使用
|
||
kv.set_current(ip=ip, edge_id=edge_id) # 设置当前使用的IP和设备
|
||
|
||
# 获取网关状态
|
||
status = eip.gateway_status(settings.eip_gateway_mac)
|
||
|
||
return {"changed": True, "ip": ip, "edge": edge_id, "status": status, "geo": geo}
|
||
|
||
|
||
def citie_list():
|
||
return {"data":list(city_dict.keys())}
|
||
|
||
def status() -> Dict[str, Any]:
|
||
"""
|
||
获取当前轮换服务状态
|
||
|
||
返回当前使用的IP、今日使用统计和网关状态信息
|
||
|
||
Returns:
|
||
Dict[str, Any]: 状态信息
|
||
- current: dict - 当前使用的IP和设备信息
|
||
- used_today: int - 今日已使用的IP数量
|
||
- gateway: dict - 网关状态信息(如果获取失败则为空字典)
|
||
"""
|
||
# 获取当前使用的IP和设备信息
|
||
cur = kv.get_current()
|
||
|
||
# 获取今日使用统计
|
||
used_count = kv.get_used_count_today()
|
||
|
||
# 获取网关状态(容错处理)
|
||
gw = {}
|
||
try:
|
||
gw = eip.gateway_status(settings.eip_gateway_mac)
|
||
except Exception:
|
||
# 网关状态获取失败时使用空字典
|
||
pass
|
||
|
||
return {"current": cur, "used_today": used_count, "gateway": gw}
|
||
|
||
|
||
def get_random_city(city_list) -> Dict[str, str]:
|
||
"""
|
||
随机获取一个城市编码
|
||
|
||
从所有可用的城市中随机选择一个,返回城市名称和对应的编码
|
||
|
||
Returns:
|
||
Dict[str, str]: 包含城市信息的字典
|
||
- province: 省份名称
|
||
- city: 城市名称
|
||
- cityhash: 城市编码
|
||
"""
|
||
# 收集所有城市信息
|
||
all_cities = []
|
||
for province, cities in city_dict.items():
|
||
if province in city_list:
|
||
for city, cityhash in cities.items():
|
||
all_cities.append({
|
||
"province": province,
|
||
"city": city,
|
||
"cityhash": cityhash
|
||
})
|
||
|
||
# 随机选择一个城市
|
||
if not all_cities:
|
||
raise ValueError("没有可用的城市数据")
|
||
|
||
selected_city = random.choice(all_cities)
|
||
return selected_city
|
||
|
||
|
||
def get_random_cityhash(cities) -> str:
|
||
"""
|
||
随机获取一个城市编码(简化版本)
|
||
|
||
Returns:
|
||
str: 随机城市编码
|
||
"""
|
||
city_info = get_random_city(cities)
|
||
return city_info["cityhash"]
|
||
|
||
def get_port(line_id):
|
||
return port_mapping[line_id]
|