factory/apps/utils/tools.py

349 lines
12 KiB
Python
Executable File

import re
import textwrap
import random
import string
from datetime import datetime
from django.conf import settings
import base64
import requests
from io import BytesIO
from rest_framework.serializers import ValidationError
import ast
from typing import Dict
from django.core.serializers.json import DjangoJSONEncoder
from decimal import Decimal
class MyJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, Decimal):
return float(obj)
return super().default(obj)
class CodeAnalyzer(ast.NodeVisitor):
def __init__(self):
self.errors = []
def analyze(self, code):
try:
tree = ast.parse(code)
self.visit(tree)
except SyntaxError as e:
self.errors.append(f"Syntax error at line {e.lineno}: {e.msg}")
def visit_Import(self, node):
self.errors.append(f"Forbidden import statement at line {node.lineno}")
self.generic_visit(node)
def visit_ImportFrom(self, node):
self.errors.append(f"Forbidden import-from statement at line {node.lineno}")
self.generic_visit(node)
def visit_Exec(self, node):
self.errors.append(f"Forbidden exec statement at line {node.lineno}")
self.generic_visit(node)
def visit_Call(self, node):
# Detect forbidden function calls like eval, open, etc.
if isinstance(node.func, ast.Name):
if node.func.id in ['eval', 'exec', 'open', 'compile']:
self.errors.append(f"Forbidden function '{node.func.id}' used at line {node.lineno}")
self.generic_visit(node)
def visit_With(self, node):
# Detect forbidden with-open blocks
for item in node.items:
context_expr = item.context_expr
# For Python versions >= 3.9, context_expr is replaced by context_exprs
if isinstance(context_expr, ast.Call) and isinstance(context_expr.func, ast.Name):
if context_expr.func.id == 'open':
self.errors.append(f"Forbidden 'open' statement within 'with' block at line {node.lineno}")
self.generic_visit(node)
def is_close(num1, num2=0, tolerance=1e-9):
"""
Check if a numeric value (int, float, etc.) is close.
"""
if isinstance(num1, float) or isinstance(num2, float): # Float check
return abs(num1-num2) < tolerance
elif isinstance(num1, int) and isinstance(num2, int): # Integer check
return num1 == num2
else:
raise ValueError("Unsupported numeric type")
def tran64(s):
missing_padding = len(s) % 4
if missing_padding != 0:
s = s+'=' * (4 - missing_padding)
return s
def singleton(cls):
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
def print_roundtrip(response, *args, **kwargs):
def format_headers(d): return '\n'.join(f'{k}: {v}' for k, v in d.items())
print(textwrap.dedent('''
---------------- request ----------------
{req.method} {req.url}
{reqhdrs}
{req.body}
---------------- response ----------------
{res.status_code} {res.reason} {res.url}
{reshdrs}
{res.text}
''').format(
req=response.request,
res=response,
reqhdrs=format_headers(response.request.headers),
reshdrs=format_headers(response.headers),
))
def ranstr(num):
salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num))
return salt
def rannum(num):
salt = ''.join(random.sample(string.digits, num))
return salt
def timestamp_to_time(millis):
"""10位时间戳转换为日期格式字符串"""
return datetime.fromtimestamp(millis)
def convert_to_base64(path: str):
"""给定图片转base64
Args:
path (str): 图片地址
"""
if path.startswith('http'): # 如果是网络图片
return str(base64.b64encode(BytesIO(requests.get(url=path).content).read()), 'utf-8')
else:
with open(settings.BASE_DIR + path, 'rb') as f:
return str(base64.b64encode(f.read()), 'utf-8')
def p_in_poly(p, poly):
px = p['x']
py = p['y']
flag = False
i = 0
l = len(poly)
j = l - 1
# for(i = 0, l = poly.length, j = l - 1; i < l; j = i, i++):
while i < l:
sx = poly[i]['x']
sy = poly[i]['y']
tx = poly[j]['x']
ty = poly[j]['y']
# 点与多边形顶点重合
if (sx == px and sy == py) or (tx == px and ty == py):
return (px, py)
# 判断线段两端点是否在射线两侧
if (sy < py and ty >= py) or (sy >= py and ty < py):
# 线段上与射线 Y 坐标相同的点的 X 坐标
x = sx + (py - sy) * (tx - sx) / (ty - sy)
# 点在多边形的边上
if x == px:
return (px, py)
# 射线穿过多边形的边界
if x > px:
flag = not flag
j = i
i += 1
# 射线穿过多边形边界的次数为奇数时点在多边形内
return (px, py) if flag else 'out'
def check_id_number_e(val):
re_s = r'^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$'
if not re.match(re_s, val):
raise ValidationError('身份证号校验错误')
return val
def get_info_from_id(val):
birth = val[6:14]
birth_year = birth[0:4]
age = datetime.now().year - int(birth_year)
sex = int(val[-2])
gender = ''
if sex % 2:
gender = ''
return dict(age=age, gender=gender)
def check_id_number(idcard):
"""校验身份证号
Args:
id_number (_type_): 身份证号
"""
Errors = ['身份证号码位数不对!', '身份证号码出生日期超出范围或含有非法字符!', '身份证号码校验错误!', '身份证地区非法!']
area = {"11": "北京", "12": "天津", "13": "河北", "14": "山西", "15": "内蒙古", "21": "辽宁", "22": "吉林", "23": "黑龙江",
"31": "上海", "32": "江苏", "33": "浙江", "34": "安徽", "35": "福建", "36": "江西", "37": "山东", "41": "河南", "42": "湖北",
"43": "湖南", "44": "广东", "45": "广西", "46": "海南", "50": "重庆", "51": "四川", "52": "贵州", "53": "云南", "54": "西藏",
"61": "陕西", "62": "甘肃", "63": "青海", "64": "宁夏", "65": "新疆", "71": "台湾", "81": "香港", "82": "澳门", "91": "国外"}
idcard = str(idcard)
idcard = idcard.strip()
idcard_list = list(idcard)
# 地区校验
if str(idcard[0:2]) not in area:
return False, Errors[3]
# 15位身份号码检测
if len(idcard) == 15:
if ((int(idcard[6:8]) + 1900) % 4 == 0 or (
(int(idcard[6:8]) + 1900) % 100 == 0 and (int(idcard[6:8]) + 1900) % 4 == 0)):
ereg = re.compile(
'[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$') # //测试出生日期的合法性
else:
ereg = re.compile(
'[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$') # //测试出生日期的合法性
if re.match(ereg, idcard):
return True, ''
else:
return False, Errors[1]
# 18位身份号码检测
elif len(idcard) == 18:
# 出生日期的合法性检查
# 闰年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))
# 平年月日:((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))
if (int(idcard[6:10]) % 4 == 0 or (int(idcard[6:10]) % 100 == 0 and int(idcard[6:10]) % 4 == 0)):
# 闰年出生日期的合法性正则表达式
ereg = re.compile(
'[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$')
else:
# 平年出生日期的合法性正则表达式
ereg = re.compile(
'[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$')
# 测试出生日期的合法性
if re.match(ereg, idcard):
# 计算校验位
S = (int(idcard_list[0]) + int(idcard_list[10])) * 7 + (int(idcard_list[1]) + int(idcard_list[11])) * 9 + (
int(idcard_list[2]) + int(idcard_list[12])) * 10 + (
int(idcard_list[3]) + int(idcard_list[13])) * 5 + (
int(idcard_list[4]) + int(idcard_list[14])) * 8 + (
int(idcard_list[5]) + int(idcard_list[15])) * 4 + (
int(idcard_list[6]) + int(idcard_list[16])) * 2 + int(idcard_list[7]) * 1 + int(
idcard_list[8]) * 6 + int(idcard_list[9]) * 3
Y = S % 11
M = "F"
JYM = "10X98765432"
M = JYM[Y] # 判断校验位
if M == idcard_list[17]: # 检测ID的校验位
return True, ''
else:
return False, Errors[2]
else:
return False, Errors[1]
else:
return False, Errors[0]
def check_id_number_strict(id_card: str):
"""校验 18 位中国身份证号码是否合法"""
if not re.match(r'^\d{17}[\dXx]$', id_card):
return False
# 系数和校验码
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check_codes = "10X98765432"
# 计算校验码
total = sum(int(id_card[i]) * weights[i] for i in range(17))
expected_check_code = check_codes[total % 11]
if expected_check_code == id_card[-1].upper():
return id_card
raise ValidationError('身份证号校验失败')
def check_phone_e(phone):
re_phone = r'^1\d{10}$'
if not re.match(re_phone, phone):
raise ValidationError('手机号格式错误')
return phone
def compare_dicts(dict1, dict2, ignore_order=False):
if ignore_order:
for key in sorted(dict1.keys()):
if key not in dict2 or not compare_values(dict1[key], dict2[key], ignore_order):
return False
return True
else:
return dict1 == dict2
def compare_lists_of_dicts(list1, list2, ignore_order=False):
"""比较两个列表,这里的列表包含字典(对象)"""
if ignore_order:
# 转换列表中的字典为元组列表,然后排序进行比较
if list1 and isinstance(list1[0], dict):
sorted_list1 = sorted((tuple(sorted(d.items())) for d in list1))
sorted_list2 = sorted((tuple(sorted(d.items())) for d in list2))
return sorted_list1 == sorted_list2
return list1 == list2
else:
# 按顺序比较列表中的字典
return all(compare_dicts(obj1, obj2) for obj1, obj2 in zip(list1, list2))
def compare_values(val1, val2, ignore_order=False):
"""通用比较函数,也可以处理字典和列表。"""
if isinstance(val1, list) and isinstance(val2, list):
# 假设这里我们关心列表中对象的顺序
return compare_lists_of_dicts(val1, val2, ignore_order)
elif isinstance(val1, dict) and isinstance(val2, dict):
return compare_dicts(val1, val2, ignore_order)
else:
return val1 == val2
def build_tree_from_list(data, parent_field="parent"):
id_map = {item["id"]: item for item in data}
tree = []
for item in data:
parent_id = item.get(parent_field, None)
if parent_id is None:
tree.append(item)
else:
parent = id_map.get(parent_id, None)
if parent:
parent.setdefault("children", []).append(item)
else:
tree.append(item)
return tree
def convert_ordereddict(item):
"""递归地将 OrderedDict 转换为普通字典"""
if isinstance(item, list):
return [convert_ordereddict(i) for i in item] # 递归处理列表中的每个元素
elif isinstance(item, dict):
# 如果是 OrderedDict 或普通字典,遍历所有键值对进行转换
return {key: convert_ordereddict(value) for key, value in item.items()}
return item
def update_dict(d, update_data:Dict) -> Dict:
d.update(update_data)
return d