349 lines
12 KiB
Python
Executable File
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 |