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,136 @@
1
+ import importlib
2
+ from typing import Any, List, Type
3
+
4
+
5
+ class ClassUtil:
6
+ """类工具类
7
+
8
+ 提供对Python类和对象的常用操作方法,包括获取类名、模块名、类型判断等。
9
+ """
10
+
11
+ @staticmethod
12
+ def get_class_name(obj: Any, is_simple: bool = True) -> str:
13
+ """获取类名
14
+
15
+ :param obj: 对象或类
16
+ :param is_simple: 是否返回简单类名(不含模块路径),默认True
17
+ :return: 类名字符串
18
+ """
19
+ if isinstance(obj, type):
20
+ cls = obj
21
+ else:
22
+ cls = type(obj)
23
+
24
+ if is_simple:
25
+ return cls.__name__
26
+ else:
27
+ module = cls.__module__
28
+ if module == "builtins":
29
+ return cls.__name__
30
+ return f"{module}.{cls.__name__}"
31
+
32
+ @staticmethod
33
+ def get_package_name(obj: Any) -> str:
34
+ """获取模块名
35
+
36
+ :param obj: 对象或类
37
+ :return: 模块名字符串
38
+ """
39
+ if isinstance(obj, type):
40
+ cls = obj
41
+ else:
42
+ cls = type(obj)
43
+
44
+ return cls.__module__
45
+
46
+ @staticmethod
47
+ def is_basic_type(obj: Any) -> bool:
48
+ """是否为基本类型
49
+
50
+ 基本类型包括:int, float, str, bool, bytes, complex, type(None)
51
+
52
+ :param obj: 待检查的对象
53
+ :return: 是否为基本类型
54
+ """
55
+ basic_types = (int, float, str, bool, bytes, complex, type(None))
56
+ return isinstance(obj, basic_types)
57
+
58
+ @staticmethod
59
+ def is_instance(obj: Any, class_or_tuple: Any) -> bool:
60
+ """是否为指定类型的实例
61
+
62
+ :param obj: 待检查的对象
63
+ :param class_or_tuple: 类或类的元组
64
+ :return: 是否为指定类型的实例
65
+ """
66
+ return isinstance(obj, class_or_tuple)
67
+
68
+ @staticmethod
69
+ def get_public_fields(obj: Any) -> List[str]:
70
+ """获取公开字段
71
+
72
+ 返回对象中不以单下划线开头的属性名称列表。
73
+
74
+ :param obj: 对象
75
+ :return: 公开字段名列表
76
+ """
77
+ return [attr for attr in dir(obj) if not attr.startswith("_") and not callable(getattr(obj, attr, None))]
78
+
79
+ @staticmethod
80
+ def get_methods(obj: Any) -> List[str]:
81
+ """获取方法列表
82
+
83
+ 返回对象中不以单下划线开头的方法名称列表。
84
+
85
+ :param obj: 对象或类
86
+ :return: 方法名列表
87
+ """
88
+ return [attr for attr in dir(obj) if not attr.startswith("_") and callable(getattr(obj, attr, None))]
89
+
90
+ @staticmethod
91
+ def has_method(obj: Any, method_name: str) -> bool:
92
+ """是否有指定方法
93
+
94
+ :param obj: 对象或类
95
+ :param method_name: 方法名
96
+ :return: 是否存在该方法
97
+ """
98
+ attr = getattr(obj, method_name, None)
99
+ return attr is not None and callable(attr)
100
+
101
+ @staticmethod
102
+ def create_instance(class_path: str) -> Any:
103
+ """通过完整类路径创建实例
104
+
105
+ 支持模块级函数和类实例化两种方式。
106
+ 例如: 'json.loads', 'datetime.datetime', 'collections.OrderedDict'
107
+
108
+ :param class_path: 完整类路径或函数路径,如 'datetime.datetime'
109
+ :return: 创建的实例或返回的函数结果
110
+ :raises ImportError: 模块导入失败
111
+ :raises AttributeError: 属性不存在
112
+ """
113
+ parts = class_path.rsplit(".", 1)
114
+ if len(parts) != 2:
115
+ raise ValueError(f"无效的类路径: {class_path},需要包含模块和类名/函数名")
116
+
117
+ module_path, attr_name = parts
118
+ module = importlib.import_module(module_path)
119
+ obj = getattr(module, attr_name)
120
+
121
+ if isinstance(obj, type):
122
+ return obj()
123
+ return obj
124
+
125
+ @staticmethod
126
+ def is_subclass(cls: Type, parent_cls: Type) -> bool:
127
+ """是否为子类
128
+
129
+ :param cls: 待检查的类
130
+ :param parent_cls: 父类
131
+ :return: 是否为父类的子类
132
+ """
133
+ try:
134
+ return issubclass(cls, parent_cls)
135
+ except TypeError:
136
+ return False
@@ -0,0 +1,186 @@
1
+ import math
2
+ from typing import NamedTuple
3
+
4
+
5
+ class Coordinate(NamedTuple):
6
+ """坐标点"""
7
+
8
+ longitude: float
9
+ latitude: float
10
+
11
+
12
+ class CoordinateUtil:
13
+ """坐标工具类,提供WGS84/GCJ02/BD09坐标系转换
14
+
15
+ WGS84: GPS全球定位系统使用的坐标系
16
+ GCJ02: 国测局坐标系(火星坐标系),国内地图服务使用
17
+ BD09: 百度坐标系,在GCJ02基础上进一步加密
18
+ """
19
+
20
+ # 常量
21
+ _PI = math.pi
22
+ _A = 6378245.0 # 克拉索夫斯基椭球长半轴(米)
23
+ _EE = 0.00669342162296594 # 偏心率平方
24
+
25
+ @staticmethod
26
+ def _out_of_china(lng: float, lat: float) -> bool:
27
+ """判断是否在中国范围外
28
+
29
+ :param lng: 经度
30
+ :param lat: 纬度
31
+ :return: 是否在中国范围外
32
+ """
33
+ return not (72.004 <= lng <= 137.8347 and 0.8293 <= lat <= 55.8271)
34
+
35
+ @staticmethod
36
+ def _transform_lat(lng: float, lat: float) -> float:
37
+ """纬度转换偏移量计算
38
+
39
+ :param lng: 经度
40
+ :param lat: 纬度
41
+ :return: 纬度偏移量
42
+ """
43
+ pi = CoordinateUtil._PI
44
+ ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng))
45
+ ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
46
+ ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0
47
+ ret += (160.0 * math.sin(lat / 12.0 * pi) + 320.0 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0
48
+ return ret
49
+
50
+ @staticmethod
51
+ def _transform_lng(lng: float, lat: float) -> float:
52
+ """经度转换偏移量计算
53
+
54
+ :param lng: 经度
55
+ :param lat: 纬度
56
+ :return: 经度偏移量
57
+ """
58
+ pi = CoordinateUtil._PI
59
+ ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng))
60
+ ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0
61
+ ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0
62
+ ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0
63
+ return ret
64
+
65
+ @staticmethod
66
+ def _delta(lng: float, lat: float) -> tuple:
67
+ """计算GCJ-02偏移量
68
+
69
+ :param lng: WGS84经度
70
+ :param lat: WGS84纬度
71
+ :return: (经度偏移, 纬度偏移)
72
+ """
73
+ pi = CoordinateUtil._PI
74
+ a = CoordinateUtil._A
75
+ ee = CoordinateUtil._EE
76
+ dlat = CoordinateUtil._transform_lat(lng - 105.0, lat - 35.0)
77
+ dlng = CoordinateUtil._transform_lng(lng - 105.0, lat - 35.0)
78
+ radlat = lat / 180.0 * pi
79
+ magic = math.sin(radlat)
80
+ magic = 1 - ee * magic * magic
81
+ sqrtmagic = math.sqrt(magic)
82
+ dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi)
83
+ dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi)
84
+ return dlng, dlat
85
+
86
+ @staticmethod
87
+ def wgs84_to_gcj02(lng: float, lat: float) -> Coordinate:
88
+ """WGS84转GCJ02(GPS转国测局坐标)
89
+
90
+ :param lng: WGS84经度
91
+ :param lat: WGS84纬度
92
+ :return: GCJ02坐标
93
+ """
94
+ if CoordinateUtil._out_of_china(lng, lat):
95
+ return Coordinate(lng, lat)
96
+ dlng, dlat = CoordinateUtil._delta(lng, lat)
97
+ return Coordinate(lng + dlng, lat + dlat)
98
+
99
+ @staticmethod
100
+ def gcj02_to_wgs84(lng: float, lat: float) -> Coordinate:
101
+ """GCJ02转WGS84
102
+
103
+ :param lng: GCJ02经度
104
+ :param lat: GCJ02纬度
105
+ :return: WGS84坐标
106
+ """
107
+ if CoordinateUtil._out_of_china(lng, lat):
108
+ return Coordinate(lng, lat)
109
+ dlng, dlat = CoordinateUtil._delta(lng, lat)
110
+ return Coordinate(lng - dlng, lat - dlat)
111
+
112
+ @staticmethod
113
+ def gcj02_to_bd09(lng: float, lat: float) -> Coordinate:
114
+ """GCJ02转BD09(转百度坐标)
115
+
116
+ :param lng: GCJ02经度
117
+ :param lat: GCJ02纬度
118
+ :return: BD09坐标
119
+ """
120
+ pi = CoordinateUtil._PI
121
+ z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * pi * 3000.0 / 180.0)
122
+ theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * pi * 3000.0 / 180.0)
123
+ bd_lng = z * math.cos(theta) + 0.0065
124
+ bd_lat = z * math.sin(theta) + 0.006
125
+ return Coordinate(bd_lng, bd_lat)
126
+
127
+ @staticmethod
128
+ def bd09_to_gcj02(lng: float, lat: float) -> Coordinate:
129
+ """BD09转GCJ02
130
+
131
+ :param lng: BD09经度
132
+ :param lat: BD09纬度
133
+ :return: GCJ02坐标
134
+ """
135
+ pi = CoordinateUtil._PI
136
+ x = lng - 0.0065
137
+ y = lat - 0.006
138
+ z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * pi * 3000.0 / 180.0)
139
+ theta = math.atan2(y, x) - 0.000003 * math.cos(x * pi * 3000.0 / 180.0)
140
+ gcj_lng = z * math.cos(theta)
141
+ gcj_lat = z * math.sin(theta)
142
+ return Coordinate(gcj_lng, gcj_lat)
143
+
144
+ @staticmethod
145
+ def wgs84_to_bd09(lng: float, lat: float) -> Coordinate:
146
+ """WGS84转BD09
147
+
148
+ :param lng: WGS84经度
149
+ :param lat: WGS84纬度
150
+ :return: BD09坐标
151
+ """
152
+ gcj = CoordinateUtil.wgs84_to_gcj02(lng, lat)
153
+ return CoordinateUtil.gcj02_to_bd09(gcj.longitude, gcj.latitude)
154
+
155
+ @staticmethod
156
+ def bd09_to_wgs84(lng: float, lat: float) -> Coordinate:
157
+ """BD09转WGS84
158
+
159
+ :param lng: BD09经度
160
+ :param lat: BD09纬度
161
+ :return: WGS84坐标
162
+ """
163
+ gcj = CoordinateUtil.bd09_to_gcj02(lng, lat)
164
+ return CoordinateUtil.gcj02_to_wgs84(gcj.longitude, gcj.latitude)
165
+
166
+ @staticmethod
167
+ def distance(lng1: float, lat1: float, lng2: float, lat2: float) -> float:
168
+ """计算两点间距离(米),使用Haversine公式
169
+
170
+ :param lng1: 第一个点的经度
171
+ :param lat1: 第一个点的纬度
172
+ :param lng2: 第二个点的经度
173
+ :param lat2: 第二个点的纬度
174
+ :return: 两点间的距离(米)
175
+ """
176
+ earth_radius = 6371000.0 # 地球平均半径(米)
177
+
178
+ lat1_rad = math.radians(lat1)
179
+ lat2_rad = math.radians(lat2)
180
+ dlat = math.radians(lat2 - lat1)
181
+ dlng = math.radians(lng2 - lng1)
182
+
183
+ a = math.sin(dlat / 2) ** 2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlng / 2) ** 2
184
+ c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
185
+
186
+ return earth_radius * c
@@ -0,0 +1,110 @@
1
+
2
+
3
+ class CreditCodeUtil:
4
+ """统一社会信用代码工具类
5
+
6
+ 统一社会信用代码共18位,由以下部分组成:
7
+ - 登记管理部门代码(1位)
8
+ - 机构类别代码(1位)
9
+ - 登记管理机关行政区划码(6位)
10
+ - 主体标识码(9位)
11
+ - 校验码(1位)
12
+ """
13
+
14
+ # 字符到数值的映射
15
+ _CHAR_MAP = "0123456789ABCDEFGHJKLMNPQRTUWXY"
16
+ # 加权因子(前17位)
17
+ _WEIGHT_FACTORS = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28]
18
+
19
+ # 登记管理部门代码
20
+ _DEPT_CODES = {
21
+ "1": "机构编制",
22
+ "2": "外交",
23
+ "3": "司法行政",
24
+ "4": "文化",
25
+ "5": "民政",
26
+ "6": "旅游",
27
+ "7": "宗教",
28
+ "8": "工会",
29
+ "9": "工商",
30
+ "A": "机构编制",
31
+ "B": "外交",
32
+ "C": "机构编制",
33
+ "D": "商务",
34
+ "E": "其他",
35
+ }
36
+ # 机构类别代码对应的管理部门
37
+ _TYPE_CODES = {
38
+ "1": {"1": "机关", "2": "事业单位", "3": "编办直接管理机构编制的群众团体", "9": "其他"},
39
+ "2": {"1": "外国常驻新闻机构", "9": "其他"},
40
+ "3": {
41
+ "1": "律师执业机构",
42
+ "2": "公证处",
43
+ "3": "基层法律服务所",
44
+ "4": "司法鉴定机构",
45
+ "5": "仲裁委员会",
46
+ "9": "其他",
47
+ },
48
+ "4": {"1": "外国在华文化中心", "9": "其他"},
49
+ "5": {"1": "社会团体", "2": "民办非企业单位", "3": "基金会", "9": "其他"},
50
+ "6": {"1": "外国旅游部门常驻代表机构", "2": "港澳台地区旅游部门常驻代表机构", "9": "其他"},
51
+ "7": {"1": "宗教活动场所", "2": "宗教院校", "9": "其他"},
52
+ "8": {"1": "基层工会", "9": "其他"},
53
+ "9": {"1": "企业", "2": "个体工商户", "3": "农民专业合作社"},
54
+ "A": {"1": "中央编办直接管理机构编制的群众团体", "9": "其他"},
55
+ "B": {"1": "外国常驻新闻机构", "9": "其他"},
56
+ "C": {"1": "律师事务所", "9": "其他"},
57
+ "D": {"1": "外国在华文化中心", "9": "其他"},
58
+ "E": {"1": "民政", "9": "其他"},
59
+ }
60
+
61
+ @staticmethod
62
+ def is_valid_credit_code(code: str) -> bool:
63
+ """校验统一社会信用代码是否有效
64
+
65
+ 校验规则:
66
+ 1. 长度必须为18位
67
+ 2. 字符必须在允许的字符集内
68
+ 3. 登记管理部门代码和机构类别代码必须合法
69
+ 4. 校验码必须正确(使用ISO 7064:1983.MOD 37-2算法)
70
+
71
+ :param code: 待校验的统一社会信用代码
72
+ :return: 是否有效
73
+ """
74
+ if not code or len(code) != 18:
75
+ return False
76
+
77
+ code = code.upper()
78
+
79
+ # 检查字符是否在允许的字符集中
80
+ for ch in code:
81
+ if ch not in CreditCodeUtil._CHAR_MAP:
82
+ return False
83
+
84
+ # 检查登记管理部门代码
85
+ dept_code = code[0]
86
+ if dept_code not in CreditCodeUtil._DEPT_CODES:
87
+ return False
88
+
89
+ # 检查机构类别代码
90
+ type_code = code[1]
91
+ if dept_code not in CreditCodeUtil._TYPE_CODES:
92
+ return False
93
+ if type_code not in CreditCodeUtil._TYPE_CODES[dept_code]:
94
+ return False
95
+
96
+ # 校验第18位校验码
97
+ # 使用ISO 7064:1983.MOD 37-2算法
98
+ check_sum = 0
99
+ for i in range(17):
100
+ char_index = CreditCodeUtil._CHAR_MAP.index(code[i])
101
+ check_sum += char_index * CreditCodeUtil._WEIGHT_FACTORS[i]
102
+
103
+ remainder = check_sum % 31
104
+ if remainder == 0:
105
+ remainder = 31
106
+
107
+ expected_index = 31 - remainder
108
+ expected_char = CreditCodeUtil._CHAR_MAP[expected_index]
109
+
110
+ return code[17] == expected_char
@@ -0,0 +1,194 @@
1
+ class DesensitizedUtil:
2
+ """数据脱敏工具类"""
3
+
4
+ @staticmethod
5
+ def chinese_name(name: str) -> str:
6
+ """中文姓名脱敏:保留姓,名替换为*
7
+
8
+ 示例:张三 -> 张*,欧阳娜娜 -> 欧***
9
+
10
+ :param name: 中文姓名
11
+ :return: 脱敏后的姓名
12
+ """
13
+ if not name:
14
+ return ""
15
+ name = name.strip()
16
+ if len(name) <= 1:
17
+ return name
18
+ elif len(name) == 2:
19
+ return name[0] + "*"
20
+ else:
21
+ return name[0] + "*" * (len(name) - 1)
22
+
23
+ @staticmethod
24
+ def id_card(idcard: str, before: int = 3, after: int = 4) -> str:
25
+ """身份证脱敏
26
+
27
+ 保留前before位和后after位,中间用*替换
28
+
29
+ :param idcard: 身份证号
30
+ :param before: 保留前N位,默认3
31
+ :param after: 保留后N位,默认4
32
+ :return: 脱敏后的身份证号
33
+ """
34
+ if not idcard:
35
+ return ""
36
+ idcard = idcard.strip()
37
+ length = len(idcard)
38
+ if before + after >= length:
39
+ return idcard
40
+ return idcard[:before] + "*" * (length - before - after) + idcard[length - after :]
41
+
42
+ @staticmethod
43
+ def mobile_phone(phone: str) -> str:
44
+ """手机号脱敏:138****1234
45
+
46
+ :param phone: 手机号
47
+ :return: 脱敏后的手机号
48
+ """
49
+ if not phone:
50
+ return ""
51
+ phone = phone.strip()
52
+ if len(phone) < 7:
53
+ return phone
54
+ return phone[:3] + "****" + phone[7:]
55
+
56
+ @staticmethod
57
+ def fixed_phone(phone: str) -> str:
58
+ """固定电话脱敏
59
+
60
+ 保留区号和后4位,中间用*替换
61
+
62
+ :param phone: 固定电话
63
+ :return: 脱敏后的固定电话
64
+ """
65
+ if not phone:
66
+ return ""
67
+ phone = phone.strip()
68
+ # 查找分隔符位置(区号后的'-')
69
+ dash_pos = phone.find("-")
70
+ if dash_pos == -1:
71
+ # 无分隔符,按规则:前3位为区号保留,后4位保留
72
+ if len(phone) <= 7:
73
+ return phone[:3] + "****" if len(phone) > 3 else phone
74
+ return phone[:3] + "*" * (len(phone) - 7) + phone[-4:]
75
+ area_code = phone[:dash_pos]
76
+ number = phone[dash_pos + 1 :]
77
+ if len(number) <= 4:
78
+ return area_code + "-" + number
79
+ return area_code + "-" + "*" * (len(number) - 4) + number[-4:]
80
+
81
+ @staticmethod
82
+ def email(email: str) -> str:
83
+ """邮箱脱敏:t***@example.com
84
+
85
+ 保留@前的首字母和@后的完整域名
86
+
87
+ :param email: 邮箱地址
88
+ :return: 脱敏后的邮箱
89
+ """
90
+ if not email:
91
+ return ""
92
+ email = email.strip()
93
+ at_pos = email.find("@")
94
+ if at_pos <= 0:
95
+ return email
96
+ prefix = email[:at_pos]
97
+ domain = email[at_pos:]
98
+ if len(prefix) <= 1:
99
+ return prefix + "***" + domain
100
+ return prefix[0] + "***" + domain
101
+
102
+ @staticmethod
103
+ def address(address: str, sensitive_size: int = 6) -> str:
104
+ """地址脱敏
105
+
106
+ 从敏感_size位之前开始替换为*
107
+
108
+ :param address: 地址
109
+ :param sensitive_size: 需要脱敏的字符数,默认6
110
+ :return: 脱敏后的地址
111
+ """
112
+ if not address:
113
+ return ""
114
+ address = address.strip()
115
+ length = len(address)
116
+ if sensitive_size >= length:
117
+ return "*" * length
118
+ return address[: length - sensitive_size] + "*" * sensitive_size
119
+
120
+ @staticmethod
121
+ def bank_card(card: str) -> str:
122
+ """银行卡脱敏
123
+
124
+ 保留前4位和后4位,中间用*替换
125
+
126
+ :param card: 银行卡号
127
+ :return: 脱敏后的银行卡号
128
+ """
129
+ if not card:
130
+ return ""
131
+ card = card.strip().replace(" ", "")
132
+ if len(card) <= 8:
133
+ return card
134
+ return card[:4] + "*" * (len(card) - 8) + card[-4:]
135
+
136
+ @staticmethod
137
+ def password(password: str) -> str:
138
+ """密码脱敏:全部替换为*
139
+
140
+ :param password: 密码
141
+ :return: 脱敏后的密码(全为*)
142
+ """
143
+ if not password:
144
+ return ""
145
+ return "*" * len(password)
146
+
147
+ @staticmethod
148
+ def car_license(license_no: str) -> str:
149
+ """车牌号脱敏
150
+
151
+ 保留前2位和后1位,中间用*替换
152
+
153
+ :param license_no: 车牌号
154
+ :return: 脱敏后的车牌号
155
+ """
156
+ if not license_no:
157
+ return ""
158
+ license_no = license_no.strip()
159
+ if len(license_no) <= 3:
160
+ return license_no
161
+ return license_no[:2] + "*" * (len(license_no) - 3) + license_no[-1:]
162
+
163
+ @staticmethod
164
+ def ipv4(ipv4: str) -> str:
165
+ """IPv4脱敏
166
+
167
+ 保留前两段,后两段替换为*
168
+
169
+ :param ipv4: IPv4地址
170
+ :return: 脱敏后的IPv4地址
171
+ """
172
+ if not ipv4:
173
+ return ""
174
+ ipv4 = ipv4.strip()
175
+ parts = ipv4.split(".")
176
+ if len(parts) != 4:
177
+ return ipv4
178
+ return parts[0] + "." + parts[1] + ".*.*"
179
+
180
+ @staticmethod
181
+ def license_plate(plate: str) -> str:
182
+ """车牌号脱敏
183
+
184
+ 保留省份简称和地区代码,后面替换为*
185
+
186
+ :param plate: 车牌号
187
+ :return: 脱敏后的车牌号
188
+ """
189
+ if not plate:
190
+ return ""
191
+ plate = plate.strip()
192
+ if len(plate) <= 2:
193
+ return plate
194
+ return plate[:2] + "*" * (len(plate) - 2)