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.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. 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)