jit-utils-backend 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jit_utils/__init__.py +152 -0
- jit_utils/apiAuthSign.py +73 -0
- jit_utils/barcode.py +50 -0
- jit_utils/clsTool.py +71 -0
- jit_utils/config/__init__.py +11 -0
- jit_utils/config/case.py +77 -0
- jit_utils/config/config.py +90 -0
- jit_utils/config/exception.py +17 -0
- jit_utils/config/field.py +177 -0
- jit_utils/convert.py +169 -0
- jit_utils/decorator.py +58 -0
- jit_utils/exceptions.py +113 -0
- jit_utils/forwarder.py +113 -0
- jit_utils/matchTool.py +136 -0
- jit_utils/network.py +36 -0
- jit_utils/qrcode.py +60 -0
- jit_utils/signature.py +56 -0
- jit_utils/spaceSender.py +44 -0
- jit_utils/string.py +118 -0
- jit_utils/time.py +701 -0
- jit_utils/validator.py +26 -0
- jit_utils/workday_constants.py +72 -0
- jit_utils_backend-0.0.1.dist-info/METADATA +182 -0
- jit_utils_backend-0.0.1.dist-info/RECORD +32 -0
- jit_utils_backend-0.0.1.dist-info/WHEEL +5 -0
- jit_utils_backend-0.0.1.dist-info/top_level.txt +2 -0
- tests/__init__.py +4 -0
- tests/run_tests.py +102 -0
- tests/test_package_imports.py +199 -0
- tests/test_qrcode_barcode.py +182 -0
- tests/test_string_utils.py +174 -0
- tests/test_time_utils.py +185 -0
jit_utils/matchTool.py
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# -*-coding:utf-8-*-
|
2
|
+
"""
|
3
|
+
Created on 2023/7/12
|
4
|
+
|
5
|
+
@author: 臧韬
|
6
|
+
|
7
|
+
@desc: 默认描述
|
8
|
+
"""
|
9
|
+
import copy
|
10
|
+
from abc import ABCMeta, abstractmethod
|
11
|
+
|
12
|
+
|
13
|
+
class BaseMatchRole(metaclass=ABCMeta):
|
14
|
+
successMsg = ""
|
15
|
+
|
16
|
+
@abstractmethod
|
17
|
+
def hash(self, data) -> int:
|
18
|
+
"""
|
19
|
+
将 Dict 数据按照自定义的方式进行hash,返回一个hash int
|
20
|
+
如果这里返回的是None,则被认为不合格数据,将不进行匹配
|
21
|
+
"""
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
class MatchResultNumEnum:
|
26
|
+
success = 0
|
27
|
+
oldOnly = -1
|
28
|
+
newOnly = 1
|
29
|
+
|
30
|
+
|
31
|
+
# 这个的 作用是 给MatchTool.match返回值 构建返回 数据
|
32
|
+
class MatchResult(object):
|
33
|
+
DEFAULT_SUCCESS_RESULT_STRING = "匹配成功"
|
34
|
+
DEFAULT_OLD_ONLY_RESULT_STRING = "只有旧数据匹配"
|
35
|
+
DEFAULT_NEW_ONLY_RESULT_STRING = "只有新数据匹配"
|
36
|
+
|
37
|
+
def __init__(self, oldData=None, newData=None, resultStr=None):
|
38
|
+
self.oldData = oldData
|
39
|
+
self.newData = newData
|
40
|
+
self.resultStr = resultStr
|
41
|
+
|
42
|
+
@property
|
43
|
+
def resultNum(self):
|
44
|
+
if self.oldData and self.newData:
|
45
|
+
return MatchResultNumEnum.success
|
46
|
+
if self.oldData:
|
47
|
+
return MatchResultNumEnum.oldOnly
|
48
|
+
if self.newData:
|
49
|
+
return MatchResultNumEnum.newOnly
|
50
|
+
|
51
|
+
@property
|
52
|
+
def matchResultStr(self):
|
53
|
+
if self.resultStr:
|
54
|
+
return self.resultStr
|
55
|
+
if self.resultNum == MatchResultNumEnum.success:
|
56
|
+
return self.DEFAULT_SUCCESS_RESULT_STRING
|
57
|
+
if self.resultNum == MatchResultNumEnum.oldOnly:
|
58
|
+
return self.DEFAULT_OLD_ONLY_RESULT_STRING
|
59
|
+
if self.resultNum == MatchResultNumEnum.newOnly:
|
60
|
+
return self.DEFAULT_NEW_ONLY_RESULT_STRING
|
61
|
+
return "未知错误"
|
62
|
+
|
63
|
+
def toDict(self):
|
64
|
+
return {
|
65
|
+
"oldData": self.oldData,
|
66
|
+
"newData": self.newData,
|
67
|
+
"resultStr": self.resultStr,
|
68
|
+
"matchResultStr": self.matchResultStr,
|
69
|
+
}
|
70
|
+
|
71
|
+
def __repr__(self):
|
72
|
+
return "<{} {}>".format(self.__class__.__name__, self.matchResultStr)
|
73
|
+
|
74
|
+
|
75
|
+
class MatchHashData(object):
|
76
|
+
def __init__(self, data, hashList=None):
|
77
|
+
self.data = data
|
78
|
+
self.hashList = hashList
|
79
|
+
|
80
|
+
|
81
|
+
class MatchTool(object):
|
82
|
+
"""
|
83
|
+
匹配工具,自定义匹配规则,只要有一个匹配成功,则算匹配成功。
|
84
|
+
"""
|
85
|
+
|
86
|
+
matchRoles = []
|
87
|
+
matchResultCls = MatchResult
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def match(cls, oldList: list, newList: list):
|
91
|
+
# 避免污染数据源
|
92
|
+
oldList = copy.deepcopy(oldList)
|
93
|
+
newList = copy.deepcopy(newList)
|
94
|
+
|
95
|
+
result = []
|
96
|
+
# 循环是判断匹配规则,可能会thirdDeptId 等多个匹配规则 或判断
|
97
|
+
for matchRole in cls.matchRoles:
|
98
|
+
oldHashMap = cls.getHashMap(oldList, matchRole())
|
99
|
+
newHashMap = cls.getHashMap(newList, matchRole())
|
100
|
+
hashSet = set(oldHashMap.keys()) | set(newHashMap.keys())
|
101
|
+
for h in hashSet:
|
102
|
+
oldData = oldHashMap.get(h)
|
103
|
+
newData = newHashMap.get(h)
|
104
|
+
if oldData and newData:
|
105
|
+
# 匹配成功,只要匹配成功,这个数据就不用进行下个匹配规则
|
106
|
+
oldHashMap.pop(h)
|
107
|
+
newHashMap.pop(h)
|
108
|
+
result.append(cls.matchResultCls(oldData, newData, matchRole.successMsg))
|
109
|
+
|
110
|
+
# 通过一次匹配规则后,将已经匹配的数据对从列表中删除
|
111
|
+
oldList = list(oldHashMap.values())
|
112
|
+
newList = list(newHashMap.values())
|
113
|
+
|
114
|
+
# 经过所有匹配规则中,将未匹配到的数据也加到匹配结果中
|
115
|
+
for oldData in oldList:
|
116
|
+
# 只有老数据,没有新数据匹配
|
117
|
+
result.append(cls.matchResultCls(oldData=oldData))
|
118
|
+
|
119
|
+
for newData in newList:
|
120
|
+
# 只有新数据,没有老数据匹配
|
121
|
+
result.append(cls.matchResultCls(newData=newData))
|
122
|
+
|
123
|
+
return result
|
124
|
+
|
125
|
+
@classmethod
|
126
|
+
def getHashMap(cls, dataList, matchRole: BaseMatchRole) -> dict:
|
127
|
+
"""
|
128
|
+
将多个数据,按照hash规则转换成map
|
129
|
+
"""
|
130
|
+
hashMap = {}
|
131
|
+
for data in dataList:
|
132
|
+
h = matchRole.hash(data)
|
133
|
+
if h is None:
|
134
|
+
h = hash(str(data))
|
135
|
+
hashMap.setdefault(h, data)
|
136
|
+
return hashMap
|
jit_utils/network.py
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*-coding:utf-8-*-
|
2
|
+
"""
|
3
|
+
Created on 2024/10/31 11:08
|
4
|
+
|
5
|
+
@author: 'wuhao'
|
6
|
+
|
7
|
+
@desc:
|
8
|
+
"""
|
9
|
+
import socket
|
10
|
+
|
11
|
+
|
12
|
+
def checkConnect(host, port, timeout=0.3):
|
13
|
+
"""
|
14
|
+
检查ip和端口是否合法
|
15
|
+
:param ip:
|
16
|
+
:param port:
|
17
|
+
:return:
|
18
|
+
"""
|
19
|
+
try:
|
20
|
+
port = int(port)
|
21
|
+
except ValueError:
|
22
|
+
return False
|
23
|
+
if not (0 <= port <= 65535):
|
24
|
+
return False
|
25
|
+
if host.startswith("127") and host != "127.0.0.1":
|
26
|
+
return False
|
27
|
+
# 检查ip port 是否开启
|
28
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
29
|
+
s.settimeout(timeout)
|
30
|
+
try:
|
31
|
+
s.connect((host, port))
|
32
|
+
return True
|
33
|
+
except: # noqa E722
|
34
|
+
return False
|
35
|
+
finally:
|
36
|
+
s.close()
|
jit_utils/qrcode.py
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*-coding:utf-8-*-
|
2
|
+
"""
|
3
|
+
Created on 2025/01/21
|
4
|
+
|
5
|
+
@author: 臧韬
|
6
|
+
|
7
|
+
@desc: 二维码工具,暂时用于文件渲染
|
8
|
+
"""
|
9
|
+
import base64
|
10
|
+
from io import BytesIO
|
11
|
+
|
12
|
+
import qrcode
|
13
|
+
|
14
|
+
|
15
|
+
class Qrcode(object):
|
16
|
+
"""
|
17
|
+
二维码模块,暂用于文件渲染
|
18
|
+
"""
|
19
|
+
|
20
|
+
DEFAULT_ROW = 200
|
21
|
+
DEFAULT_COL = 200
|
22
|
+
DEFAULT_BOX_SIZE = 10
|
23
|
+
DEFAULT_BORDER = 4
|
24
|
+
|
25
|
+
def __init__(self, value: str, row=None, col=None):
|
26
|
+
self.value = value
|
27
|
+
# self.boxSize = boxSize or self.DEFAULT_BOX_SIZE
|
28
|
+
# self.border = border or self.DEFAULT_BORDER
|
29
|
+
self.row = row or self.DEFAULT_ROW
|
30
|
+
self.col = col or self.DEFAULT_COL
|
31
|
+
|
32
|
+
def toByte(self):
|
33
|
+
file = self.toFile()
|
34
|
+
data = file.read()
|
35
|
+
return data
|
36
|
+
|
37
|
+
def toFile(self):
|
38
|
+
qr = qrcode.QRCode(
|
39
|
+
version=1,
|
40
|
+
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
41
|
+
box_size=self.DEFAULT_BOX_SIZE,
|
42
|
+
border=self.DEFAULT_BORDER,
|
43
|
+
)
|
44
|
+
# 添加数据
|
45
|
+
qr.add_data(self.value)
|
46
|
+
qr.make(fit=True)
|
47
|
+
img = qr.make_image(fill_color="black", back_color="white")
|
48
|
+
img._img = img.get_image().resize((self.row, self.col))
|
49
|
+
imgBytes = BytesIO()
|
50
|
+
img.save(imgBytes)
|
51
|
+
imgBytes.seek(0)
|
52
|
+
return imgBytes
|
53
|
+
|
54
|
+
def toStr(self):
|
55
|
+
if not self.value:
|
56
|
+
return ""
|
57
|
+
return "<image:{}>".format(base64.b64encode(self.toByte()).decode("utf-8"))
|
58
|
+
|
59
|
+
def __str__(self):
|
60
|
+
return self.toStr()
|
jit_utils/signature.py
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
"""
|
2
|
+
Created on 2024/8/14 14:24
|
3
|
+
|
4
|
+
@author: 'wuhao'
|
5
|
+
|
6
|
+
@desc:
|
7
|
+
"""
|
8
|
+
|
9
|
+
import base64
|
10
|
+
import hashlib
|
11
|
+
import json
|
12
|
+
from decimal import Decimal
|
13
|
+
|
14
|
+
|
15
|
+
def formatNumber(obj):
|
16
|
+
if isinstance(obj, (float, int)) and not isinstance(obj, bool):
|
17
|
+
# 保证不使用科学计数法,保留 10 位精度
|
18
|
+
obj = Decimal(str(obj))
|
19
|
+
return format(obj, ".32f").rstrip("0").rstrip(".")
|
20
|
+
elif isinstance(obj, dict):
|
21
|
+
return {k: formatNumber(v) for k, v in obj.items()}
|
22
|
+
elif isinstance(obj, list):
|
23
|
+
return [formatNumber(i) for i in obj]
|
24
|
+
else:
|
25
|
+
return obj
|
26
|
+
|
27
|
+
|
28
|
+
def uniqueParams(paramDict, signKey="sign"):
|
29
|
+
parmList = []
|
30
|
+
for key in sorted(paramDict.keys(), reverse=True):
|
31
|
+
if key == signKey:
|
32
|
+
continue
|
33
|
+
parmList.append("{key}{val}".format(key=key, val=paramDict[key]))
|
34
|
+
return "".join(parmList), len(parmList)
|
35
|
+
|
36
|
+
|
37
|
+
def generateSignature(string, interval, divisor=1.4):
|
38
|
+
signString = hashlib.sha1(base64.b64encode(bytes(string, encoding="utf-8"))).hexdigest()
|
39
|
+
signStringLen = len(signString)
|
40
|
+
sampleCount = int(interval * divisor)
|
41
|
+
if sampleCount >= signStringLen:
|
42
|
+
return signString
|
43
|
+
cycle = int(signStringLen / sampleCount)
|
44
|
+
rr = "".join(signString[int(i * cycle)] for i in range(sampleCount))
|
45
|
+
return rr
|
46
|
+
|
47
|
+
|
48
|
+
def getSign(paramDict):
|
49
|
+
paramDict = formatNumber(paramDict)
|
50
|
+
for k, v in paramDict.items():
|
51
|
+
if isinstance(v, (list, dict)):
|
52
|
+
paramDict[k] = json.dumps(v, ensure_ascii=False, separators=(",", ":"))
|
53
|
+
else:
|
54
|
+
paramDict[k] = v
|
55
|
+
uString, lenValue = uniqueParams(paramDict)
|
56
|
+
return generateSignature(uString, lenValue)
|
jit_utils/spaceSender.py
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*-coding:utf-8-*-
|
2
|
+
"""
|
3
|
+
Created on 2024/04/28
|
4
|
+
|
5
|
+
@author: 臧韬
|
6
|
+
|
7
|
+
@desc: 默认描述
|
8
|
+
"""
|
9
|
+
import requests
|
10
|
+
|
11
|
+
from .signature import getSign
|
12
|
+
from .time import getTimestamp
|
13
|
+
|
14
|
+
|
15
|
+
class SpaceSender:
|
16
|
+
@classmethod
|
17
|
+
def sendApi(cls, entry, appId, element: str, func: str, argDict: dict, headers: dict = None):
|
18
|
+
"""
|
19
|
+
myapp之间接口转发
|
20
|
+
:param entry: 请求入口
|
21
|
+
:param appId: 发送给哪个app
|
22
|
+
:param element: 元素的fullName(通常是svc)
|
23
|
+
:param func: 具体调用的函数
|
24
|
+
:param argDict: 具体请求数据
|
25
|
+
:param headers: 具体请求头
|
26
|
+
:return:
|
27
|
+
"""
|
28
|
+
path = "/api/{appId}/{element}/{func}".format(
|
29
|
+
appId=appId.replace(".", "/"), element=element.replace(".", "/"), func=func
|
30
|
+
)
|
31
|
+
headers = headers or {}
|
32
|
+
url = "{entry}{path}".format(entry=entry, path=path)
|
33
|
+
token = headers.get("token", "")
|
34
|
+
timestamp = str(getTimestamp())
|
35
|
+
paramDict = {"path": path, "token": token, "timestamp": timestamp, **argDict}
|
36
|
+
sign = getSign(paramDict)
|
37
|
+
headers["sign"] = sign
|
38
|
+
headers["timestamp"] = timestamp
|
39
|
+
headers["Domain"] = entry
|
40
|
+
response = requests.post(url, json=argDict, headers=headers)
|
41
|
+
if response.status_code != 200:
|
42
|
+
raise Exception("请求失败")
|
43
|
+
respJson = response.json()
|
44
|
+
return respJson
|
jit_utils/string.py
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# -*-coding:utf-8-*-
|
2
|
+
"""
|
3
|
+
Created on 2023/10/25 20:04
|
4
|
+
|
5
|
+
@author: 'wuhao'
|
6
|
+
|
7
|
+
@desc: 简单说明
|
8
|
+
"""
|
9
|
+
import hashlib
|
10
|
+
import math
|
11
|
+
import os
|
12
|
+
import random
|
13
|
+
import re
|
14
|
+
import string
|
15
|
+
import uuid
|
16
|
+
|
17
|
+
CHARS = string.ascii_letters + string.digits
|
18
|
+
|
19
|
+
|
20
|
+
def randomString(size=8):
|
21
|
+
chars = random.choices(CHARS, k=size)
|
22
|
+
return "".join(chars)
|
23
|
+
|
24
|
+
|
25
|
+
def randomNum(size=6):
|
26
|
+
"""生成随机6位数字,用于手机号验证码"""
|
27
|
+
num = "%0{}d".format(size) % random.randint(0, 10**size - 1)
|
28
|
+
return num
|
29
|
+
|
30
|
+
|
31
|
+
def getUuidStr():
|
32
|
+
return str(uuid.uuid4()).replace("-", "")
|
33
|
+
|
34
|
+
|
35
|
+
def md5Bytes(b):
|
36
|
+
"""
|
37
|
+
md5加密
|
38
|
+
:param b: 文件的二进制数据
|
39
|
+
:return: str: md5字符串
|
40
|
+
"""
|
41
|
+
return hashlib.md5(b).hexdigest()
|
42
|
+
|
43
|
+
|
44
|
+
def md5Str(normalStr, encodeType="utf-8"):
|
45
|
+
"""
|
46
|
+
md5加密
|
47
|
+
:param normalStr: 普通python字符串
|
48
|
+
:param encodeType: 编码方式
|
49
|
+
:return str: md5字符串
|
50
|
+
"""
|
51
|
+
return md5Bytes(normalStr.encode(encodeType))
|
52
|
+
|
53
|
+
|
54
|
+
def lowercase(name):
|
55
|
+
if not name:
|
56
|
+
return ""
|
57
|
+
return name[0].lower() + name[1:]
|
58
|
+
|
59
|
+
|
60
|
+
def capitalize(name):
|
61
|
+
if not name:
|
62
|
+
return ""
|
63
|
+
return name[0].upper() + name[1:]
|
64
|
+
|
65
|
+
|
66
|
+
def getFileMd5(filename):
|
67
|
+
if not os.path.isfile(filename):
|
68
|
+
return
|
69
|
+
myhash = hashlib.md5()
|
70
|
+
f = open(filename, "rb")
|
71
|
+
while True:
|
72
|
+
b = f.read(4096)
|
73
|
+
if not b:
|
74
|
+
break
|
75
|
+
myhash.update(b)
|
76
|
+
f.close()
|
77
|
+
return myhash.hexdigest()
|
78
|
+
|
79
|
+
|
80
|
+
def getRandomField(k=4):
|
81
|
+
character = string.ascii_lowercase + string.digits
|
82
|
+
charArray = random.choices(character, k=k)
|
83
|
+
res = "fk" + "".join(charArray)
|
84
|
+
return res
|
85
|
+
|
86
|
+
|
87
|
+
def getRandom(k=8):
|
88
|
+
character = string.ascii_lowercase + string.digits
|
89
|
+
charArray = random.choices(character, k=k)
|
90
|
+
return "".join(charArray)
|
91
|
+
|
92
|
+
|
93
|
+
def genrSublist(orgList, sliceSize):
|
94
|
+
"""
|
95
|
+
for tempList in genrSublist(range(20), 5):
|
96
|
+
print(tempList)
|
97
|
+
|
98
|
+
[0, 1, 2, 3, 4]
|
99
|
+
[5, 6, 7, 8, 9]
|
100
|
+
[10, 11, 12, 13, 14]
|
101
|
+
[15, 16, 17, 18, 19]
|
102
|
+
"""
|
103
|
+
sliceCount = int(math.ceil(len(orgList) / float(sliceSize)))
|
104
|
+
for i in range(sliceCount):
|
105
|
+
yield orgList[i * sliceSize : (i + 1) * sliceSize]
|
106
|
+
|
107
|
+
|
108
|
+
def renderTemplateString(source, **context):
|
109
|
+
# 使用正则表达式查找所有的变量 {{var_name}}
|
110
|
+
pattern = r"\{\{(\w+)\}\}"
|
111
|
+
|
112
|
+
def replaceVar(match):
|
113
|
+
var_name = match.group(1) # 获取变量名
|
114
|
+
return str(context.get(var_name, "")) # 从上下文中获取变量值,如果不存在则返回空字符串
|
115
|
+
|
116
|
+
rendered = re.sub(pattern, replaceVar, source)
|
117
|
+
|
118
|
+
return rendered
|