common_util.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. """
  2. 通用工具类
  3. """
  4. import math
  5. import os
  6. import re
  7. import socket
  8. import subprocess
  9. import sys
  10. import telnetlib
  11. import threading
  12. import time
  13. import requests
  14. import uiautomator2
  15. import yaml
  16. from flask import json
  17. from tools import loggerKit
  18. from tools.device_status import DeviceStatus
  19. # 读取 YAML 文件
  20. with open('config.yaml', 'r') as file:
  21. config = yaml.load(file, Loader=yaml.FullLoader)
  22. # wework_package = config['wework']['package']
  23. # net_domain = 'http://api-qa.risingauto.net/'
  24. net_domain = config['bmp-cp']['net_domain']
  25. # extern_domain = 'https://api-qa.risingauto.com/'
  26. extern_domain = config['bmp-cp']['extern_domain']
  27. def get_ip_info(ip):
  28. response = requests.get('http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true' % (ip))
  29. if response.ok:
  30. print(response.text)
  31. data = json.loads(response.text)
  32. pro = data['pro']
  33. city = data['city']
  34. addr = data['addr']
  35. print('%s, %s, %s', pro, city, addr)
  36. def get_city(ip):
  37. response = requests.get('http://whois.pconline.com.cn/ipJson.jsp?ip=%s&json=true' % (ip))
  38. if response.ok:
  39. data = json.loads(response.text)
  40. city = data['city']
  41. return city
  42. else:
  43. return None
  44. """
  45. 获取本地IP
  46. """
  47. def get_local_ip():
  48. ip = None
  49. s = None
  50. try:
  51. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  52. s.connect(('8.8.8.8', 80))
  53. ip = s.getsockname()[0]
  54. finally:
  55. s.close()
  56. return ip
  57. # 获取连接设备
  58. def get_local_devices():
  59. # 执行 adb 命令并获取输出
  60. result = subprocess.run(['adb', 'devices'], capture_output=True, text=True)
  61. # 解析输出,获取已连接设备列表
  62. output_lines = result.stdout.strip().split('\n')
  63. devices = []
  64. for line in output_lines[1:]:
  65. if '\tdevice' in line:
  66. device_info = line.split('\t')[0]
  67. devices.append(device_info)
  68. # 输出已连接设备列表
  69. if devices:
  70. print("已连接设备:")
  71. for device in devices:
  72. print(device)
  73. else:
  74. print("没有已连接的设备")
  75. return devices
  76. # 检验设备是否连接成功
  77. def check_device_connect(device):
  78. devices = get_local_devices()
  79. if device in devices:
  80. print(device + "已连接")
  81. return True
  82. else:
  83. print(device + "未连接")
  84. return False
  85. def is_app_running(device_id, package_name):
  86. # 执行 adb shell pidof 命令检测应用程序是否运行
  87. command = ['adb', '-s', device_id, 'shell', 'pidof', package_name]
  88. result = subprocess.run(command, capture_output=True, text=True)
  89. # 检查命令执行结果
  90. if result.returncode == 0 and result.stdout.strip().isdigit():
  91. return True
  92. else:
  93. return False
  94. # 检查app运行状态
  95. def check_app_running(device_id, package_name):
  96. # 执行 adb shell pidof 命令检测应用程序是否运行
  97. try:
  98. command = ['adb', '-s', device_id, 'shell', 'pidof', package_name]
  99. result = subprocess.check_output(command, stderr=subprocess.STDOUT, text=True)
  100. return result.strip().isdigit()
  101. except subprocess.CalledProcessError as e:
  102. loggerKit.error("Failed to check if app {0} is running on device {1}, 异常信息:{2}", package_name, device_id,
  103. e.output)
  104. return True
  105. # check app运行状态
  106. def check_app_status(device_id, package_name):
  107. try:
  108. # Execute adb command to check if the app process is running
  109. command = ['adb', '-s', device_id, 'shell', 'pidof', package_name]
  110. result = subprocess.run(command, capture_output=True, text=True, check=True)
  111. # Check the return code to determine if the app process is running
  112. if result.returncode == 0:
  113. return True
  114. else:
  115. loggerKit.error("app {0} 未运行 on device {1}", package_name, device_id)
  116. return False
  117. except subprocess.CalledProcessError as e:
  118. loggerKit.error("Failed to check if app {0} is running on device {1}, 异常信息:{2}", package_name, device_id,
  119. e)
  120. return False
  121. # 判断设备网络状态
  122. def check_mobile_network(device_id):
  123. try:
  124. # 使用 adb 命令检查设备是否有网络连接 请求超时时间设置1s
  125. output = subprocess.check_output(
  126. ["adb", "-s", device_id, "shell", "ping", "-c", "1", "-W", "1", "api-qa.risingauto.com"])
  127. loggerKit.info("thread[{0}=>{1}], 设备号:{2}, 网络正常,返回结果:{3}", threading.current_thread().name,
  128. threading.get_ident(), device_id, output)
  129. return True
  130. except subprocess.CalledProcessError as e:
  131. loggerKit.error("thread[{0}=>{1}], 设备号:{2}, 网络异常,返回结果:{3}", threading.current_thread().name,
  132. threading.get_ident(), device_id,
  133. e.output)
  134. return False
  135. # 检查设备网络状态
  136. def check_network_status(device_id):
  137. try:
  138. ping_domain = extern_domain.replace("https://", "").replace("http://", "").replace("/", "")
  139. command = "adb -s " + device_id + " shell ping -c 3 " + ping_domain
  140. print(f'command: {command}')
  141. output = subprocess.check_output(command, shell=True, timeout=4).decode('utf-8')
  142. if '3 received' in output or '3 packets received' in output:
  143. loggerKit.info("thread[{0}=>{1}], 设备号:{2}, 网络正常,返回结果:{3}", threading.current_thread().name,
  144. threading.get_ident(), device_id, output)
  145. return True
  146. elif '100% packet loss' in output:
  147. loggerKit.info("thread[{0}=>{1}], 设备号:{2}, 网络不可用, 100%丢包率, 返回结果:{3}",
  148. threading.current_thread().name, threading.get_ident(),
  149. device_id, output)
  150. return False
  151. else:
  152. loggerKit.info("thread[{0}=>{1}], 设备号:{2}, 出现部分丢包情况, 返回结果:{3}",
  153. threading.current_thread().name, threading.get_ident(),
  154. device_id, output)
  155. return True
  156. except subprocess.CalledProcessError as e:
  157. loggerKit.error("thread[{0}=>{1}], 设备号:{2}, ping出现异常,返回结果:{3}", threading.current_thread().name,
  158. threading.get_ident(), device_id,
  159. e.output)
  160. return False
  161. def is_number(input_str):
  162. if input_str.isdigit():
  163. return True
  164. else:
  165. try:
  166. float(input_str)
  167. return True
  168. except ValueError:
  169. return False
  170. def contains_digit(string):
  171. for char in string:
  172. if char.isdigit():
  173. return True
  174. return False
  175. def delete_image(image_path):
  176. if os.path.exists(image_path):
  177. os.remove(image_path)
  178. # print(f"Image {image_path} deleted successfully.")
  179. else:
  180. print(f"Image {image_path} does not exist")
  181. # 判断参数是否错误 错误返回True
  182. def param_error(param):
  183. loggerKit.info("开始OCR参数对比:{0}", param)
  184. if '万' in param:
  185. param = param.replace('万', '')
  186. if '%' in param:
  187. param = param.replace('%', '')
  188. if ',' in param:
  189. param = param.replace(',', '')
  190. if ',' in param:
  191. param = param.replace(',', '')
  192. non_numeric_chars = re.compile(r'[^0-9.]')
  193. if non_numeric_chars.search(param):
  194. loggerKit.info("OCR参数对比有异常:{0}", param)
  195. return True
  196. else:
  197. loggerKit.info("OCR参数对比无异常:{0}", param)
  198. return False
  199. '''
  200. 判断字符串是否包含
  201. '''
  202. def contains_substring(string, substring):
  203. if substring in string:
  204. return True
  205. else:
  206. return False
  207. # 判断网络代理端口是否是通的
  208. def check_telnet(host, port):
  209. '''
  210. :param host: host
  211. :param port: port
  212. :return:
  213. '''
  214. try:
  215. # 创建Telnet对象
  216. tn = telnetlib.Telnet(host, port, timeout=10)
  217. tn.close()
  218. return True
  219. except ConnectionRefusedError:
  220. return False
  221. except TimeoutError:
  222. return False
  223. except Exception as e:
  224. print("An error occurred:", e)
  225. return False
  226. '''
  227. 通用手机截图
  228. '''
  229. def cap_device(d, device, task_id):
  230. '''
  231. :param d: uiautomator2 对象
  232. :param device: 设备id
  233. :param task_id: 任务id
  234. :return:
  235. '''
  236. try:
  237. app_dir = os.path.dirname(sys.argv[0])
  238. screenshot_path = os.path.join(app_dir, f'screen/{device}/')
  239. tmp_path = screenshot_path.replace(":", "_").replace(".", "_")
  240. # 检查路径是否存在
  241. if not os.path.exists(tmp_path):
  242. # 创建多级目录
  243. os.makedirs(os.path.dirname(tmp_path))
  244. file_path = os.path.join(tmp_path, f'{device}_{task_id}_{time.time()}.png')
  245. d.screenshot().save(file_path)
  246. loggerKit.info(f'设备:{device} 执行任务出现异常,截图保存:{file_path}, tmp_path:{tmp_path}')
  247. except Exception as e:
  248. loggerKit.error(f'设备:{device} 执行任务出现异常后截图保存发生异常:{str(e)}')
  249. '''
  250. check 手机设备状态
  251. '''
  252. def check_single_adb_connect(device, mobile):
  253. target_url = extern_domain.replace("https://", "").replace("http://", "").replace("/", "")
  254. cmd_conn = f"adb connect {device}"
  255. conn_output = subprocess.check_output(cmd_conn, shell=True)
  256. loggerKit.info(f"{conn_output}")
  257. if b'Connection refused' in conn_output:
  258. loggerKit.info('手机待激活{0}->{1},不能领取任务', device, mobile)
  259. return DeviceStatus.Mobile_Phone_To_Be_Activated
  260. elif b'already connected' in conn_output:
  261. # 再次check 设备的 atx agent 服务状态
  262. w = uiautomator2.connect(device)
  263. # check atx agent 服务状态
  264. if w.agent_alive:
  265. loggerKit.info('atx-agent 服务正常, atx 已连接{0}->{1}, 再次成功连接', device, mobile)
  266. else:
  267. # 启动atx-agent服务,防止服务手动误关闭
  268. start_atx_agent = f"adb -s {device} shell /data/local/tmp/atx-agent server -d --nouia "
  269. opts_output = subprocess.check_output(start_atx_agent, shell=True)
  270. loggerKit.info(f"启动设备{device}->{mobile} atx agent 服务, {opts_output}, 再次成功连接")
  271. # check 设备是否在线
  272. cmd_show = f"adb devices -l | grep {device}"
  273. show_output = subprocess.check_output(cmd_show, shell=True)
  274. loggerKit.info(f"设备{device}->{mobile}, {show_output}")
  275. if b"device product" in show_output:
  276. loggerKit.info("设备在线{0}->{1}, 可以领取任务", device, mobile)
  277. d = uiautomator2.connect(device)
  278. loggerKit.info("{0}->{1}=>{2}", d.uiautomator.running(), device, mobile)
  279. if d.uiautomator.running():
  280. # loggerKit.info('atx 连接正常{0}->{1}, uiautomator2 服务正常, 开始判断设备网络质量', device, mobile)
  281. loggerKit.info('atx 连接正常{0}, uiautomator2 服务正常', device)
  282. # 判断手机端网络质量
  283. # cmd_ping = f"adb -s " + device + " shell ping -c 6 " + target_url
  284. # wifi_output = subprocess.check_output(cmd_ping, shell=True)
  285. #
  286. # byte_array = bytearray(wifi_output)
  287. # byte_string = bytes(byte_array)
  288. #
  289. # # Convert byte string to string
  290. # wifi_str = byte_string.decode('utf-8')
  291. #
  292. # latencies = extract_latency(wifi_str, device=device, mobile=mobile)
  293. # network_quality = check_network_quality(latencies, device=device, mobile=mobile)
  294. # match network_quality:
  295. # case DeviceStatus.Device_Net_Good:
  296. # loggerKit.info('atx 连接正常{0}, uiautomator2 服务正常, 设备网络质量良好, 可以领取任务', device)
  297. # return DeviceStatus.Available_For_Task
  298. # case DeviceStatus.Device_Net_Common:
  299. # loggerKit.info('atx 连接正常{0}, uiautomator2 服务正常, 设备网络质量一般, 可以领取任务', device)
  300. # return DeviceStatus.Available_For_Task
  301. # case DeviceStatus.Device_Net_Bad:
  302. # loggerKit.info('atx 连接正常{0}, uiautomator2 服务正常, 设备网络质量较差, 不能领取任务', device)
  303. # return DeviceStatus.Device_Await_Recover
  304. # if network_quality == DeviceStatus.Device_Net_Good:
  305. # loggerKit.info('atx 连接正常{0}->{1}, uiautomator2 服务正常, 设备网络质量良好, 可以领取任务',
  306. # device, mobile)
  307. # return DeviceStatus.Available_For_Task
  308. # elif network_quality == DeviceStatus.Device_Net_Common:
  309. # loggerKit.info('atx 连接正常{0}->{1}, uiautomator2 服务正常, 设备网络质量一般, 可以领取任务',
  310. # device, mobile)
  311. # return DeviceStatus.Available_For_Task
  312. # else:
  313. # loggerKit.info('atx 连接正常{0}->{1}, uiautomator2 服务正常, 设备网络质量较差, 不能领取任务',
  314. # device, mobile)
  315. # return DeviceStatus.Device_Await_Recover
  316. return DeviceStatus.Available_For_Task
  317. else:
  318. loggerKit.info('atx 连接正常 {0}->{1}, uiautomator2 服务待人工启动, 不能领取任务', device, mobile)
  319. time.sleep(1)
  320. return DeviceStatus.To_Be_Manually_Started
  321. elif b"offline product" in show_output:
  322. loggerKit.info("设备不在线{0}->{1}, 不能领取任务", device, mobile)
  323. return DeviceStatus.Device_Offline
  324. else:
  325. loggerKit.info("{0}->{1}=>{2}", device, mobile, show_output)
  326. return DeviceStatus.Device_Offline
  327. elif b'connected to' in conn_output:
  328. u = uiautomator2.connect(device)
  329. # check atx agent 服务状态
  330. if u.agent_alive:
  331. loggerKit.info('atx-agent 服务正常, atx 已连接{0}->{1}, 首次成功连接', device, mobile)
  332. else:
  333. # 启动atx-agent服务,防止服务手动误关闭
  334. cmd_start_atx_agent = f"adb -s {device} shell /data/local/tmp/atx-agent server -d --nouia "
  335. opt_output = subprocess.check_output(cmd_start_atx_agent, shell=True)
  336. loggerKit.info(f"启动设备{device}->{mobile} atx agent 服务, {opt_output}, 首次成功连接")
  337. return DeviceStatus.First_Success_Connect
  338. else:
  339. loggerKit('atx已连接{0}->{1}, 首次成功连接', device, mobile)
  340. return DeviceStatus.First_Success_Connect
  341. def check_wifi_info(device, mobile):
  342. target_url = extern_domain.replace("https://", "").replace("http://", "").replace("/", "")
  343. cmd_conn = f"adb connect {device}"
  344. conn_output = subprocess.check_output(cmd_conn, shell=True)
  345. loggerKit.info(f"设备:{device} => {conn_output}")
  346. cmd_ping = f"adb -s " + device + " shell ping -c 6 " + target_url
  347. wifi_output = subprocess.check_output(cmd_ping, shell=True)
  348. loggerKit.info(f"设备:{device} => {wifi_output}")
  349. byte_array = bytearray(wifi_output)
  350. byte_string = bytes(byte_array)
  351. # Convert byte string to string
  352. wifi_str = byte_string.decode('utf-8')
  353. latencies = extract_latency(wifi_str, device, mobile)
  354. network_quality = check_network_quality(latencies, device, mobile)
  355. print(f"设备:{device} => {network_quality}")
  356. # check 远程端口是否是通的
  357. def check_remote_port(host, port):
  358. try:
  359. # 创建socket对象
  360. sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  361. sock.settimeout(5)
  362. # 尝试连接远程主机的指定端口
  363. result = sock.connect_ex((host, port))
  364. sock.close()
  365. # 根据连接结果判断端口是否开放
  366. if result == 0:
  367. return True
  368. else:
  369. return False
  370. except Exception as e:
  371. loggerKit.error('check_remote_port, exception:{0}', e)
  372. return False
  373. # 计算距离
  374. def get_distance(lat1, lon1, lat2, lon2):
  375. r = 6371000
  376. dLat = (lat2 - lat1) * math.pi / 180
  377. dLon = (lon2 - lon1) * math.pi / 180
  378. a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(lat1 * math.pi / 180) * math.cos(
  379. lat2 * math.pi / 180) * math.sin(dLon / 2) * math.sin(dLon / 2)
  380. c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
  381. d = r * c
  382. return d
  383. '''
  384. 提取延迟时间
  385. '''
  386. def extract_latency(ping_result, device, mobile):
  387. latency_pattern = r"time=(\d+\.\d+)"
  388. latencies = re.findall(latency_pattern, ping_result)
  389. loggerKit.info("设备{0}->{1}, ping_result=>{2}, latencies=>{3}", device, mobile, ping_result, latencies)
  390. return [float(latency) for latency in latencies]
  391. '''
  392. 判断网络情况好坏
  393. '''
  394. def check_network_quality(latencies, device, mobile):
  395. avg_latency = sum(latencies) / len(latencies)
  396. max_latency = max(latencies)
  397. packet_loss = 100 - (len(latencies) / 6) * 100
  398. loggerKit.info('设备{0}->{1}, avg_latency=>{2}, max_latency={3}, packet_loss={4}', device, mobile, avg_latency,
  399. max_latency, packet_loss)
  400. if avg_latency <= 50 and max_latency <= 100 and packet_loss == 0:
  401. return DeviceStatus.Device_Net_Good
  402. elif avg_latency <= 200 and max_latency <= 300 and packet_loss <= 85:
  403. return DeviceStatus.Device_Net_Common
  404. else:
  405. return DeviceStatus.Device_Net_Bad
  406. def seconds_to_str(s):
  407. """秒转化天 时 分 秒"""
  408. day = s / 86400
  409. hour = (s % 86400) / 3600
  410. minute = (s % 3600) / 60
  411. secs = (s % 60)
  412. ret = ''
  413. if day:
  414. ret += '%d天' % day
  415. if hour:
  416. ret += '%d小时' % hour
  417. if minute:
  418. ret += '%d分' % minute
  419. if secs:
  420. ret += '%d秒' % secs
  421. return ret
  422. def to_json(obj):
  423. """
  424. 对象转json 的函数
  425. :param obj:
  426. :return:
  427. """
  428. return json.dumps(obj, default=lambda x: x.__dict__, ensure_ascii=False)