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,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FileUtil:
|
|
6
|
+
|
|
7
|
+
@staticmethod
|
|
8
|
+
def write(path, content):
|
|
9
|
+
"""写入内容到文件(覆盖模式)"""
|
|
10
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
11
|
+
with open(path, 'w', encoding='utf-8') as file:
|
|
12
|
+
file.write(content)
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def append(path, content):
|
|
16
|
+
"""
|
|
17
|
+
追加内容到文件末尾
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
path (str): 文件路径
|
|
21
|
+
content (str): 要追加的内容
|
|
22
|
+
"""
|
|
23
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
24
|
+
with open(path, 'a', encoding='utf-8') as file:
|
|
25
|
+
file.write(content)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def append_line(path, content):
|
|
29
|
+
"""
|
|
30
|
+
追加一行内容到文件末尾(自动添加换行符)
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
path (str): 文件路径
|
|
34
|
+
content (str): 要追加的行内容
|
|
35
|
+
"""
|
|
36
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
37
|
+
with open(path, 'a', encoding='utf-8') as file:
|
|
38
|
+
file.write(content + '\n')
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def read_json(path):
|
|
42
|
+
"""
|
|
43
|
+
读取JSON文件并返回解析后的数据
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path (str): JSON文件路径
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
dict|list: 解析后的JSON数据
|
|
50
|
+
"""
|
|
51
|
+
with open(path, 'r', encoding='utf-8') as file:
|
|
52
|
+
return json.load(file)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def write_json(path, data, ensure_ascii=False, indent=2):
|
|
56
|
+
"""
|
|
57
|
+
将数据写入JSON文件
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
path (str): JSON文件路径
|
|
61
|
+
data (dict|list): 要写入的JSON数据
|
|
62
|
+
ensure_ascii (bool): 是否确保ASCII编码,默认False
|
|
63
|
+
indent (int): 缩进空格数,默认2
|
|
64
|
+
"""
|
|
65
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
66
|
+
with open(path, 'w', encoding='utf-8') as file:
|
|
67
|
+
json.dump(data, file, ensure_ascii=ensure_ascii, indent=indent)
|
goutool/util/id_util.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import tempfile
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PathUtil:
|
|
9
|
+
"""路径工具类"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def path(*paths):
|
|
13
|
+
"""
|
|
14
|
+
构建路径
|
|
15
|
+
:param paths: 路径组件
|
|
16
|
+
:return: 完整路径
|
|
17
|
+
"""
|
|
18
|
+
# 连接路径组件
|
|
19
|
+
path = "/".join(paths)
|
|
20
|
+
|
|
21
|
+
# 处理特殊前缀
|
|
22
|
+
if path.startswith('project:'):
|
|
23
|
+
path = path.replace("project:", PathUtil.project_path(), 1)
|
|
24
|
+
elif path.startswith('goutool:'):
|
|
25
|
+
path = path.replace("goutool:", PathUtil.goutool_path(), 1)
|
|
26
|
+
elif path.startswith('gou2tool:'):
|
|
27
|
+
path = path.replace("gou2tool:", PathUtil.goutool_path(), 1)
|
|
28
|
+
elif path.startswith('system-temp:'):
|
|
29
|
+
path = path.replace("system-temp:", tempfile.gettempdir(), 1)
|
|
30
|
+
elif path.startswith('create-system-temp:'):
|
|
31
|
+
temp_dir = tempfile.gettempdir()
|
|
32
|
+
filename = path.replace("create-system-temp:", "")
|
|
33
|
+
path = os.path.join(temp_dir, filename)
|
|
34
|
+
elif path.startswith('create-system-temp-file:*'):
|
|
35
|
+
temp_dir = tempfile.gettempdir()
|
|
36
|
+
if path.startswith('create-system-temp-file:*.*'):
|
|
37
|
+
ext = path.replace("create-system-temp-file:*.*", ".", 1)
|
|
38
|
+
elif path.startswith('create-system-temp-file:*'):
|
|
39
|
+
ext = ".tmp"
|
|
40
|
+
else:
|
|
41
|
+
ext = ""
|
|
42
|
+
filename = str(uuid.uuid4()) + ext
|
|
43
|
+
path = os.path.join(temp_dir, filename)
|
|
44
|
+
elif path.startswith('vendor:'):
|
|
45
|
+
path = path.replace("vendor:", os.path.join(PathUtil.project_path(), "vendor"), 1)
|
|
46
|
+
|
|
47
|
+
# 规范化路径分隔符
|
|
48
|
+
lists = [part for part in re.split(r'([/\\]+)', path) if part]
|
|
49
|
+
|
|
50
|
+
# Linux环境下处理包含空格的路径
|
|
51
|
+
if os.name != 'nt': # 非Windows系统
|
|
52
|
+
parts = re.split(r'([\\/]+)', path)
|
|
53
|
+
for i, part in enumerate(parts):
|
|
54
|
+
if re.search(r'\s', part) and not (part.startswith('"') and part.endswith('"')):
|
|
55
|
+
parts[i] = f'"{part}"'
|
|
56
|
+
path = "".join(parts)
|
|
57
|
+
|
|
58
|
+
return "".join(lists)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def raw_path(path):
|
|
62
|
+
"""
|
|
63
|
+
获取原始路径(去除引号)
|
|
64
|
+
:param path: 路径
|
|
65
|
+
:return: 原始路径
|
|
66
|
+
"""
|
|
67
|
+
return path.replace('"', '')
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def exist(path):
|
|
71
|
+
"""
|
|
72
|
+
检查文件或目录是否存在
|
|
73
|
+
:param path: 路径
|
|
74
|
+
:return: 是否存在
|
|
75
|
+
"""
|
|
76
|
+
return os.path.exists(path)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def is_path(path):
|
|
80
|
+
"""
|
|
81
|
+
判断是否是有效路径格式
|
|
82
|
+
:param path: 路径
|
|
83
|
+
:return: 是否是路径
|
|
84
|
+
"""
|
|
85
|
+
pattern = r'^(?:\/{2})?[a-zA-Z0-9._-]+(?:\/[a-zA-Z0-9._-]+)*$'
|
|
86
|
+
return bool(re.match(pattern, path))
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def project_path():
|
|
90
|
+
"""
|
|
91
|
+
获取应用根目录
|
|
92
|
+
:return: 项目路径
|
|
93
|
+
"""
|
|
94
|
+
# 这里需要根据实际项目结构调整
|
|
95
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
96
|
+
project = os.path.dirname(current_dir) # 根据实际情况调整层级
|
|
97
|
+
|
|
98
|
+
project_file = os.path.join(project, "composer.json")
|
|
99
|
+
project_vendor = os.path.join(project, "vendor")
|
|
100
|
+
goutool_file = os.path.join(project, "vendor", "wl4837", "goutool", "composer.json")
|
|
101
|
+
|
|
102
|
+
if (os.path.exists(project_file) and os.path.isfile(project_file) and
|
|
103
|
+
os.path.exists(project_vendor) and os.path.isdir(project_vendor) and
|
|
104
|
+
os.path.exists(goutool_file) and os.path.isfile(goutool_file)):
|
|
105
|
+
return project
|
|
106
|
+
else:
|
|
107
|
+
return PathUtil.goutool_path()
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def goutool_path():
|
|
111
|
+
"""
|
|
112
|
+
获取框架目录
|
|
113
|
+
:return: 框架路径
|
|
114
|
+
"""
|
|
115
|
+
path = PathUtil.parent(os.path.abspath(__file__), 6)
|
|
116
|
+
file_path = os.path.join(path, "setup.py")
|
|
117
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
118
|
+
return path
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def project_composer_path():
|
|
123
|
+
"""
|
|
124
|
+
获取项目composer.json路径
|
|
125
|
+
:return: composer.json路径
|
|
126
|
+
"""
|
|
127
|
+
project_path = PathUtil.project_path()
|
|
128
|
+
if project_path is False:
|
|
129
|
+
return False
|
|
130
|
+
file_path = os.path.join(project_path, "composer.json")
|
|
131
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
132
|
+
return file_path
|
|
133
|
+
else:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def project_package_path():
|
|
138
|
+
"""
|
|
139
|
+
获取项目package.json路径
|
|
140
|
+
:return: package.json路径
|
|
141
|
+
"""
|
|
142
|
+
project_path = PathUtil.project_path()
|
|
143
|
+
if project_path is False:
|
|
144
|
+
return False
|
|
145
|
+
file_path = os.path.join(project_path, "package.json")
|
|
146
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
147
|
+
return file_path
|
|
148
|
+
else:
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def project_env_path():
|
|
153
|
+
"""
|
|
154
|
+
获取项目.env文件路径
|
|
155
|
+
:return: .env文件路径
|
|
156
|
+
"""
|
|
157
|
+
project_path = PathUtil.project_path()
|
|
158
|
+
if project_path is False:
|
|
159
|
+
return False
|
|
160
|
+
file_path = os.path.join(project_path, ".env")
|
|
161
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
162
|
+
return file_path
|
|
163
|
+
else:
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def goutool_composer_path():
|
|
168
|
+
"""
|
|
169
|
+
获取框架composer.json路径
|
|
170
|
+
:return: composer.json路径
|
|
171
|
+
"""
|
|
172
|
+
goutool_path = PathUtil.goutool_path()
|
|
173
|
+
if goutool_path is False:
|
|
174
|
+
return False
|
|
175
|
+
file_path = os.path.join(goutool_path, "composer.json")
|
|
176
|
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
|
177
|
+
return file_path
|
|
178
|
+
else:
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def name(path):
|
|
183
|
+
"""
|
|
184
|
+
获取路径名称
|
|
185
|
+
:param path: 路径
|
|
186
|
+
:return: 路径名称
|
|
187
|
+
"""
|
|
188
|
+
path = PathUtil.path(path)
|
|
189
|
+
return os.path.basename(path)
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def parent(path, level=1):
|
|
193
|
+
"""
|
|
194
|
+
获取上级路径
|
|
195
|
+
:param path: 路径
|
|
196
|
+
:param level: 上级层数
|
|
197
|
+
:return: 上级路径
|
|
198
|
+
"""
|
|
199
|
+
path = PathUtil.path(path)
|
|
200
|
+
# 根据level层数逐级获取上级目录
|
|
201
|
+
for _ in range(level):
|
|
202
|
+
path = os.path.dirname(path)
|
|
203
|
+
return path
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class PhoneUtil:
|
|
4
|
+
|
|
5
|
+
@staticmethod
|
|
6
|
+
def is_phone(value):
|
|
7
|
+
if not value:
|
|
8
|
+
return False
|
|
9
|
+
return PhoneUtil.is_mobile(value) or PhoneUtil.is_tel(value)
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def is_mobile(value):
|
|
13
|
+
if not value:
|
|
14
|
+
return False
|
|
15
|
+
return bool(re.match(r'^1[3-9]\d{9}$', value))
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def is_tel(value):
|
|
19
|
+
if not value:
|
|
20
|
+
return False
|
|
21
|
+
return bool(re.match(r'^(0\d{2,3}[- ]?)?\d{7,8}(-\d{1,6})?$', value))
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def hide_between(phone):
|
|
25
|
+
return phone[0:3] + '****' + phone[-4:]
|
goutool/util/sql_util.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
class SQLUtil:
|
|
4
|
+
"""SQL语句生成工具类"""
|
|
5
|
+
|
|
6
|
+
@staticmethod
|
|
7
|
+
def escape_string(val):
|
|
8
|
+
"""
|
|
9
|
+
转义SQL字符串中的特殊字符
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
val: 要转义的字符串
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
转义后的字符串
|
|
16
|
+
"""
|
|
17
|
+
if isinstance(val, str):
|
|
18
|
+
# 转义单引号
|
|
19
|
+
return val.replace("'", "''")
|
|
20
|
+
return val
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def format_value(val):
|
|
24
|
+
"""
|
|
25
|
+
格式化值为SQL字符串
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
val: 要格式化的值
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
格式化后的SQL值字符串
|
|
32
|
+
"""
|
|
33
|
+
if val is None:
|
|
34
|
+
return "NULL"
|
|
35
|
+
elif isinstance(val, str):
|
|
36
|
+
# 转义字符串并加引号
|
|
37
|
+
escaped_val = SQLUtil.escape_string(val)
|
|
38
|
+
return f"'{escaped_val}'"
|
|
39
|
+
elif isinstance(val, (list, tuple)):
|
|
40
|
+
# 数组转为逗号分隔的字符串
|
|
41
|
+
escaped_val = SQLUtil.escape_string(','.join(map(str, val)))
|
|
42
|
+
return f"'{escaped_val}'"
|
|
43
|
+
elif isinstance(val, dict):
|
|
44
|
+
# 字典转为JSON字符串
|
|
45
|
+
escaped_val = SQLUtil.escape_string(json.dumps(val, ensure_ascii=False))
|
|
46
|
+
return f"'{escaped_val}'"
|
|
47
|
+
else:
|
|
48
|
+
return str(val)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def insert(table: str, data: dict, returning: str = None) -> str:
|
|
52
|
+
"""
|
|
53
|
+
生成INSERT SQL语句
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
table: 表名
|
|
57
|
+
data: 要插入的数据字典 {列名: 值}
|
|
58
|
+
returning: 返回指定列的值(可选)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
INSERT SQL语句
|
|
62
|
+
"""
|
|
63
|
+
columns = ', '.join(data.keys())
|
|
64
|
+
|
|
65
|
+
# 直接使用值而不是占位符
|
|
66
|
+
values = []
|
|
67
|
+
for val in data.values():
|
|
68
|
+
values.append(SQLUtil.format_value(val))
|
|
69
|
+
|
|
70
|
+
placeholders = ', '.join(values)
|
|
71
|
+
sql = f"INSERT INTO {table} ({columns}) VALUES ({placeholders})"
|
|
72
|
+
|
|
73
|
+
if returning:
|
|
74
|
+
sql += f" RETURNING {returning}"
|
|
75
|
+
|
|
76
|
+
return sql
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def batch_insert(table: str, data_list: list) -> str:
|
|
80
|
+
"""
|
|
81
|
+
生成批量INSERT SQL语句
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
table: 表名
|
|
85
|
+
data_list: 要插入的数据列表 [{列名: 值}, ...]
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
批量INSERT SQL语句
|
|
89
|
+
"""
|
|
90
|
+
if not data_list:
|
|
91
|
+
return ""
|
|
92
|
+
|
|
93
|
+
columns = ', '.join(data_list[0].keys())
|
|
94
|
+
sql = f"INSERT INTO {table} ({columns}) VALUES "
|
|
95
|
+
|
|
96
|
+
# 构建所有值的列表
|
|
97
|
+
value_groups = []
|
|
98
|
+
for data in data_list:
|
|
99
|
+
values = []
|
|
100
|
+
for val in data.values():
|
|
101
|
+
values.append(SQLUtil.format_value(val))
|
|
102
|
+
value_groups.append(f"({', '.join(values)})")
|
|
103
|
+
|
|
104
|
+
sql += ', '.join(value_groups)
|
|
105
|
+
return sql
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def update(table: str, data: dict, where: dict = None, where_clause: str = None) -> str:
|
|
109
|
+
"""
|
|
110
|
+
生成UPDATE SQL语句
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
table: 表名
|
|
114
|
+
data: 要更新的数据字典 {列名: 值}
|
|
115
|
+
where: WHERE条件字典 {列名: 值}
|
|
116
|
+
where_clause: 自定义WHERE子句(可选)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
UPDATE SQL语句
|
|
120
|
+
"""
|
|
121
|
+
if not data:
|
|
122
|
+
raise ValueError("更新数据不能为空")
|
|
123
|
+
|
|
124
|
+
# 直接使用值而不是占位符
|
|
125
|
+
set_parts = []
|
|
126
|
+
for col, val in data.items():
|
|
127
|
+
formatted_val = SQLUtil.format_value(val)
|
|
128
|
+
set_parts.append(f"`{col}` = {formatted_val}")
|
|
129
|
+
|
|
130
|
+
set_clause = ', '.join(set_parts)
|
|
131
|
+
sql = f"UPDATE `{table}` SET {set_clause}"
|
|
132
|
+
|
|
133
|
+
# 处理WHERE条件
|
|
134
|
+
if where_clause:
|
|
135
|
+
sql += f" WHERE {where_clause}"
|
|
136
|
+
elif where:
|
|
137
|
+
where_parts = []
|
|
138
|
+
for col, val in where.items():
|
|
139
|
+
if val is None:
|
|
140
|
+
where_parts.append(f"`{col}` IS NULL")
|
|
141
|
+
elif isinstance(val, dict):
|
|
142
|
+
# 支持操作符字典 {"=": 1, ">": 5, "LIKE": "%test%", "IN": [1,2,3]}
|
|
143
|
+
for op, op_val in val.items():
|
|
144
|
+
if op.upper() == "IN" and isinstance(op_val, (list, tuple)):
|
|
145
|
+
in_vals = [SQLUtil.format_value(v) for v in op_val]
|
|
146
|
+
where_parts.append(f"`{col}` IN ({', '.join(in_vals)})")
|
|
147
|
+
if op.upper() == "NOT IN" and isinstance(op_val, (list, tuple)):
|
|
148
|
+
in_vals = [SQLUtil.format_value(v) for v in op_val]
|
|
149
|
+
where_parts.append(f"`{col}` NOT IN ({', '.join(in_vals)})")
|
|
150
|
+
elif op.upper() in ["LIKE", "=", "!=", ">", "<", ">=", "<="]:
|
|
151
|
+
formatted_val = SQLUtil.format_value(op_val)
|
|
152
|
+
where_parts.append(f"`{col}` {op.upper()} {formatted_val}")
|
|
153
|
+
else:
|
|
154
|
+
formatted_val = SQLUtil.format_value(op_val)
|
|
155
|
+
where_parts.append(f"`{col}` {op} {formatted_val}")
|
|
156
|
+
else:
|
|
157
|
+
formatted_val = SQLUtil.format_value(val)
|
|
158
|
+
where_parts.append(f"`{col}` = {formatted_val}")
|
|
159
|
+
if where_parts:
|
|
160
|
+
sql += " WHERE " + " AND ".join(where_parts)
|
|
161
|
+
|
|
162
|
+
return sql
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def delete(table: str, where: dict = None, where_clause: str = None) -> str:
|
|
166
|
+
"""
|
|
167
|
+
生成DELETE SQL语句
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
table: 表名
|
|
171
|
+
where: WHERE条件字典 {列名: 值}
|
|
172
|
+
where_clause: 自定义WHERE子句(可选)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
DELETE SQL语句
|
|
176
|
+
"""
|
|
177
|
+
sql = f"DELETE FROM {table}"
|
|
178
|
+
|
|
179
|
+
# 处理WHERE条件
|
|
180
|
+
if where_clause:
|
|
181
|
+
sql += f" WHERE {where_clause}"
|
|
182
|
+
elif where:
|
|
183
|
+
where_parts = []
|
|
184
|
+
for col, val in where.items():
|
|
185
|
+
if val is None:
|
|
186
|
+
where_parts.append(f"{col} IS NULL")
|
|
187
|
+
elif isinstance(val, dict):
|
|
188
|
+
# 支持操作符字典 {"=": 1, ">": 5, "LIKE": "%test%", "IN": [1,2,3]}
|
|
189
|
+
for op, op_val in val.items():
|
|
190
|
+
if op.upper() == "IN" and isinstance(op_val, (list, tuple)):
|
|
191
|
+
in_vals = [SQLUtil.format_value(v) for v in op_val]
|
|
192
|
+
where_parts.append(f"{col} IN ({', '.join(in_vals)})")
|
|
193
|
+
elif op.upper() in ["LIKE", "=", "!=", ">", "<", ">=", "<="]:
|
|
194
|
+
formatted_val = SQLUtil.format_value(op_val)
|
|
195
|
+
where_parts.append(f"{col} {op.upper()} {formatted_val}")
|
|
196
|
+
else:
|
|
197
|
+
formatted_val = SQLUtil.format_value(op_val)
|
|
198
|
+
where_parts.append(f"{col} {op} {formatted_val}")
|
|
199
|
+
else:
|
|
200
|
+
formatted_val = SQLUtil.format_value(val)
|
|
201
|
+
where_parts.append(f"{col} = {formatted_val}")
|
|
202
|
+
if where_parts:
|
|
203
|
+
sql += " WHERE " + " AND ".join(where_parts)
|
|
204
|
+
else:
|
|
205
|
+
# 防止意外删除所有数据
|
|
206
|
+
raise ValueError("删除操作必须提供WHERE条件")
|
|
207
|
+
|
|
208
|
+
return sql
|
goutool/util/str_util.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
class StrUtil:
|
|
4
|
+
|
|
5
|
+
@staticmethod
|
|
6
|
+
def calculate_similarity(str1, str2):
|
|
7
|
+
"""
|
|
8
|
+
计算两个字符串的相似度,返回百分比
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
str1 (str): 第一个字符串
|
|
12
|
+
str2 (str): 第二个字符串
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
float: 相似度百分比 (0-100)
|
|
16
|
+
"""
|
|
17
|
+
if not str1 and not str2:
|
|
18
|
+
return 100.0
|
|
19
|
+
if not str1 or not str2:
|
|
20
|
+
return 0.0
|
|
21
|
+
|
|
22
|
+
# 转换为小写进行比较
|
|
23
|
+
s1, s2 = str1.lower(), str2.lower()
|
|
24
|
+
m, n = len(s1), len(s2)
|
|
25
|
+
|
|
26
|
+
# 创建DP表并计算编辑距离
|
|
27
|
+
dp = [[0] * (n + 1) for _ in range(m + 1)]
|
|
28
|
+
|
|
29
|
+
# 初始化边界条件
|
|
30
|
+
for i in range(m + 1):
|
|
31
|
+
dp[i][0] = i
|
|
32
|
+
for j in range(n + 1):
|
|
33
|
+
dp[0][j] = j
|
|
34
|
+
|
|
35
|
+
# 填充DP表
|
|
36
|
+
for i in range(1, m + 1):
|
|
37
|
+
for j in range(1, n + 1):
|
|
38
|
+
if s1[i - 1] == s2[j - 1]:
|
|
39
|
+
dp[i][j] = dp[i - 1][j - 1]
|
|
40
|
+
else:
|
|
41
|
+
dp[i][j] = min(
|
|
42
|
+
dp[i - 1][j] + 1, # 删除
|
|
43
|
+
dp[i][j - 1] + 1, # 插入
|
|
44
|
+
dp[i - 1][j - 1] + 1 # 替换
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 计算相似度百分比
|
|
48
|
+
distance = dp[m][n]
|
|
49
|
+
max_len = max(len(s1), len(s2))
|
|
50
|
+
similarity = (1 - distance / max_len) * 100
|
|
51
|
+
return round(similarity, 2)
|
|
File without changes
|