hutool-python 1.0.0__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.
- hutool/__init__.py +174 -0
- hutool/cache/__init__.py +7 -0
- hutool/cache/cache_util.py +47 -0
- hutool/cache/fifo_cache.py +87 -0
- hutool/cache/lfu_cache.py +129 -0
- hutool/cache/lru_cache.py +93 -0
- hutool/cache/timed_cache.py +115 -0
- hutool/captcha/__init__.py +3 -0
- hutool/captcha/captcha_util.py +215 -0
- hutool/core/__init__.py +23 -0
- hutool/core/_base.py +61 -0
- hutool/core/bean.py +214 -0
- hutool/core/codec.py +111 -0
- hutool/core/coll.py +635 -0
- hutool/core/date.py +1024 -0
- hutool/core/exceptions.py +66 -0
- hutool/core/io/__init__.py +0 -0
- hutool/core/io/data_size_util.py +79 -0
- hutool/core/io/file_name_util.py +111 -0
- hutool/core/io/file_util.py +650 -0
- hutool/core/io/io_util.py +133 -0
- hutool/core/io/path_util.py +247 -0
- hutool/core/io/resource_util.py +137 -0
- hutool/core/map.py +933 -0
- hutool/core/math_util.py +105 -0
- hutool/core/net.py +288 -0
- hutool/core/text/__init__.py +0 -0
- hutool/core/text/csv_util.py +54 -0
- hutool/core/text/str_builder.py +224 -0
- hutool/core/text/unicode_util.py +58 -0
- hutool/core/tree.py +242 -0
- hutool/core/util/__init__.py +63 -0
- hutool/core/util/array_util.py +503 -0
- hutool/core/util/boolean_util.py +124 -0
- hutool/core/util/charset_util.py +60 -0
- hutool/core/util/class_util.py +136 -0
- hutool/core/util/coordinate_util.py +186 -0
- hutool/core/util/credit_code_util.py +110 -0
- hutool/core/util/desensitized_util.py +194 -0
- hutool/core/util/enum_util.py +94 -0
- hutool/core/util/escape_util.py +97 -0
- hutool/core/util/hash_util.py +243 -0
- hutool/core/util/hex_util.py +140 -0
- hutool/core/util/id_util.py +147 -0
- hutool/core/util/idcard_util.py +300 -0
- hutool/core/util/number_util.py +720 -0
- hutool/core/util/object_util.py +294 -0
- hutool/core/util/page_util.py +61 -0
- hutool/core/util/phone_util.py +140 -0
- hutool/core/util/random_util.py +112 -0
- hutool/core/util/re_util.py +231 -0
- hutool/core/util/reflect_util.py +135 -0
- hutool/core/util/runtime_util.py +89 -0
- hutool/core/util/str_util.py +2320 -0
- hutool/core/util/system_util.py +62 -0
- hutool/core/util/url_util.py +232 -0
- hutool/core/util/version_util.py +41 -0
- hutool/core/util/xml_util.py +158 -0
- hutool/core/util/zip_util.py +126 -0
- hutool/cron/__init__.py +4 -0
- hutool/cron/cron_pattern.py +123 -0
- hutool/cron/cron_util.py +115 -0
- hutool/crypto/__init__.py +5 -0
- hutool/crypto/digest_util.py +167 -0
- hutool/crypto/secure_util.py +311 -0
- hutool/crypto/sign_util.py +74 -0
- hutool/dfa/__init__.py +3 -0
- hutool/dfa/sensitive_util.py +114 -0
- hutool/extra/__init__.py +6 -0
- hutool/extra/emoji_util.py +90 -0
- hutool/extra/pinyin_util.py +44 -0
- hutool/extra/qr_code_util.py +58 -0
- hutool/extra/template_util.py +41 -0
- hutool/http/__init__.py +6 -0
- hutool/http/html_util.py +88 -0
- hutool/http/http_request.py +188 -0
- hutool/http/http_response.py +139 -0
- hutool/http/http_util.py +237 -0
- hutool/json_util.py +251 -0
- hutool/jwt_util.py +57 -0
- hutool/setting/__init__.py +5 -0
- hutool/setting/props_util.py +79 -0
- hutool/setting/setting_util.py +80 -0
- hutool/setting/yaml_util.py +45 -0
- hutool_python-1.0.0.dist-info/LICENSE +127 -0
- hutool_python-1.0.0.dist-info/METADATA +438 -0
- hutool_python-1.0.0.dist-info/RECORD +89 -0
- hutool_python-1.0.0.dist-info/WHEEL +5 -0
- hutool_python-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python port of Java Hutool's ObjectUtil.
|
|
3
|
+
|
|
4
|
+
对象工具类,提供对象比较、空值判断、默认值处理、类型判断等常用对象工具方法。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
from typing import Any, Optional, Sized
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ObjectUtil",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ObjectUtil:
|
|
16
|
+
"""对象工具类,对应 Java cn.hutool.core.util.ObjectUtil"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def equals(obj1: Any, obj2: Any) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
比较两个对象是否相等,null安全。
|
|
22
|
+
当两对象均为None时返回True,当其中之一为None时返回False。
|
|
23
|
+
|
|
24
|
+
:param obj1: 对象1
|
|
25
|
+
:param obj2: 对象2
|
|
26
|
+
:return: 是否相等
|
|
27
|
+
"""
|
|
28
|
+
if obj1 is obj2:
|
|
29
|
+
return True
|
|
30
|
+
if obj1 is None or obj2 is None:
|
|
31
|
+
return False
|
|
32
|
+
return obj1 == obj2
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def not_equal(obj1: Any, obj2: Any) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
比较两个对象是否不等,null安全。
|
|
38
|
+
|
|
39
|
+
:param obj1: 对象1
|
|
40
|
+
:param obj2: 对象2
|
|
41
|
+
:return: 是否不等
|
|
42
|
+
"""
|
|
43
|
+
return not ObjectUtil.equals(obj1, obj2)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def length(obj: Any) -> int:
|
|
47
|
+
"""
|
|
48
|
+
获取对象长度,支持 str、list、tuple、dict、bytes 等可计算长度的对象。
|
|
49
|
+
当对象为None时返回0。
|
|
50
|
+
|
|
51
|
+
:param obj: 对象
|
|
52
|
+
:return: 长度
|
|
53
|
+
"""
|
|
54
|
+
if obj is None:
|
|
55
|
+
return 0
|
|
56
|
+
if isinstance(obj, Sized):
|
|
57
|
+
return len(obj)
|
|
58
|
+
# 对于不支持len的对象,尝试迭代计数
|
|
59
|
+
raise TypeError(f"Object of type '{type(obj).__name__}' has no len()")
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def contains(obj: Any, element: Any) -> bool:
|
|
63
|
+
"""
|
|
64
|
+
对象中是否包含元素。
|
|
65
|
+
支持的类型:
|
|
66
|
+
- str: 检查是否包含子串
|
|
67
|
+
- dict: 检查是否包含key
|
|
68
|
+
- set: 检查是否包含元素
|
|
69
|
+
- list/tuple/其他可迭代对象: 检查是否包含元素
|
|
70
|
+
|
|
71
|
+
当对象为None时返回False。
|
|
72
|
+
|
|
73
|
+
:param obj: 对象
|
|
74
|
+
:param element: 元素
|
|
75
|
+
:return: 是否包含
|
|
76
|
+
"""
|
|
77
|
+
if obj is None:
|
|
78
|
+
return False
|
|
79
|
+
if isinstance(obj, str):
|
|
80
|
+
if element is None:
|
|
81
|
+
return False
|
|
82
|
+
return str(element) in obj
|
|
83
|
+
if isinstance(obj, dict):
|
|
84
|
+
return element in obj
|
|
85
|
+
if isinstance(obj, (set, frozenset)):
|
|
86
|
+
return element in obj
|
|
87
|
+
if isinstance(obj, (list, tuple)):
|
|
88
|
+
return element in obj
|
|
89
|
+
# 兜底:尝试使用 in 运算符
|
|
90
|
+
try:
|
|
91
|
+
return element in obj
|
|
92
|
+
except TypeError:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def is_null(obj: Any) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
检查对象是否为None。
|
|
99
|
+
|
|
100
|
+
:param obj: 对象
|
|
101
|
+
:return: 是否为None
|
|
102
|
+
"""
|
|
103
|
+
return obj is None
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def is_not_null(obj: Any) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
检查对象是否不为None。
|
|
109
|
+
|
|
110
|
+
:param obj: 对象
|
|
111
|
+
:return: 是否不为None
|
|
112
|
+
"""
|
|
113
|
+
return obj is not None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def is_empty(obj: Any) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
检查对象是否为空(None或空容器)。
|
|
119
|
+
支持 str、list、tuple、dict、set、frozenset、bytes、bytearray。
|
|
120
|
+
对于非上述类型,如果为None则返回True,否则返回False。
|
|
121
|
+
|
|
122
|
+
:param obj: 对象
|
|
123
|
+
:return: 是否为空
|
|
124
|
+
"""
|
|
125
|
+
if obj is None:
|
|
126
|
+
return True
|
|
127
|
+
if isinstance(obj, (str, bytes, bytearray, list, tuple, dict, set, frozenset)):
|
|
128
|
+
return len(obj) == 0
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def is_not_empty(obj: Any) -> bool:
|
|
133
|
+
"""
|
|
134
|
+
检查对象是否为非空。
|
|
135
|
+
|
|
136
|
+
:param obj: 对象
|
|
137
|
+
:return: 是否为非空
|
|
138
|
+
"""
|
|
139
|
+
return not ObjectUtil.is_empty(obj)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def default_if_null(obj: Any, default_value: Any) -> Any:
|
|
143
|
+
"""
|
|
144
|
+
如果obj为None,返回默认值,否则返回obj本身。
|
|
145
|
+
|
|
146
|
+
:param obj: 对象
|
|
147
|
+
:param default_value: 默认值
|
|
148
|
+
:return: 对象或默认值
|
|
149
|
+
"""
|
|
150
|
+
return default_value if obj is None else obj
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def default_if_empty(obj: Any, default_value: Any) -> Any:
|
|
154
|
+
"""
|
|
155
|
+
如果obj为空(None或空容器),返回默认值,否则返回obj本身。
|
|
156
|
+
|
|
157
|
+
:param obj: 对象
|
|
158
|
+
:param default_value: 默认值
|
|
159
|
+
:return: 对象或默认值
|
|
160
|
+
"""
|
|
161
|
+
return default_value if ObjectUtil.is_empty(obj) else obj
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def default_if_blank(obj: Optional[str], default_value: str) -> str:
|
|
165
|
+
"""
|
|
166
|
+
如果字符串为空白(None、空串或纯空白),返回默认值。
|
|
167
|
+
|
|
168
|
+
:param obj: 字符串对象
|
|
169
|
+
:param default_value: 默认值
|
|
170
|
+
:return: 字符串或默认值
|
|
171
|
+
"""
|
|
172
|
+
if obj is None:
|
|
173
|
+
return default_value
|
|
174
|
+
if isinstance(obj, str) and obj.strip() == "":
|
|
175
|
+
return default_value
|
|
176
|
+
return obj
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def clone(obj: Any) -> Any:
|
|
180
|
+
"""
|
|
181
|
+
深拷贝对象。当对象为None时返回None。
|
|
182
|
+
|
|
183
|
+
:param obj: 对象
|
|
184
|
+
:return: 深拷贝后的对象
|
|
185
|
+
"""
|
|
186
|
+
if obj is None:
|
|
187
|
+
return None
|
|
188
|
+
return copy.deepcopy(obj)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def is_basic_type(obj: Any) -> bool:
|
|
192
|
+
"""
|
|
193
|
+
是否为基本类型,包括 int、float、complex、bool、str、bytes、None。
|
|
194
|
+
|
|
195
|
+
:param obj: 对象
|
|
196
|
+
:return: 是否为基本类型
|
|
197
|
+
"""
|
|
198
|
+
if obj is None:
|
|
199
|
+
return True
|
|
200
|
+
return isinstance(obj, (int, float, complex, bool, str, bytes))
|
|
201
|
+
|
|
202
|
+
@staticmethod
|
|
203
|
+
def compare(c1: Any, c2: Any) -> int:
|
|
204
|
+
"""
|
|
205
|
+
比较两个对象的大小。
|
|
206
|
+
当c1为None时,c2为None返回0,否则返回-1。
|
|
207
|
+
当c2为None时,返回1。
|
|
208
|
+
两者均非None时使用比较运算符。
|
|
209
|
+
|
|
210
|
+
与Java Comparable接口行为一致:
|
|
211
|
+
- c1 == c2 -> 0
|
|
212
|
+
- c1 < c2 -> -1
|
|
213
|
+
- c1 > c2 -> 1
|
|
214
|
+
|
|
215
|
+
:param c1: 对象1
|
|
216
|
+
:param c2: 对象2
|
|
217
|
+
:return: 比较结果,0表示相等,-1表示c1小于c2,1表示c1大于c2
|
|
218
|
+
"""
|
|
219
|
+
if c1 is None:
|
|
220
|
+
return 0 if c2 is None else -1
|
|
221
|
+
if c2 is None:
|
|
222
|
+
return 1
|
|
223
|
+
if c1 == c2:
|
|
224
|
+
return 0
|
|
225
|
+
return -1 if c1 < c2 else 1
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def to_string(obj: Any, default: str = "") -> str:
|
|
229
|
+
"""
|
|
230
|
+
将对象转为字符串,当对象为None时返回默认值。
|
|
231
|
+
|
|
232
|
+
:param obj: 对象
|
|
233
|
+
:param default: 默认值
|
|
234
|
+
:return: 字符串
|
|
235
|
+
"""
|
|
236
|
+
if obj is None:
|
|
237
|
+
return default
|
|
238
|
+
return str(obj)
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def has_null(*args: Any) -> bool:
|
|
242
|
+
"""
|
|
243
|
+
检查参数中是否有None。
|
|
244
|
+
当没有参数传入时返回False。
|
|
245
|
+
|
|
246
|
+
:param args: 参数列表
|
|
247
|
+
:return: 是否包含None
|
|
248
|
+
"""
|
|
249
|
+
for arg in args:
|
|
250
|
+
if arg is None:
|
|
251
|
+
return True
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def has_empty(*args: Any) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
检查参数中是否有空值(None或空容器)。
|
|
258
|
+
当没有参数传入时返回False。
|
|
259
|
+
|
|
260
|
+
:param args: 参数列表
|
|
261
|
+
:return: 是否包含空值
|
|
262
|
+
"""
|
|
263
|
+
for arg in args:
|
|
264
|
+
if ObjectUtil.is_empty(arg):
|
|
265
|
+
return True
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
@staticmethod
|
|
269
|
+
def is_all_empty(*args: Any) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
检查是否全部为空。
|
|
272
|
+
当没有参数传入时返回True。
|
|
273
|
+
|
|
274
|
+
:param args: 参数列表
|
|
275
|
+
:return: 是否全部为空
|
|
276
|
+
"""
|
|
277
|
+
for arg in args:
|
|
278
|
+
if ObjectUtil.is_not_empty(arg):
|
|
279
|
+
return False
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def is_all_not_empty(*args: Any) -> bool:
|
|
284
|
+
"""
|
|
285
|
+
检查是否全部为非空。
|
|
286
|
+
当没有参数传入时返回True。
|
|
287
|
+
|
|
288
|
+
:param args: 参数列表
|
|
289
|
+
:return: 是否全部为非空
|
|
290
|
+
"""
|
|
291
|
+
for arg in args:
|
|
292
|
+
if ObjectUtil.is_empty(arg):
|
|
293
|
+
return False
|
|
294
|
+
return True
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""分页工具类"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PageUtil:
|
|
10
|
+
"""分页工具类"""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def total_page(total_count: int, page_size: int) -> int:
|
|
14
|
+
"""计算总页数"""
|
|
15
|
+
if total_count <= 0 or page_size <= 0:
|
|
16
|
+
return 0
|
|
17
|
+
return math.ceil(total_count / page_size)
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def rainbow(page_num: int, total_page: int, display_count: int) -> List[int]:
|
|
21
|
+
"""彩虹分页,获取页码列表
|
|
22
|
+
例如: rainbow(5, 10, 3) -> [3,4,5,6,7]
|
|
23
|
+
"""
|
|
24
|
+
if page_num < 1 or total_page < 1 or display_count < 1:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
half = display_count // 2
|
|
28
|
+
start = max(1, page_num - half)
|
|
29
|
+
end = min(total_page, start + display_count - 1)
|
|
30
|
+
|
|
31
|
+
# 修正起始位置
|
|
32
|
+
if end - start + 1 < display_count:
|
|
33
|
+
start = max(1, end - display_count + 1)
|
|
34
|
+
|
|
35
|
+
return list(range(start, end + 1))
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def to_page(start_index: int, page_size: int) -> int:
|
|
39
|
+
"""起始索引转页码(从1开始)"""
|
|
40
|
+
if page_size <= 0:
|
|
41
|
+
return 1
|
|
42
|
+
return start_index // page_size + 1
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def first_page() -> int:
|
|
46
|
+
"""第一页页码,始终返回1"""
|
|
47
|
+
return 1
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def get_start(page: int, limit: int) -> int:
|
|
51
|
+
"""根据页码和每页条数计算起始行号(从0开始)"""
|
|
52
|
+
if page < 1 or limit < 1:
|
|
53
|
+
return 0
|
|
54
|
+
return (page - 1) * limit
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def to_start_index(page_num: int, page_size: int) -> int:
|
|
58
|
+
"""页码转起始索引"""
|
|
59
|
+
if page_num < 1 or page_size < 1:
|
|
60
|
+
return 0
|
|
61
|
+
return (page_num - 1) * page_size
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PhoneUtil:
|
|
5
|
+
"""手机号工具类"""
|
|
6
|
+
|
|
7
|
+
# 中国大陆手机号正则
|
|
8
|
+
_MOBILE_PATTERN = re.compile(r"^1[3-9]\d{9}$")
|
|
9
|
+
|
|
10
|
+
# 香港手机号正则(8位,5/6/9开头)
|
|
11
|
+
_MOBILE_HK_PATTERN = re.compile(r"^[569]\d{7}$")
|
|
12
|
+
|
|
13
|
+
# 台湾手机号正则(09开头10位)
|
|
14
|
+
_MOBILE_TW_PATTERN = re.compile(r"^09\d{8}$")
|
|
15
|
+
|
|
16
|
+
# 澳门手机号正则(6开头8位)
|
|
17
|
+
_MOBILE_MO_PATTERN = re.compile(r"^6\d{7}$")
|
|
18
|
+
|
|
19
|
+
# 座机正则(区号-号码,区号3-4位,号码7-8位)
|
|
20
|
+
_FIXED_PHONE_PATTERN = re.compile(r"^0\d{2,3}-?\d{7,8}$")
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def is_mobile(phone: str) -> bool:
|
|
24
|
+
"""是否为中国大陆手机号
|
|
25
|
+
|
|
26
|
+
:param phone: 手机号字符串
|
|
27
|
+
:return: 是否合法
|
|
28
|
+
"""
|
|
29
|
+
if not phone:
|
|
30
|
+
return False
|
|
31
|
+
return bool(PhoneUtil._MOBILE_PATTERN.match(phone.strip()))
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def is_mobile_hk(phone: str) -> bool:
|
|
35
|
+
"""是否为香港手机号(8位,5/6/9开头)
|
|
36
|
+
|
|
37
|
+
:param phone: 手机号字符串
|
|
38
|
+
:return: 是否合法
|
|
39
|
+
"""
|
|
40
|
+
if not phone:
|
|
41
|
+
return False
|
|
42
|
+
return bool(PhoneUtil._MOBILE_HK_PATTERN.match(phone.strip()))
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def is_mobile_tw(phone: str) -> bool:
|
|
46
|
+
"""是否为台湾手机号(09开头10位)
|
|
47
|
+
|
|
48
|
+
:param phone: 手机号字符串
|
|
49
|
+
:return: 是否合法
|
|
50
|
+
"""
|
|
51
|
+
if not phone:
|
|
52
|
+
return False
|
|
53
|
+
return bool(PhoneUtil._MOBILE_TW_PATTERN.match(phone.strip()))
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def is_mobile_mo(phone: str) -> bool:
|
|
57
|
+
"""是否为澳门手机号(6开头8位)
|
|
58
|
+
|
|
59
|
+
:param phone: 手机号字符串
|
|
60
|
+
:return: 是否合法
|
|
61
|
+
"""
|
|
62
|
+
if not phone:
|
|
63
|
+
return False
|
|
64
|
+
return bool(PhoneUtil._MOBILE_MO_PATTERN.match(phone.strip()))
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def is_phone(phone: str) -> bool:
|
|
68
|
+
"""是否为电话号码(手机或座机)
|
|
69
|
+
|
|
70
|
+
:param phone: 电话号码字符串
|
|
71
|
+
:return: 是否合法
|
|
72
|
+
"""
|
|
73
|
+
if not phone:
|
|
74
|
+
return False
|
|
75
|
+
phone = phone.strip()
|
|
76
|
+
return PhoneUtil.is_mobile(phone) or bool(PhoneUtil._FIXED_PHONE_PATTERN.match(phone))
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def hide_before(phone: str) -> str:
|
|
80
|
+
"""隐藏前3位,如 138****1234
|
|
81
|
+
|
|
82
|
+
:param phone: 手机号字符串
|
|
83
|
+
:return: 脱敏后的手机号
|
|
84
|
+
"""
|
|
85
|
+
if not phone:
|
|
86
|
+
return ""
|
|
87
|
+
phone = phone.strip()
|
|
88
|
+
if len(phone) < 7:
|
|
89
|
+
return phone
|
|
90
|
+
return phone[:3] + "****" + phone[7:]
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def hide_between(phone: str) -> str:
|
|
94
|
+
"""隐藏中间4位,如 138****1234
|
|
95
|
+
|
|
96
|
+
:param phone: 手机号字符串
|
|
97
|
+
:return: 脱敏后的手机号
|
|
98
|
+
"""
|
|
99
|
+
if not phone:
|
|
100
|
+
return ""
|
|
101
|
+
phone = phone.strip()
|
|
102
|
+
if len(phone) < 11:
|
|
103
|
+
return phone
|
|
104
|
+
return phone[:3] + "****" + phone[7:]
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def hide_after(phone: str) -> str:
|
|
108
|
+
"""隐藏后4位
|
|
109
|
+
|
|
110
|
+
:param phone: 手机号字符串
|
|
111
|
+
:return: 脱敏后的手机号
|
|
112
|
+
"""
|
|
113
|
+
if not phone:
|
|
114
|
+
return ""
|
|
115
|
+
phone = phone.strip()
|
|
116
|
+
if len(phone) < 4:
|
|
117
|
+
return phone
|
|
118
|
+
return phone[:-4] + "****"
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def sub_before(phone: str) -> str:
|
|
122
|
+
"""获取手机号前3位
|
|
123
|
+
|
|
124
|
+
:param phone: 手机号字符串
|
|
125
|
+
:return: 前3位
|
|
126
|
+
"""
|
|
127
|
+
if not phone:
|
|
128
|
+
return ""
|
|
129
|
+
return phone.strip()[:3]
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def sub_after(phone: str) -> str:
|
|
133
|
+
"""获取手机号后4位
|
|
134
|
+
|
|
135
|
+
:param phone: 手机号字符串
|
|
136
|
+
:return: 后4位
|
|
137
|
+
"""
|
|
138
|
+
if not phone:
|
|
139
|
+
return ""
|
|
140
|
+
return phone.strip()[-4:]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import secrets
|
|
3
|
+
import string
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from typing import List, Optional, Sequence, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RandomUtil:
|
|
12
|
+
"""随机工具类,对应 Java cn.hutool.core.util.RandomUtil
|
|
13
|
+
|
|
14
|
+
默认使用线程安全的 secrets 模块生成密码学安全随机数,
|
|
15
|
+
部分方法(如 random_color、random_date)使用 random 模块。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def random_int(min_include: int = 0, max_exclude: int = sys.maxsize) -> int:
|
|
20
|
+
"""生成随机int,范围[min, max)"""
|
|
21
|
+
if min_include >= max_exclude:
|
|
22
|
+
raise ValueError(f"min_include({min_include}) 必须小于 max_exclude({max_exclude})")
|
|
23
|
+
return secrets.randbelow(max_exclude - min_include) + min_include
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def random_long(min_include: int = 0, max_exclude: int = 2**63) -> int:
|
|
27
|
+
"""生成随机long"""
|
|
28
|
+
if min_include >= max_exclude:
|
|
29
|
+
raise ValueError(f"min_include({min_include}) 必须小于 max_exclude({max_exclude})")
|
|
30
|
+
return secrets.randbelow(max_exclude - min_include) + min_include
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def random_float(min_include: float = 0.0, max_exclude: float = 1.0) -> float:
|
|
34
|
+
"""生成随机float"""
|
|
35
|
+
if min_include >= max_exclude:
|
|
36
|
+
raise ValueError(f"min_include({min_include}) 必须小于 max_exclude({max_exclude})")
|
|
37
|
+
# 使用 secrets 生成 [0, 1) 的安全随机数,再映射到目标范围
|
|
38
|
+
ratio = secrets.randbelow(1 << 32) / (1 << 32)
|
|
39
|
+
return min_include + ratio * (max_exclude - min_include)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def random_double(min_include: float = 0.0, max_exclude: float = 1.0) -> float:
|
|
43
|
+
"""生成随机double(别名)"""
|
|
44
|
+
return RandomUtil.random_float(min_include, max_exclude)
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def random_boolean() -> bool:
|
|
48
|
+
"""生成随机布尔值"""
|
|
49
|
+
return bool(secrets.randbelow(2))
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def random_bytes(length: int) -> bytes:
|
|
53
|
+
"""生成随机字节数组"""
|
|
54
|
+
if length < 0:
|
|
55
|
+
raise ValueError(f"length({length}) 不能为负数")
|
|
56
|
+
return secrets.token_bytes(length)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def random_ele(sequence: Sequence[T]) -> T:
|
|
60
|
+
"""从序列中随机选取一个元素"""
|
|
61
|
+
if not sequence:
|
|
62
|
+
raise ValueError("序列不能为空")
|
|
63
|
+
return random.choice(sequence)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def random_eles(sequence: Sequence[T], count: int) -> List[T]:
|
|
67
|
+
"""从序列中随机选取count个元素(不重复)"""
|
|
68
|
+
if not sequence:
|
|
69
|
+
raise ValueError("序列不能为空")
|
|
70
|
+
if count < 0:
|
|
71
|
+
raise ValueError(f"count({count}) 不能为负数")
|
|
72
|
+
if count > len(sequence):
|
|
73
|
+
raise ValueError(f"count({count}) 不能大于序列长度({len(sequence)})")
|
|
74
|
+
return random.sample(list(sequence), count)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def random_string(length: int, base: Optional[str] = None) -> str:
|
|
78
|
+
"""生成随机字符串,默认包含字母和数字"""
|
|
79
|
+
if length < 0:
|
|
80
|
+
raise ValueError(f"length({length}) 不能为负数")
|
|
81
|
+
if base is None:
|
|
82
|
+
base = string.ascii_letters + string.digits
|
|
83
|
+
return "".join(secrets.choice(base) for _ in range(length))
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def random_string_upper(length: int) -> str:
|
|
87
|
+
"""生成随机大写字母字符串"""
|
|
88
|
+
return RandomUtil.random_string(length, string.ascii_uppercase)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def random_string_lower(length: int) -> str:
|
|
92
|
+
"""生成随机小写字母字符串"""
|
|
93
|
+
return RandomUtil.random_string(length, string.ascii_lowercase)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def random_numbers(length: int) -> str:
|
|
97
|
+
"""生成随机数字字符串"""
|
|
98
|
+
return RandomUtil.random_string(length, string.digits)
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def random_color() -> str:
|
|
102
|
+
"""生成随机十六进制颜色,如 #A1B2C3"""
|
|
103
|
+
return f"#{random.randint(0, 0xFFFFFF):06X}"
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def random_date(start: datetime, end: datetime) -> datetime:
|
|
107
|
+
"""在日期范围内生成随机日期"""
|
|
108
|
+
if start >= end:
|
|
109
|
+
raise ValueError("开始日期必须早于结束日期")
|
|
110
|
+
delta = end - start
|
|
111
|
+
random_seconds = random.uniform(0, delta.total_seconds())
|
|
112
|
+
return start + timedelta(seconds=random_seconds)
|