gou2tool 0.2.11__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.
- gou2tool-0.2.11.dist-info/METADATA +49 -0
- gou2tool-0.2.11.dist-info/RECORD +28 -0
- gou2tool-0.2.11.dist-info/WHEEL +5 -0
- gou2tool-0.2.11.dist-info/licenses/LICENSE +201 -0
- gou2tool-0.2.11.dist-info/top_level.txt +2 -0
- goutool/__init__.py +13 -0
- goutool/util/__init__.py +50 -0
- goutool/util/address_util.py +136 -0
- goutool/util/date_util.py +0 -0
- goutool/util/db_template_util.py +121 -0
- goutool/util/email_util.py +9 -0
- goutool/util/env_util.py +19 -0
- goutool/util/file_util.py +67 -0
- goutool/util/id_util.py +11 -0
- goutool/util/path_util.py +203 -0
- goutool/util/phone_util.py +25 -0
- goutool/util/sql_util.py +208 -0
- goutool/util/str_util.py +51 -0
- goutool/util/time_util.py +0 -0
- goutool/util/tree_util.py +202 -0
- goutool/util/web_hook_util.py +111 -0
- tests/__init__.py +0 -0
- tests/id_util.py +17 -0
- tests/path_util.py +11 -0
- tests/phone_util.py +18 -0
- tests/sql_util.py +21 -0
- tests/str_util.py +15 -0
- tests/web_hook_util.py +22 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Callable
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TreeUtil:
|
|
6
|
+
"""
|
|
7
|
+
树结构工具类
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def list_to_tree(list_data: List[Dict[str, Any]], config: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
|
12
|
+
"""
|
|
13
|
+
数组转树结构
|
|
14
|
+
:param list_data: 原始列表数据
|
|
15
|
+
:param config: 配置参数
|
|
16
|
+
:return: 树结构数据
|
|
17
|
+
"""
|
|
18
|
+
if config is None:
|
|
19
|
+
config = {}
|
|
20
|
+
|
|
21
|
+
# 初始化参数
|
|
22
|
+
config.setdefault("id", "id")
|
|
23
|
+
config.setdefault("parent_id", "parent_id")
|
|
24
|
+
config.setdefault("children", "children")
|
|
25
|
+
|
|
26
|
+
# 新建映射关系
|
|
27
|
+
map_dict = {}
|
|
28
|
+
filtered_list = []
|
|
29
|
+
|
|
30
|
+
for item in list_data:
|
|
31
|
+
# 跳过开头的上级节点
|
|
32
|
+
if "level" in config and "init_level" in config and config.get("level") in item:
|
|
33
|
+
if item[config["level"]] < config["init_level"]:
|
|
34
|
+
continue
|
|
35
|
+
map_dict[item[config["id"]]] = item
|
|
36
|
+
filtered_list.append(item)
|
|
37
|
+
|
|
38
|
+
# 树数据
|
|
39
|
+
tree = []
|
|
40
|
+
for item in filtered_list:
|
|
41
|
+
# 跳过开头的上级节点
|
|
42
|
+
if "level" in config and "init_level" in config and config.get("level") in item:
|
|
43
|
+
if item[config["level"]] < config["init_level"]:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# 获取上级节点
|
|
47
|
+
parent_id = None
|
|
48
|
+
if item[config["parent_id"]] in map_dict:
|
|
49
|
+
parent_id = item[config["parent_id"]]
|
|
50
|
+
else:
|
|
51
|
+
# 通过完整id字段 查找上级节点 补充缺省上级数据
|
|
52
|
+
if "full_id" in config and config["full_id"] in item:
|
|
53
|
+
ids = item[config["full_id"]].split(",")
|
|
54
|
+
for id_val in ids:
|
|
55
|
+
if item[config["id"]] != id_val and id_val in map_dict:
|
|
56
|
+
parent_id = id_val
|
|
57
|
+
|
|
58
|
+
# 判断上级节点是否存在 不存在则添加到顶级节点
|
|
59
|
+
if parent_id is not None:
|
|
60
|
+
# 添加到子节点
|
|
61
|
+
if config["children"] not in map_dict[parent_id]:
|
|
62
|
+
map_dict[parent_id][config["children"]] = []
|
|
63
|
+
map_dict[parent_id][config["children"]].append(item)
|
|
64
|
+
else:
|
|
65
|
+
tree.append(item)
|
|
66
|
+
|
|
67
|
+
def recursion(data, next_func, node):
|
|
68
|
+
result = []
|
|
69
|
+
for item in data:
|
|
70
|
+
new_item = copy.deepcopy(item)
|
|
71
|
+
if "level" in config:
|
|
72
|
+
new_item[config["level"]] = node["level"]
|
|
73
|
+
|
|
74
|
+
if config["children"] in new_item:
|
|
75
|
+
if "level" in config and "max_level" in config:
|
|
76
|
+
if node["level"] + 1 > config["max_level"]:
|
|
77
|
+
del new_item[config["children"]]
|
|
78
|
+
else:
|
|
79
|
+
new_item[config["children"]] = next_func(new_item[config["children"]], next_func)
|
|
80
|
+
else:
|
|
81
|
+
new_item[config["children"]] = next_func(new_item[config["children"]], next_func)
|
|
82
|
+
result.append(new_item)
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
return TreeUtil.recursion(tree, recursion)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def tree_to_list(tree: List[Dict[str, Any]], config: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
|
89
|
+
"""
|
|
90
|
+
树结构转数组
|
|
91
|
+
:param tree: 树结构数据
|
|
92
|
+
:param config: 配置参数
|
|
93
|
+
:return: 列表数据
|
|
94
|
+
"""
|
|
95
|
+
if config is None:
|
|
96
|
+
config = {}
|
|
97
|
+
|
|
98
|
+
config.setdefault("children", "children")
|
|
99
|
+
config.setdefault("unset_children", True)
|
|
100
|
+
|
|
101
|
+
def list_handle(tree_data, conf, handler):
|
|
102
|
+
result = []
|
|
103
|
+
for item in tree_data:
|
|
104
|
+
new_item = copy.deepcopy(item)
|
|
105
|
+
children = None
|
|
106
|
+
if conf["children"] in new_item:
|
|
107
|
+
children = new_item[conf["children"]]
|
|
108
|
+
if conf.get("unset_children", True):
|
|
109
|
+
del new_item[conf["children"]]
|
|
110
|
+
else:
|
|
111
|
+
children = None
|
|
112
|
+
|
|
113
|
+
result.append(new_item)
|
|
114
|
+
if isinstance(children, list):
|
|
115
|
+
result.extend(handler(children, conf, handler))
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
return list_handle(tree, config, list_handle)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def get_max_depth(tree: List[Dict[str, Any]], config: Dict[str, Any] = None) -> int:
|
|
122
|
+
"""
|
|
123
|
+
获取树节点的最大深度
|
|
124
|
+
:param tree: 树结构数据
|
|
125
|
+
:param config: 配置参数
|
|
126
|
+
:return: 最大深度
|
|
127
|
+
"""
|
|
128
|
+
if config is None:
|
|
129
|
+
config = {}
|
|
130
|
+
|
|
131
|
+
config.setdefault("id", "id")
|
|
132
|
+
config.setdefault("children", "children")
|
|
133
|
+
|
|
134
|
+
max_depth = 0
|
|
135
|
+
|
|
136
|
+
def callback(item, node):
|
|
137
|
+
nonlocal max_depth
|
|
138
|
+
if node['level'] + 1 > max_depth:
|
|
139
|
+
max_depth = node['level'] + 1
|
|
140
|
+
|
|
141
|
+
TreeUtil.recursion_each(tree, callback, config)
|
|
142
|
+
return max_depth
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def recursion(param_or_tree: List[Dict[str, Any]], main_next: Callable,
|
|
146
|
+
node: Dict[str, Any] = None) -> Any:
|
|
147
|
+
"""
|
|
148
|
+
递归处理树形数据的核心方法
|
|
149
|
+
:param param_or_tree: 待处理的数据
|
|
150
|
+
:param main_next: 处理函数
|
|
151
|
+
:param node: 节点信息
|
|
152
|
+
:return: 处理后的结果
|
|
153
|
+
"""
|
|
154
|
+
if node is None:
|
|
155
|
+
node = {"level": 0, "top": True, "parent": None, "parents": []}
|
|
156
|
+
|
|
157
|
+
def next_func(param_or_tree_inner, parent):
|
|
158
|
+
new_node = {
|
|
159
|
+
"level": node["level"] + 1,
|
|
160
|
+
"top": False,
|
|
161
|
+
"parent": parent,
|
|
162
|
+
"parents": node["parents"] + [parent] if parent else []
|
|
163
|
+
}
|
|
164
|
+
return TreeUtil.recursion(param_or_tree_inner, main_next, new_node)
|
|
165
|
+
|
|
166
|
+
return main_next(param_or_tree, next_func, node)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def recursion_each(tree: List[Dict[str, Any]], func: Callable,
|
|
170
|
+
config: Dict[str, Any] = None) -> List[Dict[str, Any]]:
|
|
171
|
+
"""
|
|
172
|
+
遍历树形结构中的每个节点并执行回调函数
|
|
173
|
+
:param tree: 树形结构数据
|
|
174
|
+
:param func: 回调函数
|
|
175
|
+
:param config: 配置参数
|
|
176
|
+
:return: 处理后的树
|
|
177
|
+
"""
|
|
178
|
+
if config is None:
|
|
179
|
+
config = {}
|
|
180
|
+
|
|
181
|
+
config.setdefault("children", "children")
|
|
182
|
+
|
|
183
|
+
def tree_handle(tree_data, handler, node=None):
|
|
184
|
+
if node is None:
|
|
185
|
+
node = {"level": 0, "top": True, "parent": None, "parents": []}
|
|
186
|
+
|
|
187
|
+
for item in tree_data:
|
|
188
|
+
func(item, node)
|
|
189
|
+
if config["children"] in item and isinstance(item[config["children"]], list):
|
|
190
|
+
handler(
|
|
191
|
+
item[config["children"]],
|
|
192
|
+
handler,
|
|
193
|
+
{
|
|
194
|
+
"parent": item,
|
|
195
|
+
"parents": node["parents"] + [item],
|
|
196
|
+
"level": node["level"] + 1,
|
|
197
|
+
"top": False,
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
tree_handle(tree, tree_handle)
|
|
202
|
+
return tree
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import hmac
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import requests
|
|
6
|
+
import base64
|
|
7
|
+
import urllib.parse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WebHookUtil:
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def send(url, data, secret=None):
|
|
14
|
+
"""
|
|
15
|
+
发送Webhook请求
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
url (str): Webhook地址
|
|
19
|
+
data (dict): 发送的数据
|
|
20
|
+
secret (str, optional): 签名密钥
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
dict: 响应结果
|
|
24
|
+
"""
|
|
25
|
+
headers = {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'User-Agent': 'WebHookUtil/1.0'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# 如果提供了secret,则添加签名
|
|
31
|
+
if secret:
|
|
32
|
+
timestamp = str(int(time.time() * 1000))
|
|
33
|
+
string_to_sign = timestamp + "\n" + secret
|
|
34
|
+
hmac_code = hmac.new(
|
|
35
|
+
secret.encode('utf-8'),
|
|
36
|
+
string_to_sign.encode('utf-8'),
|
|
37
|
+
hashlib.sha256
|
|
38
|
+
).digest()
|
|
39
|
+
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
|
|
40
|
+
|
|
41
|
+
# 将签名参数添加到URL中而不是请求头
|
|
42
|
+
separator = '&' if '?' in url else '?'
|
|
43
|
+
url_with_sign = f"{url}{separator}timestamp={timestamp}&sign={sign}"
|
|
44
|
+
else:
|
|
45
|
+
url_with_sign = url
|
|
46
|
+
|
|
47
|
+
response = requests.post(url_with_sign, headers=headers, data=json.dumps(data), timeout=30)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def send_text(url, content, secret=None):
|
|
52
|
+
"""
|
|
53
|
+
发送文本消息
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
url (str): Webhook地址
|
|
57
|
+
content (str): 消息内容
|
|
58
|
+
secret (str, optional): 签名密钥
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: 响应结果
|
|
62
|
+
"""
|
|
63
|
+
WebHookUtil.send(url, {"msgtype": "text", "text": {"content": content}}, secret)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def send_markdown(url, title, text, secret=None):
|
|
67
|
+
"""
|
|
68
|
+
发送Markdown消息
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
url (str): Webhook地址
|
|
72
|
+
title (str): 消息标题
|
|
73
|
+
text (str): 消息文本
|
|
74
|
+
secret (str, optional): 签名密钥
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
dict: 响应结果
|
|
78
|
+
"""
|
|
79
|
+
WebHookUtil.send(url, {"msgtype": "markdown", "markdown": {"title": title, "text": text}}, secret)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def send_link(url, title, text, link, secret=None):
|
|
83
|
+
"""
|
|
84
|
+
发送链接消息
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
url (str): Webhook地址
|
|
88
|
+
title (str): 消息标题
|
|
89
|
+
text (str): 消息文本
|
|
90
|
+
link (str): 消息链接
|
|
91
|
+
secret (str, optional): 签名密钥
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict: 响应结果
|
|
95
|
+
"""
|
|
96
|
+
WebHookUtil.send(url, {"msgtype": "link", "link": {"title": title, "text": text}, "messageUrl": link}, secret)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def send_image(url, image_base64, secret=None):
|
|
100
|
+
"""
|
|
101
|
+
发送图片消息
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
url (str): Webhook地址
|
|
105
|
+
image_base64 (str): 图片
|
|
106
|
+
secret (str, optional): 签名密钥
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict: 响应结果
|
|
110
|
+
"""
|
|
111
|
+
WebHookUtil.send(url, {"msgtype": "image", "image": {"base64": image_base64}}, secret)
|
tests/__init__.py
ADDED
|
File without changes
|
tests/id_util.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from src.main.py.gou2tool.util.IdUtil import IdUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDemo(unittest.TestCase):
|
|
7
|
+
def test_simple_uuid(self):
|
|
8
|
+
print(IdUtil.simple_uuid())
|
|
9
|
+
self.assertTrue(True)
|
|
10
|
+
|
|
11
|
+
def test_random_uuid(self):
|
|
12
|
+
print(IdUtil.random_uuid())
|
|
13
|
+
self.assertTrue(True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == '__main__':
|
|
17
|
+
unittest.main()
|
tests/path_util.py
ADDED
tests/phone_util.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from src.main.py.gou2tool.util.PhoneUtil import PhoneUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDemo(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_is_phone(self):
|
|
9
|
+
print(PhoneUtil.is_phone('11571631036'))
|
|
10
|
+
self.assertTrue(True)
|
|
11
|
+
|
|
12
|
+
def test_is_tel(self):
|
|
13
|
+
print(PhoneUtil.is_tel('111-58597128'))
|
|
14
|
+
self.assertTrue(True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if __name__ == '__main__':
|
|
18
|
+
unittest.main()
|
tests/sql_util.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from src.main.py.gou2tool.util.SqlUtil import SQLUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDemo(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_insert(self):
|
|
9
|
+
print(SQLUtil.update("crm_organizations", {
|
|
10
|
+
"lat": 1,
|
|
11
|
+
"lon": 1,
|
|
12
|
+
"name": None,
|
|
13
|
+
"desc": "'adq'qa",
|
|
14
|
+
"phone": ["11", "22'2"],
|
|
15
|
+
"dict": {'a': 1, 'b': "a'"}
|
|
16
|
+
}, {"id": 1, "ide": "2'"}))
|
|
17
|
+
self.assertTrue(True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == '__main__':
|
|
21
|
+
unittest.main()
|
tests/str_util.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from src.main.py.gou2tool.util.StrUtil import StrUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDemo(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_insert(self):
|
|
9
|
+
print(StrUtil.calculate_similarity("湖北省武汉市武昌区楚汉路18号武汉中央文化区k4地块一期k4-2-5栋1-2层14-15室",
|
|
10
|
+
"湖北省武汉市武昌区楚汉路武汉中央文化区地块一期k4-1-1栋"))
|
|
11
|
+
self.assertTrue(True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if __name__ == '__main__':
|
|
15
|
+
unittest.main()
|
tests/web_hook_util.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from src.main.py.gou2tool.util.WebHookUtil import WebHookUtil
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDemo(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_insert(self):
|
|
9
|
+
WebHookUtil.send(
|
|
10
|
+
"https://oapi.dingtalk.com/robot/send?access_token=6eb7c4a05da8552f740e9912b488a91a56b958eb191ab2c84783389ac6f8a559",
|
|
11
|
+
{
|
|
12
|
+
'msgtype': 'text',
|
|
13
|
+
'text': {
|
|
14
|
+
'content': "1111"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"SEC745aba3ccb27eba9e72e73de98649624bd9630085e708eb939456fa5ca2664c4")
|
|
18
|
+
self.assertTrue(True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
if __name__ == '__main__':
|
|
22
|
+
unittest.main()
|