2024-10-24 09:23:39 +08:00

361 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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}")