SCLP
This commit is contained in:
360
SCLP/utils/misc.py
Normal file
360
SCLP/utils/misc.py
Normal file
@@ -0,0 +1,360 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
"""
|
||||
@Author : xuxingchen
|
||||
@Contact : xuxingchen@sinochem.com
|
||||
@Desc : 杂项
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
import time
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
import re
|
||||
import random
|
||||
import string
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
import socket
|
||||
import psutil
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytz
|
||||
import requests
|
||||
from PIL import Image
|
||||
import paho.mqtt.client as mqtt
|
||||
from openpyxl.styles import PatternFill, Font, Border, Side
|
||||
from openpyxl.utils.dataframe import dataframe_to_rows
|
||||
from openpyxl.workbook import Workbook
|
||||
from pydantic import BaseModel
|
||||
|
||||
from utils import logger
|
||||
|
||||
|
||||
def get_ip_address(interface_name: str) -> tuple[str, str]:
|
||||
interfaces = psutil.net_if_addrs()
|
||||
interface = interfaces.get(interface_name, None)
|
||||
ipv4_address, ipv6_address = "", ""
|
||||
if interface:
|
||||
for address in interface:
|
||||
if address.family == socket.AF_INET:
|
||||
ipv4_address = address.address
|
||||
elif address.family == socket.AF_INET6 and not address.address.startswith("fe80"):
|
||||
ipv6_address = address.address
|
||||
return ipv4_address, ipv6_address
|
||||
|
||||
def extract_fixed_length_number(number_str: str, fixed_length: int = 2) -> str:
|
||||
"""提取一串存在数值的字符串中的第一个数值,对其从右往左取值定值,不足补0"""
|
||||
pattern = re.compile(r'[0-9]+')
|
||||
number = pattern.search(number_str)
|
||||
if number is None:
|
||||
return "0" * fixed_length
|
||||
else:
|
||||
if len(number[0]) < fixed_length:
|
||||
return "0" * (fixed_length - len(number[0])) + number[0]
|
||||
else:
|
||||
return number[0][-fixed_length:]
|
||||
|
||||
|
||||
def snake2camel(key: str) -> str:
|
||||
"""snake命名风格转成camel命名风格"""
|
||||
parts = key.split('_')
|
||||
return parts[0] + ''.join(word.capitalize() for word in parts[1:])
|
||||
|
||||
|
||||
def snake2camel_list_dict(snake_list: list[dict]) -> list[dict]:
|
||||
"""将list[dict]中所有的snake格式的key转换成camel格式的key命名风格"""
|
||||
camel_list = []
|
||||
for snake_dict in snake_list:
|
||||
camel_dict = {}
|
||||
for key, value in snake_dict.items():
|
||||
camel_dict[snake2camel(key)] = value
|
||||
camel_list.append(camel_dict)
|
||||
return camel_list
|
||||
|
||||
|
||||
def clear_log_file(log_path: str, day: int = 7):
|
||||
"""每7天清除一次日志文件"""
|
||||
creation_time = os.path.getctime(log_path)
|
||||
days_since_creation = (time.time() - creation_time) / (60 * 60 * 24)
|
||||
if os.path.exists(log_path) and days_since_creation >= day:
|
||||
try:
|
||||
f0 = open(log_path, "r", encoding="utf8")
|
||||
f1 = open(f"{log_path}.old", "w", encoding="utf8")
|
||||
f1.write(f0.read())
|
||||
f1.close()
|
||||
f0.close()
|
||||
os.remove(log_path)
|
||||
print(f"日志文件 {logger.LOGGER_PATH} 完成重置")
|
||||
except Exception as e:
|
||||
print(f"日志文件 {logger.LOGGER_PATH} 重置失败: {e}")
|
||||
|
||||
|
||||
def generate_captcha_text(characters: int = 6) -> str:
|
||||
"""生成指定长度的随机文本"""
|
||||
letters_and_digits = string.ascii_letters + string.digits
|
||||
return ''.join(random.choice(letters_and_digits) for _ in range(characters))
|
||||
|
||||
|
||||
def encrypt_number(phone_number: str, key: bytes = b'7A') -> str:
|
||||
"""将电话号码加密为字符串"""
|
||||
phone_bytes = phone_number.encode('utf-8') # 将字符串转换为字节
|
||||
encrypted_bytes = bytes(
|
||||
[byte ^ key[i % len(key)] for i, byte in enumerate(phone_bytes)]
|
||||
)
|
||||
# 使用Base64编码加密后的字节
|
||||
encrypted_number = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_number
|
||||
|
||||
|
||||
def decrypt_number(encrypted_number: str, key: bytes = b'7A') -> str:
|
||||
"""将字符串解密为电话号码"""
|
||||
# 使用Base64解码加密后的字符串
|
||||
encrypted_bytes = base64.b64decode(encrypted_number)
|
||||
decrypted_bytes = bytes(
|
||||
[byte ^ key[i % len(key)] for i, byte in enumerate(encrypted_bytes)]
|
||||
)
|
||||
decrypted_number = decrypted_bytes.decode('utf-8')
|
||||
return decrypted_number
|
||||
|
||||
|
||||
def now_tz_datetime(days: int = 0) -> str:
|
||||
future_time = datetime.now() + timedelta(days=days)
|
||||
return future_time.strftime("%Y-%m-%dT%H:%M:%S.") + f"{future_time.microsecond // 1000:03d}Z"
|
||||
|
||||
|
||||
def now_datetime_nanosecond(days: int = 0) -> str:
|
||||
future_time = datetime.now() + timedelta(days=days)
|
||||
return future_time.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
|
||||
def now_datetime_second(days: int = 0) -> str:
|
||||
future_time = datetime.now() + timedelta(days=days)
|
||||
return future_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def millisecond_timestamp2tz(timestamp_13: str):
|
||||
timestamp = int(timestamp_13) / 1000
|
||||
dt_utc = datetime.fromtimestamp(timestamp, tz=pytz.UTC)
|
||||
# 转换为所需的时区,这里以北京时间(China Standard Time)为例
|
||||
china_tz = pytz.timezone('Asia/Shanghai')
|
||||
return dt_utc.astimezone(china_tz).strftime("%Y-%m-%dT%H:%M:%S.") + f"{dt_utc.microsecond // 1000:03d}Z"
|
||||
|
||||
|
||||
def is_image_url_valid(url: str) -> bool:
|
||||
try:
|
||||
# 发送请求获取URL内容
|
||||
response = requests.get(url)
|
||||
response.raise_for_status() # 如果状态码不是200,会抛出异常
|
||||
|
||||
# 将内容加载为图片
|
||||
image = Image.open(BytesIO(response.content))
|
||||
image.verify() # 验证图像文件是否可读
|
||||
|
||||
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
|
||||
return True
|
||||
except (requests.RequestException, IOError):
|
||||
# 如果有任何异常,说明图片不可用或格式不可读
|
||||
return False
|
||||
|
||||
|
||||
def is_image_valid(path: str) -> bool:
|
||||
try:
|
||||
# 将内容加载为图片
|
||||
image = Image.open(open(path, 'rb'))
|
||||
image.verify() # 验证图像文件是否可读
|
||||
|
||||
# 如果上面的代码没有抛出异常,说明图片存在且格式可读
|
||||
return True
|
||||
except (requests.RequestException, IOError):
|
||||
# 如果有任何异常,说明图片不可用或格式不可读
|
||||
return False
|
||||
|
||||
|
||||
def get_file_md5(file_path):
|
||||
content = open(file_path, 'rb')
|
||||
md5hash = hashlib.md5(content.read())
|
||||
return md5hash.hexdigest()
|
||||
|
||||
|
||||
def sql_export_xls(query,
|
||||
db_connection,
|
||||
save_file_path,
|
||||
sheet_title,
|
||||
sheet_header: Optional[list] = None,
|
||||
header_background_color: str = "808080",
|
||||
header_font_color: str = "ffffff"):
|
||||
df = pd.read_sql_query(query, db_connection)
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = sheet_title
|
||||
for i, r in enumerate(dataframe_to_rows(df, index=False, header=True)):
|
||||
if sheet_header is not None and i == 0:
|
||||
ws.append(sheet_header)
|
||||
continue
|
||||
ws.append(r)
|
||||
# 表头样式
|
||||
header_fill = PatternFill(start_color=header_background_color, fill_type="solid")
|
||||
header_font = Font(color=header_font_color, bold=True)
|
||||
for cell in ws[1]:
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
# 边框样式 表头无边框-数据无顶实线框
|
||||
thin_border = Border(
|
||||
left=Side(style='thin'),
|
||||
right=Side(style='thin'),
|
||||
bottom=Side(style='thin')
|
||||
)
|
||||
for i, row in enumerate(ws.iter_rows()):
|
||||
if i == 0:
|
||||
continue
|
||||
for cell in row:
|
||||
cell.border = thin_border
|
||||
# 单元格宽度自适应调整
|
||||
for column in ws.columns:
|
||||
max_length = 0
|
||||
column = list(column)
|
||||
for cell in column:
|
||||
if cell.value is not None:
|
||||
cell_length = len(str(cell.value))
|
||||
if re.search(r'[\u4e00-\u9fff]', str(cell.value)):
|
||||
cell_length += len(re.findall(r'[\u4e00-\u9fff]', str(cell.value)))
|
||||
if cell_length > max_length:
|
||||
max_length = cell_length
|
||||
adjusted_width = (max_length + 2)
|
||||
ws.column_dimensions[column[0].column_letter].width = adjusted_width
|
||||
wb.save(save_file_path)
|
||||
|
||||
|
||||
def valid_xls(file: BytesIO, required_columns: Optional[list]) -> tuple[bool, str, Optional[list]]:
|
||||
"""如果校验通过返回list结构的数据,如果检验不通过返回None"""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
try:
|
||||
df = pd.read_excel(file)
|
||||
df = df.replace(np.nan, None)
|
||||
if all(col in df.columns for col in required_columns):
|
||||
return True, "", df[required_columns].values.tolist()
|
||||
else:
|
||||
missing_cols = [col for col in required_columns if col not in df.columns]
|
||||
return False, f"缺少必要的列: {', '.join(missing_cols)}", None
|
||||
except Exception as e:
|
||||
return False, f"文件解析失败 {type(e).__name__}, {e}", None
|
||||
|
||||
|
||||
class BasicCallback(BaseModel):
|
||||
status: bool
|
||||
message: Optional[str]
|
||||
|
||||
|
||||
class InvalidException(Exception):
|
||||
def __init__(self, message: str):
|
||||
self.message = message
|
||||
|
||||
|
||||
class UserData:
|
||||
def __init__(self):
|
||||
self.table_handle = None
|
||||
self.topic: Optional[str] = None
|
||||
self.topics: list = []
|
||||
self.table_handler = None
|
||||
self.message = None
|
||||
self.token = None
|
||||
self.status: dict = {}
|
||||
self.clients: dict = {}
|
||||
self.lock = threading.Lock() # 添加一个锁用于线程同步
|
||||
|
||||
def set_table_handle(self, value):
|
||||
with self.lock:
|
||||
self.table_handle = value
|
||||
|
||||
def set_topic(self, value: str):
|
||||
with self.lock:
|
||||
self.topic = value
|
||||
|
||||
def set_topics(self, value: list):
|
||||
with self.lock:
|
||||
self.topics = value
|
||||
|
||||
def set_table_handler(self, value):
|
||||
with self.lock:
|
||||
self.table_handler = value
|
||||
|
||||
def set_message(self, value):
|
||||
with self.lock:
|
||||
self.message = value
|
||||
|
||||
def set_token(self, value):
|
||||
with self.lock:
|
||||
self.token = value
|
||||
|
||||
def set_status(self, value: dict):
|
||||
with self.lock:
|
||||
self.status = value
|
||||
|
||||
def set_status_add(self, key, value):
|
||||
with self.lock:
|
||||
self.status[key] = value
|
||||
|
||||
def set_status_remove(self, key):
|
||||
with self.lock:
|
||||
if self.status and key in self.status.keys():
|
||||
self.status.pop(key)
|
||||
|
||||
def get_status(self, key):
|
||||
if self.status and key in self.status.keys():
|
||||
return self.status[key]
|
||||
|
||||
def set_clients(self, value: dict):
|
||||
with self.lock:
|
||||
self.clients = value
|
||||
|
||||
def set_client_add(self, key, value):
|
||||
with self.lock:
|
||||
self.clients[key] = value
|
||||
|
||||
|
||||
def create_mqtt_client(broker_host,
|
||||
broker_port,
|
||||
userdata: UserData,
|
||||
on_message=None,
|
||||
on_publish=None,
|
||||
on_connect=None,
|
||||
on_disconnect=None,
|
||||
client_id: str = "",
|
||||
username: str = "",
|
||||
password: str = ""):
|
||||
if client_id != "":
|
||||
client = mqtt.Client(client_id=client_id)
|
||||
else:
|
||||
client = mqtt.Client()
|
||||
client.user_data_set(userdata)
|
||||
if on_connect:
|
||||
client.on_connect = on_connect
|
||||
if on_disconnect:
|
||||
client.on_disconnect = on_disconnect
|
||||
if on_message:
|
||||
client.on_message = on_message
|
||||
if on_publish:
|
||||
client.on_publish = on_publish
|
||||
client.username_pw_set(username, password)
|
||||
client.connect(broker_host, broker_port)
|
||||
return client
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
logger.Logger.init(logger.new_dc(f"🔗 Mqtt connection! {{rc: {rc}}} 🔗", '[1;32m'))
|
||||
if userdata.topics:
|
||||
_topics = [(topic, 0) for topic in userdata.topics]
|
||||
client.subscribe(_topics)
|
||||
logger.Logger.debug(f"subscribe topics: {userdata.topics}")
|
||||
|
||||
|
||||
def on_disconnect(client, userdata, rc):
|
||||
logger.Logger.info(logger.new_dc(f"🔌 Break mqtt connection! {{rc: {rc}}} 🔌", "[1m"))
|
||||
|
||||
|
||||
def on_publish(client, userdata, rc):
|
||||
logger.Logger.debug(f"{userdata.topic} <- {userdata.message}")
|
||||
Reference in New Issue
Block a user