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,147 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class _SnowflakeWorkerPool:
|
|
8
|
+
"""模块级雪花算法工作池,跨调用维持状态"""
|
|
9
|
+
|
|
10
|
+
_workers: dict = {}
|
|
11
|
+
_lock = threading.Lock()
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def get(cls, worker_id: int, datacenter_id: int) -> "_SnowflakeIdWorker":
|
|
15
|
+
key = (worker_id, datacenter_id)
|
|
16
|
+
with cls._lock:
|
|
17
|
+
if key not in cls._workers:
|
|
18
|
+
cls._workers[key] = _SnowflakeIdWorker(worker_id, datacenter_id)
|
|
19
|
+
return cls._workers[key]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IdUtil:
|
|
23
|
+
"""ID生成工具类,对应 Java cn.hutool.core.util.IdUtil"""
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def random_uuid() -> str:
|
|
27
|
+
"""生成随机UUID(带横线)"""
|
|
28
|
+
return str(uuid.uuid4())
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def simple_uuid() -> str:
|
|
32
|
+
"""生成简单UUID(不带横线)"""
|
|
33
|
+
return str(uuid.uuid4()).replace("-", "")
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def fast_uuid() -> str:
|
|
37
|
+
"""快速生成UUID"""
|
|
38
|
+
return str(uuid.uuid4())
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def fast_simple_uuid() -> str:
|
|
42
|
+
"""快速生成简单UUID"""
|
|
43
|
+
return str(uuid.uuid4()).replace("-", "")
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def nano_id(size: int = 21) -> str:
|
|
47
|
+
"""生成NanoID(URL安全的短ID)
|
|
48
|
+
|
|
49
|
+
使用 secrets 模块实现,类似 nanoid 算法。
|
|
50
|
+
|
|
51
|
+
:param size: 生成的ID长度,默认为21
|
|
52
|
+
:return: NanoID字符串
|
|
53
|
+
"""
|
|
54
|
+
if size <= 0:
|
|
55
|
+
raise ValueError(f"size({size}) 必须为正数")
|
|
56
|
+
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
|
|
57
|
+
return "".join(secrets.choice(alphabet) for _ in range(size))
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def snowflake_id(worker_id: int = 1, datacenter_id: int = 1) -> int:
|
|
61
|
+
"""生成雪花算法ID
|
|
62
|
+
|
|
63
|
+
:param worker_id: 工作机器ID
|
|
64
|
+
:param datacenter_id: 数据中心ID
|
|
65
|
+
:return: 雪花算法生成的ID
|
|
66
|
+
"""
|
|
67
|
+
return _SnowflakeWorkerPool.get(worker_id, datacenter_id).next_id()
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def object_id() -> str:
|
|
71
|
+
"""生成MongoDB ObjectId格式的24位十六进制字符串"""
|
|
72
|
+
# 4字节时间戳 + 5字节随机值 + 3字节递增计数器
|
|
73
|
+
timestamp_part = format(int(time.time()), "08x")
|
|
74
|
+
random_part = secrets.token_hex(5)
|
|
75
|
+
counter_part = secrets.token_hex(3)
|
|
76
|
+
return timestamp_part + random_part + counter_part
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class _SnowflakeIdWorker:
|
|
80
|
+
"""雪花算法ID生成器内部实现"""
|
|
81
|
+
|
|
82
|
+
# 起始时间戳 (2020-01-01 00:00:00)
|
|
83
|
+
EPOCH = 1577808000000
|
|
84
|
+
|
|
85
|
+
# 各部分位数
|
|
86
|
+
WORKER_ID_BITS = 5
|
|
87
|
+
DATACENTER_ID_BITS = 5
|
|
88
|
+
SEQUENCE_BITS = 12
|
|
89
|
+
|
|
90
|
+
# 最大值
|
|
91
|
+
MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1 # 31
|
|
92
|
+
MAX_DATACENTER_ID = (1 << DATACENTER_ID_BITS) - 1 # 31
|
|
93
|
+
|
|
94
|
+
# 序列掩码
|
|
95
|
+
SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1 # 4095
|
|
96
|
+
|
|
97
|
+
# 左移位数
|
|
98
|
+
WORKER_ID_SHIFT = SEQUENCE_BITS # 12
|
|
99
|
+
DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS # 17
|
|
100
|
+
TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS # 22
|
|
101
|
+
|
|
102
|
+
def __init__(self, worker_id: int, datacenter_id: int):
|
|
103
|
+
"""初始化雪花算法ID生成器
|
|
104
|
+
|
|
105
|
+
:param worker_id: 工作机器ID,范围 [0, 31]
|
|
106
|
+
:param datacenter_id: 数据中心ID,范围 [0, 31]
|
|
107
|
+
"""
|
|
108
|
+
if worker_id < 0 or worker_id > self.MAX_WORKER_ID:
|
|
109
|
+
raise ValueError(f"worker_id({worker_id}) 必须在 [0, {self.MAX_WORKER_ID}] 范围内")
|
|
110
|
+
if datacenter_id < 0 or datacenter_id > self.MAX_DATACENTER_ID:
|
|
111
|
+
raise ValueError(f"datacenter_id({datacenter_id}) 必须在 [0, {self.MAX_DATACENTER_ID}] 范围内")
|
|
112
|
+
self._worker_id = worker_id
|
|
113
|
+
self._datacenter_id = datacenter_id
|
|
114
|
+
self._sequence = 0
|
|
115
|
+
self._last_timestamp = -1
|
|
116
|
+
self._lock = threading.Lock()
|
|
117
|
+
|
|
118
|
+
def next_id(self) -> int:
|
|
119
|
+
"""生成下一个ID(线程安全)"""
|
|
120
|
+
with self._lock:
|
|
121
|
+
timestamp = self._current_millis()
|
|
122
|
+
if timestamp < self._last_timestamp:
|
|
123
|
+
raise RuntimeError(f"时钟回拨,拒绝生成ID,回拨毫秒数: {self._last_timestamp - timestamp}")
|
|
124
|
+
if timestamp == self._last_timestamp:
|
|
125
|
+
self._sequence = (self._sequence + 1) & self.SEQUENCE_MASK
|
|
126
|
+
if self._sequence == 0:
|
|
127
|
+
timestamp = self._wait_next_millis()
|
|
128
|
+
else:
|
|
129
|
+
self._sequence = 0
|
|
130
|
+
self._last_timestamp = timestamp
|
|
131
|
+
return (
|
|
132
|
+
((timestamp - self.EPOCH) << self.TIMESTAMP_LEFT_SHIFT)
|
|
133
|
+
| (self._datacenter_id << self.DATACENTER_ID_SHIFT)
|
|
134
|
+
| (self._worker_id << self.WORKER_ID_SHIFT)
|
|
135
|
+
| self._sequence
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _current_millis(self) -> int:
|
|
139
|
+
"""获取当前时间戳(毫秒)"""
|
|
140
|
+
return int(time.time() * 1000)
|
|
141
|
+
|
|
142
|
+
def _wait_next_millis(self) -> int:
|
|
143
|
+
"""自旋等待直到下一毫秒"""
|
|
144
|
+
ts = self._current_millis()
|
|
145
|
+
while ts <= self._last_timestamp:
|
|
146
|
+
ts = self._current_millis()
|
|
147
|
+
return ts
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import date
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IdcardUtil:
|
|
6
|
+
"""身份证工具类"""
|
|
7
|
+
|
|
8
|
+
# 省份代码
|
|
9
|
+
_PROVINCE_CODES = {
|
|
10
|
+
"11": "北京",
|
|
11
|
+
"12": "天津",
|
|
12
|
+
"13": "河北",
|
|
13
|
+
"14": "山西",
|
|
14
|
+
"15": "内蒙古",
|
|
15
|
+
"21": "辽宁",
|
|
16
|
+
"22": "吉林",
|
|
17
|
+
"23": "黑龙江",
|
|
18
|
+
"31": "上海",
|
|
19
|
+
"32": "江苏",
|
|
20
|
+
"33": "浙江",
|
|
21
|
+
"34": "安徽",
|
|
22
|
+
"35": "福建",
|
|
23
|
+
"36": "江西",
|
|
24
|
+
"37": "山东",
|
|
25
|
+
"41": "河南",
|
|
26
|
+
"42": "湖北",
|
|
27
|
+
"43": "湖南",
|
|
28
|
+
"44": "广东",
|
|
29
|
+
"45": "广西",
|
|
30
|
+
"46": "海南",
|
|
31
|
+
"50": "重庆",
|
|
32
|
+
"51": "四川",
|
|
33
|
+
"52": "贵州",
|
|
34
|
+
"53": "云南",
|
|
35
|
+
"54": "西藏",
|
|
36
|
+
"61": "陕西",
|
|
37
|
+
"62": "甘肃",
|
|
38
|
+
"63": "青海",
|
|
39
|
+
"64": "宁夏",
|
|
40
|
+
"65": "新疆",
|
|
41
|
+
"71": "台湾",
|
|
42
|
+
"81": "香港",
|
|
43
|
+
"82": "澳门",
|
|
44
|
+
"91": "国外",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 加权因子(前17位)
|
|
48
|
+
_WEIGHT_FACTORS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
|
49
|
+
|
|
50
|
+
# 校验码对应值(余数 -> 校验码)
|
|
51
|
+
_CHECK_CODES = "10X98765432"
|
|
52
|
+
|
|
53
|
+
# 18位身份证正则
|
|
54
|
+
_ID_CARD_18_PATTERN = re.compile(r"^\d{6}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$")
|
|
55
|
+
|
|
56
|
+
# 15位身份证正则
|
|
57
|
+
_ID_CARD_15_PATTERN = re.compile(r"^\d{15}$")
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def is_valid_idcard(idcard: str) -> bool:
|
|
61
|
+
"""校验身份证号是否有效(支持15位和18位)
|
|
62
|
+
|
|
63
|
+
:param idcard: 身份证号
|
|
64
|
+
:return: 是否有效
|
|
65
|
+
"""
|
|
66
|
+
if not idcard:
|
|
67
|
+
return False
|
|
68
|
+
idcard = idcard.strip()
|
|
69
|
+
if len(idcard) == 18:
|
|
70
|
+
return IdcardUtil.is_valid_card18(idcard)
|
|
71
|
+
elif len(idcard) == 15:
|
|
72
|
+
return IdcardUtil.is_valid_card15(idcard)
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def is_valid_card18(idcard: str) -> bool:
|
|
77
|
+
"""校验18位身份证
|
|
78
|
+
|
|
79
|
+
校验规则:
|
|
80
|
+
1. 格式校验(17位数字 + 第18位数字或X/x)
|
|
81
|
+
2. 省份代码校验
|
|
82
|
+
3. 出生日期校验(合法的YYYYMMDD)
|
|
83
|
+
4. 校验码计算验证(加权求和 mod 11)
|
|
84
|
+
|
|
85
|
+
:param idcard: 18位身份证号
|
|
86
|
+
:return: 是否有效
|
|
87
|
+
"""
|
|
88
|
+
if not idcard:
|
|
89
|
+
return False
|
|
90
|
+
idcard = idcard.strip().upper()
|
|
91
|
+
|
|
92
|
+
# 1. 格式校验
|
|
93
|
+
if not IdcardUtil._ID_CARD_18_PATTERN.match(idcard):
|
|
94
|
+
# 也接受大写X
|
|
95
|
+
if not re.match(r"^\d{17}[\dX]$", idcard):
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
# 2. 省份代码校验
|
|
99
|
+
province = idcard[:2]
|
|
100
|
+
if province not in IdcardUtil._PROVINCE_CODES:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
# 3. 出生日期校验
|
|
104
|
+
birth_str = idcard[6:14]
|
|
105
|
+
try:
|
|
106
|
+
year = int(birth_str[:4])
|
|
107
|
+
month = int(birth_str[4:6])
|
|
108
|
+
day = int(birth_str[6:8])
|
|
109
|
+
birth_date = date(year, month, day)
|
|
110
|
+
# 日期不能在未来
|
|
111
|
+
if birth_date > date.today():
|
|
112
|
+
return False
|
|
113
|
+
except ValueError:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# 4. 校验码计算验证
|
|
117
|
+
# 前17位数字乘以加权因子求和
|
|
118
|
+
total = 0
|
|
119
|
+
for i in range(17):
|
|
120
|
+
total += int(idcard[i]) * IdcardUtil._WEIGHT_FACTORS[i]
|
|
121
|
+
# 取模11得到校验码索引
|
|
122
|
+
check_index = total % 11
|
|
123
|
+
expected_check = IdcardUtil._CHECK_CODES[check_index]
|
|
124
|
+
return idcard[17] == expected_check
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def is_valid_card15(idcard: str) -> bool:
|
|
128
|
+
"""校验15位身份证
|
|
129
|
+
|
|
130
|
+
:param idcard: 15位身份证号
|
|
131
|
+
:return: 是否有效
|
|
132
|
+
"""
|
|
133
|
+
if not idcard:
|
|
134
|
+
return False
|
|
135
|
+
idcard = idcard.strip()
|
|
136
|
+
|
|
137
|
+
# 格式校验
|
|
138
|
+
if not IdcardUtil._ID_CARD_15_PATTERN.match(idcard):
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
# 省份代码校验
|
|
142
|
+
province = idcard[:2]
|
|
143
|
+
if province not in IdcardUtil._PROVINCE_CODES:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
# 出生日期校验(15位:6位地区码 + 6位出生日期YYMMDD + 3位顺序码)
|
|
147
|
+
try:
|
|
148
|
+
year = int(idcard[6:8])
|
|
149
|
+
# 15位身份证的年份默认为19xx
|
|
150
|
+
year += 1900
|
|
151
|
+
month = int(idcard[8:10])
|
|
152
|
+
day = int(idcard[10:12])
|
|
153
|
+
birth_date = date(year, month, day)
|
|
154
|
+
if birth_date > date.today():
|
|
155
|
+
return False
|
|
156
|
+
except ValueError:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def get_birth(idcard: str) -> str:
|
|
163
|
+
"""获取出生日期字符串,如 "19900101"
|
|
164
|
+
|
|
165
|
+
:param idcard: 身份证号
|
|
166
|
+
:return: 出生日期字符串(YYYYMMDD格式),无效则返回空字符串
|
|
167
|
+
"""
|
|
168
|
+
if not idcard:
|
|
169
|
+
return ""
|
|
170
|
+
idcard = idcard.strip()
|
|
171
|
+
if len(idcard) == 18:
|
|
172
|
+
return idcard[6:14]
|
|
173
|
+
elif len(idcard) == 15:
|
|
174
|
+
return "19" + idcard[6:12]
|
|
175
|
+
return ""
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def get_age(idcard: str) -> int:
|
|
179
|
+
"""根据身份证号获取年龄
|
|
180
|
+
|
|
181
|
+
:param idcard: 身份证号
|
|
182
|
+
:return: 年龄,无效则返回-1
|
|
183
|
+
"""
|
|
184
|
+
if not idcard:
|
|
185
|
+
return -1
|
|
186
|
+
idcard = idcard.strip()
|
|
187
|
+
birth_str = IdcardUtil.get_birth(idcard)
|
|
188
|
+
if not birth_str or len(birth_str) != 8:
|
|
189
|
+
return -1
|
|
190
|
+
try:
|
|
191
|
+
year = int(birth_str[:4])
|
|
192
|
+
month = int(birth_str[4:6])
|
|
193
|
+
day = int(birth_str[6:8])
|
|
194
|
+
birth_date = date(year, month, day)
|
|
195
|
+
return IdcardUtil.get_age_by_birth(birth_date)
|
|
196
|
+
except ValueError:
|
|
197
|
+
return -1
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def get_age_by_birth(birth_date: date) -> int:
|
|
201
|
+
"""根据出生日期获取年龄
|
|
202
|
+
|
|
203
|
+
:param birth_date: 出生日期
|
|
204
|
+
:return: 年龄
|
|
205
|
+
"""
|
|
206
|
+
today = date.today()
|
|
207
|
+
age = today.year - birth_date.year
|
|
208
|
+
# 如果今年的生日还没到,年龄减1
|
|
209
|
+
if (today.month, today.day) < (birth_date.month, birth_date.day):
|
|
210
|
+
age -= 1
|
|
211
|
+
return age
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def get_gender(idcard: str) -> str:
|
|
215
|
+
"""获取性别
|
|
216
|
+
|
|
217
|
+
身份证第17位(18位身份证)或第15位(15位身份证):
|
|
218
|
+
奇数为男性,偶数为女性
|
|
219
|
+
|
|
220
|
+
:param idcard: 身份证号
|
|
221
|
+
:return: "M"(男)或 "F"(女),无效则返回空字符串
|
|
222
|
+
"""
|
|
223
|
+
if not idcard:
|
|
224
|
+
return ""
|
|
225
|
+
idcard = idcard.strip()
|
|
226
|
+
if len(idcard) == 18:
|
|
227
|
+
gender_digit = int(idcard[16])
|
|
228
|
+
elif len(idcard) == 15:
|
|
229
|
+
gender_digit = int(idcard[14])
|
|
230
|
+
else:
|
|
231
|
+
return ""
|
|
232
|
+
return "M" if gender_digit % 2 == 1 else "F"
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def get_province(idcard: str) -> str:
|
|
236
|
+
"""获取省份
|
|
237
|
+
|
|
238
|
+
:param idcard: 身份证号
|
|
239
|
+
:return: 省份名称,无效则返回空字符串
|
|
240
|
+
"""
|
|
241
|
+
if not idcard:
|
|
242
|
+
return ""
|
|
243
|
+
idcard = idcard.strip()
|
|
244
|
+
if len(idcard) < 2:
|
|
245
|
+
return ""
|
|
246
|
+
province_code = idcard[:2]
|
|
247
|
+
return IdcardUtil._PROVINCE_CODES.get(province_code, "")
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def convert15to18(idcard15: str) -> str:
|
|
251
|
+
"""15位身份证转18位
|
|
252
|
+
|
|
253
|
+
转换规则:
|
|
254
|
+
1. 在第6位后插入"19"(年份补全)
|
|
255
|
+
2. 计算第18位校验码
|
|
256
|
+
|
|
257
|
+
:param idcard15: 15位身份证号
|
|
258
|
+
:return: 18位身份证号,无效输入返回空字符串
|
|
259
|
+
"""
|
|
260
|
+
if not idcard15:
|
|
261
|
+
return ""
|
|
262
|
+
idcard15 = idcard15.strip()
|
|
263
|
+
if len(idcard15) != 15:
|
|
264
|
+
return ""
|
|
265
|
+
if not IdcardUtil._ID_CARD_15_PATTERN.match(idcard15):
|
|
266
|
+
return ""
|
|
267
|
+
|
|
268
|
+
# 在第6位后插入"19"
|
|
269
|
+
idcard17 = idcard15[:6] + "19" + idcard15[6:]
|
|
270
|
+
|
|
271
|
+
# 计算校验码
|
|
272
|
+
total = 0
|
|
273
|
+
for i in range(17):
|
|
274
|
+
total += int(idcard17[i]) * IdcardUtil._WEIGHT_FACTORS[i]
|
|
275
|
+
check_index = total % 11
|
|
276
|
+
check_code = IdcardUtil._CHECK_CODES[check_index]
|
|
277
|
+
|
|
278
|
+
return idcard17 + check_code
|
|
279
|
+
|
|
280
|
+
@staticmethod
|
|
281
|
+
def hide(idcard: str) -> str:
|
|
282
|
+
"""隐藏身份证号中间部分
|
|
283
|
+
|
|
284
|
+
18位身份证:保留前6位和后4位,中间用****代替
|
|
285
|
+
15位身份证:保留前4位和后4位,中间用****代替
|
|
286
|
+
|
|
287
|
+
:param idcard: 身份证号
|
|
288
|
+
:return: 脱敏后的身份证号
|
|
289
|
+
"""
|
|
290
|
+
if not idcard:
|
|
291
|
+
return ""
|
|
292
|
+
idcard = idcard.strip()
|
|
293
|
+
if len(idcard) == 18:
|
|
294
|
+
return idcard[:6] + "********" + idcard[14:]
|
|
295
|
+
elif len(idcard) == 15:
|
|
296
|
+
return idcard[:4] + "*******" + idcard[11:]
|
|
297
|
+
elif len(idcard) > 8:
|
|
298
|
+
half = (len(idcard) - 4) // 2
|
|
299
|
+
return idcard[:half] + "****" + idcard[half + 4 :]
|
|
300
|
+
return idcard
|