springbootAI 1.8.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 (175) hide show
  1. spring/__init__.py +66 -0
  2. spring/ai/__init__.py +78 -0
  3. spring/ai/advisors.py +139 -0
  4. spring/ai/annotations.py +74 -0
  5. spring/ai/autoconfig.py +481 -0
  6. spring/ai/core.py +391 -0
  7. spring/ai/etl.py +188 -0
  8. spring/ai/memory.py +109 -0
  9. spring/ai/observability.py +129 -0
  10. spring/ai/providers.py +789 -0
  11. spring/ai/resilience.py +258 -0
  12. spring/ai/tools.py +106 -0
  13. spring/ai/vectorstore.py +303 -0
  14. spring/annotations/__init__.py +188 -0
  15. spring/annotations/cache.py +126 -0
  16. spring/annotations/cloud.py +207 -0
  17. spring/annotations/conditional.py +272 -0
  18. spring/annotations/core.py +864 -0
  19. spring/annotations/messaging.py +107 -0
  20. spring/aop/__init__.py +4 -0
  21. spring/aop/cloud_aop.py +404 -0
  22. spring/aop/comprehensive_aop.py +1015 -0
  23. spring/aop/method_interceptor.py +19 -0
  24. spring/aop/proxy_factory.py +55 -0
  25. spring/cloud/__init__.py +76 -0
  26. spring/cloud/discovery.py +364 -0
  27. spring/cloud/feign.py +469 -0
  28. spring/cloud/gateway.py +452 -0
  29. spring/cloud/load_balancer.py +149 -0
  30. spring/cloud/seata.py +557 -0
  31. spring/cloud/sentinel.py +525 -0
  32. spring/cloud/tracer.py +337 -0
  33. spring/config/__init__.py +21 -0
  34. spring/config/binding.py +206 -0
  35. spring/config/config_loader.py +405 -0
  36. spring/context/__init__.py +13 -0
  37. spring/context/application_context.py +589 -0
  38. spring/context/bean_definition.py +70 -0
  39. spring/context/bean_factory.py +1052 -0
  40. spring/context/registry.py +58 -0
  41. spring/context/scanner.py +106 -0
  42. spring/core/__init__.py +3 -0
  43. spring/core/graceful_shutdown.py +196 -0
  44. spring/core/typing_utils.py +50 -0
  45. spring/csv/__init__.py +52 -0
  46. spring/csv/annotations.py +402 -0
  47. spring/csv/converters.py +69 -0
  48. spring/csv/easy_csv.py +95 -0
  49. spring/csv/exceptions.py +27 -0
  50. spring/csv/reader.py +195 -0
  51. spring/csv/writer.py +155 -0
  52. spring/data/__init__.py +54 -0
  53. spring/data/page.py +181 -0
  54. spring/data/repository.py +274 -0
  55. spring/data/specification.py +228 -0
  56. spring/datasource/__init__.py +66 -0
  57. spring/datasource/annotations.py +133 -0
  58. spring/datasource/context.py +69 -0
  59. spring/datasource/dynamic.py +148 -0
  60. spring/event/__init__.py +7 -0
  61. spring/event/publisher.py +69 -0
  62. spring/excel/__init__.py +51 -0
  63. spring/excel/annotations.py +405 -0
  64. spring/excel/converters.py +231 -0
  65. spring/excel/easy_excel.py +94 -0
  66. spring/excel/exceptions.py +31 -0
  67. spring/excel/reader.py +254 -0
  68. spring/excel/style.py +95 -0
  69. spring/excel/writer.py +197 -0
  70. spring/i18n/__init__.py +97 -0
  71. spring/i18n/accessor.py +94 -0
  72. spring/i18n/auto_config.py +177 -0
  73. spring/i18n/holder.py +106 -0
  74. spring/i18n/locale.py +152 -0
  75. spring/i18n/locale_resolver.py +367 -0
  76. spring/i18n/message_source.py +250 -0
  77. spring/i18n/middleware.py +79 -0
  78. spring/i18n/properties.py +168 -0
  79. spring/i18n/sources.py +255 -0
  80. spring/logging/__init__.py +1 -0
  81. spring/logging/loguru_logger.py +228 -0
  82. spring/main.py +378 -0
  83. spring/messaging/__init__.py +1 -0
  84. spring/messaging/rabbitmq.py +302 -0
  85. spring/monitoring/__init__.py +1 -0
  86. spring/monitoring/prometheus.py +199 -0
  87. spring/orm/__init__.py +258 -0
  88. spring/orm/database.py +222 -0
  89. spring/orm/ddl_auto.py +1217 -0
  90. spring/orm/migration.py +419 -0
  91. spring/orm/mybatis_integration.py +400 -0
  92. spring/orm/pymybatis/__init__.py +86 -0
  93. spring/orm/pymybatis/annotations/__init__.py +30 -0
  94. spring/orm/pymybatis/annotations/annotations.py +332 -0
  95. spring/orm/pymybatis/cache/__init__.py +47 -0
  96. spring/orm/pymybatis/cache/cache.py +371 -0
  97. spring/orm/pymybatis/cache/redis_cache.py +434 -0
  98. spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
  99. spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
  100. spring/orm/pymybatis/configuration.py +525 -0
  101. spring/orm/pymybatis/core/__init__.py +10 -0
  102. spring/orm/pymybatis/core/sql_session.py +1382 -0
  103. spring/orm/pymybatis/core/sql_session_factory.py +76 -0
  104. spring/orm/pymybatis/dialect/__init__.py +9 -0
  105. spring/orm/pymybatis/dialect/dialect.py +445 -0
  106. spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
  107. spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
  108. spring/orm/pymybatis/interceptor/__init__.py +31 -0
  109. spring/orm/pymybatis/interceptor/interceptor.py +427 -0
  110. spring/orm/pymybatis/mapper/__init__.py +9 -0
  111. spring/orm/pymybatis/mapper/mapper.py +540 -0
  112. spring/orm/pymybatis/metrics/__init__.py +41 -0
  113. spring/orm/pymybatis/metrics/metrics.py +595 -0
  114. spring/orm/pymybatis/pool/__init__.py +9 -0
  115. spring/orm/pymybatis/pool/connection_pool.py +711 -0
  116. spring/orm/pymybatis/security/__init__.py +19 -0
  117. spring/orm/pymybatis/security/access_control.py +415 -0
  118. spring/orm/pymybatis/security/password_encoder.py +293 -0
  119. spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
  120. spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
  121. spring/orm/pymybatis/transaction/__init__.py +9 -0
  122. spring/orm/pymybatis/transaction/transaction.py +288 -0
  123. spring/orm/pymybatis/type_handler/__init__.py +37 -0
  124. spring/orm/pymybatis/type_handler/type_handler.py +473 -0
  125. spring/orm/pymybatis/version.py +9 -0
  126. spring/orm/pymybatis/xml_parser/__init__.py +9 -0
  127. spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
  128. spring/retry/__init__.py +12 -0
  129. spring/retry/retry_annotations.py +71 -0
  130. spring/retry/retry_decorator.py +155 -0
  131. spring/scheduling/__init__.py +3 -0
  132. spring/scheduling/scheduler.py +389 -0
  133. spring/security/__init__.py +39 -0
  134. spring/security/jwt_utils.py +281 -0
  135. spring/security/replay_protection.py +206 -0
  136. spring/security/secret_manager.py +226 -0
  137. spring/security/security_aop.py +248 -0
  138. spring/security/security_context.py +172 -0
  139. spring/test/__init__.py +45 -0
  140. spring/test/slicing.py +341 -0
  141. spring/tracing/__init__.py +11 -0
  142. spring/tracing/skywalking.py +229 -0
  143. spring/tx/__init__.py +52 -0
  144. spring/tx/events.py +172 -0
  145. spring/tx/synchronization.py +143 -0
  146. spring/utils/__init__.py +5 -0
  147. spring/utils/banner.py +32 -0
  148. spring/utils/logger.py +73 -0
  149. spring/utils/redis_client.py +526 -0
  150. spring/validation/__init__.py +55 -0
  151. spring/validation/aop.py +141 -0
  152. spring/validation/constraints.py +357 -0
  153. spring/validation/exceptions.py +55 -0
  154. spring/validation/validator.py +139 -0
  155. spring/web/__init__.py +12 -0
  156. spring/web/actuator.py +319 -0
  157. spring/web/exception_handler.py +61 -0
  158. spring/web/health.py +399 -0
  159. spring/web/interceptor.py +91 -0
  160. spring/web/result.py +44 -0
  161. spring/web/swagger.py +601 -0
  162. spring/web/web_context.py +755 -0
  163. spring/websocket/__init__.py +86 -0
  164. spring/websocket/annotations.py +169 -0
  165. spring/websocket/broker.py +238 -0
  166. spring/websocket/exceptions.py +26 -0
  167. spring/websocket/handler.py +243 -0
  168. spring/websocket/router.py +526 -0
  169. spring/websocket/session.py +216 -0
  170. springbootai-1.8.0.dist-info/METADATA +2796 -0
  171. springbootai-1.8.0.dist-info/RECORD +175 -0
  172. springbootai-1.8.0.dist-info/WHEEL +5 -0
  173. springbootai-1.8.0.dist-info/entry_points.txt +2 -0
  174. springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
  175. springbootai-1.8.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ """
2
+ PyMyBatis密码加密模块
3
+
4
+ 实现密码字段的安全哈希存储和验证,支持多种算法:
5
+ - 旧 MD5 哈希只读兼容(新编码升级为 PBKDF2-SHA256)
6
+ - PBKDF2-SHA256
7
+ - BCrypt
8
+ """
9
+
10
+ import hashlib
11
+ import hmac
12
+ import os
13
+ from typing import Optional
14
+ from enum import Enum
15
+
16
+
17
+ class EncryptionAlgorithm(Enum):
18
+ """加密算法枚举"""
19
+ MD5 = 'md5'
20
+ SHA256 = 'sha256'
21
+ BCRYPT = 'bcrypt'
22
+
23
+
24
+ class PasswordEncoder:
25
+ """
26
+ 密码加密器
27
+
28
+ 核心功能:
29
+ 1. 支持 BCrypt、PBKDF2-SHA256 和旧 MD5 哈希迁移
30
+ 2. 自动添加随机盐值(Salt)
31
+ 3. 密码验证
32
+ 4. 支持配置加密算法
33
+ """
34
+
35
+ def __init__(self, algorithm: str = 'bcrypt'):
36
+ """
37
+ 初始化密码加密器
38
+
39
+ Args:
40
+ algorithm: 加密算法(md5/sha256/bcrypt)
41
+ """
42
+ self.algorithm = EncryptionAlgorithm(algorithm.lower())
43
+ self._bcrypt = None
44
+
45
+ # 如果使用BCrypt,延迟加载bcrypt库
46
+ if self.algorithm == EncryptionAlgorithm.BCRYPT:
47
+ self._load_bcrypt()
48
+
49
+ def _load_bcrypt(self) -> None:
50
+ """加载BCrypt库"""
51
+ try:
52
+ import bcrypt
53
+ self._bcrypt = bcrypt
54
+ except ImportError:
55
+ raise ImportError("请安装bcrypt: pip install bcrypt")
56
+
57
+ def _generate_salt(self, length: int = 16) -> bytes:
58
+ """
59
+ 生成随机盐值
60
+
61
+ Args:
62
+ length: 盐值长度
63
+
64
+ Returns:
65
+ 盐值字节数组
66
+ """
67
+ return os.urandom(length)
68
+
69
+ def encode(self, password: str, salt: Optional[str] = None) -> str:
70
+ """
71
+ 加密密码
72
+
73
+ Args:
74
+ password: 明文密码
75
+ salt: 盐值,不指定则自动生成
76
+
77
+ Returns:
78
+ 加密后的密码字符串
79
+ """
80
+ if password is None:
81
+ raise ValueError("密码不能为空")
82
+
83
+ if self.algorithm == EncryptionAlgorithm.BCRYPT:
84
+ return self._encode_bcrypt(password)
85
+ elif self.algorithm == EncryptionAlgorithm.SHA256:
86
+ return self._encode_sha256(password, salt)
87
+ elif self.algorithm == EncryptionAlgorithm.MD5:
88
+ return self._encode_md5(password, salt)
89
+
90
+ raise ValueError(f"不支持的加密算法: {self.algorithm}")
91
+
92
+ def _encode_bcrypt(self, password: str) -> str:
93
+ """
94
+ 使用BCrypt加密密码
95
+
96
+ Args:
97
+ password: 明文密码
98
+
99
+ Returns:
100
+ 加密后的密码字符串
101
+ """
102
+ if self._bcrypt is None:
103
+ self._load_bcrypt()
104
+
105
+ salt = self._bcrypt.gensalt()
106
+ hashed = self._bcrypt.hashpw(password.encode('utf-8'), salt)
107
+ return hashed.decode('utf-8')
108
+
109
+ def _encode_sha256(self, password: str, salt: Optional[str] = None) -> str:
110
+ """
111
+ 使用SHA-256加密密码
112
+
113
+ Args:
114
+ password: 明文密码
115
+ salt: 盐值
116
+
117
+ Returns:
118
+ 加密后的密码字符串(格式:salt$hash)
119
+ """
120
+ if salt is None:
121
+ salt = self._generate_salt(16).hex()
122
+
123
+ salt_bytes = bytes.fromhex(salt)
124
+ password_bytes = password.encode('utf-8')
125
+
126
+ # 使用PBKDF2进行多次哈希
127
+ import hashlib
128
+ hashed = hashlib.pbkdf2_hmac(
129
+ 'sha256',
130
+ password_bytes,
131
+ salt_bytes,
132
+ 100000
133
+ )
134
+
135
+ return f"{salt}${hashed.hex()}"
136
+
137
+ def _encode_md5(self, password: str, salt: Optional[str] = None) -> str:
138
+ """
139
+ 兼容旧 ``algorithm=md5`` 配置,但新密码使用 PBKDF2-SHA256。
140
+
141
+ Args:
142
+ password: 明文密码
143
+ salt: 盐值
144
+
145
+ Returns:
146
+ 加密后的密码字符串(格式:salt$hash)
147
+ """
148
+ if salt is None:
149
+ salt = self._generate_salt(16).hex()
150
+
151
+ return f"pbkdf2_sha256${self._encode_sha256(password, salt)}"
152
+
153
+ def matches(self, raw_password: str, encoded_password: str) -> bool:
154
+ """
155
+ 验证密码是否匹配
156
+
157
+ Args:
158
+ raw_password: 明文密码
159
+ encoded_password: 加密后的密码
160
+
161
+ Returns:
162
+ 是否匹配
163
+ """
164
+ if raw_password is None or encoded_password is None:
165
+ return False
166
+
167
+ if self.algorithm == EncryptionAlgorithm.BCRYPT:
168
+ return self._matches_bcrypt(raw_password, encoded_password)
169
+ elif self.algorithm == EncryptionAlgorithm.SHA256:
170
+ return self._matches_sha256(raw_password, encoded_password)
171
+ elif self.algorithm == EncryptionAlgorithm.MD5:
172
+ return self._matches_md5(raw_password, encoded_password)
173
+
174
+ raise ValueError(f"不支持的加密算法: {self.algorithm}")
175
+
176
+ def _matches_bcrypt(self, raw_password: str, encoded_password: str) -> bool:
177
+ """
178
+ 使用BCrypt验证密码
179
+
180
+ Args:
181
+ raw_password: 明文密码
182
+ encoded_password: 加密后的密码
183
+
184
+ Returns:
185
+ 是否匹配
186
+ """
187
+ if self._bcrypt is None:
188
+ self._load_bcrypt()
189
+
190
+ return self._bcrypt.checkpw(
191
+ raw_password.encode('utf-8'),
192
+ encoded_password.encode('utf-8')
193
+ )
194
+
195
+ def _matches_sha256(self, raw_password: str, encoded_password: str) -> bool:
196
+ """
197
+ 使用SHA-256验证密码
198
+
199
+ Args:
200
+ raw_password: 明文密码
201
+ encoded_password: 加密后的密码
202
+
203
+ Returns:
204
+ 是否匹配
205
+ """
206
+ parts = encoded_password.split('$')
207
+ if len(parts) != 2:
208
+ return False
209
+
210
+ salt = parts[0]
211
+ expected_hash = parts[1]
212
+
213
+ salt_bytes = bytes.fromhex(salt)
214
+ password_bytes = raw_password.encode('utf-8')
215
+
216
+ hashed = hashlib.pbkdf2_hmac(
217
+ 'sha256',
218
+ password_bytes,
219
+ salt_bytes,
220
+ 100000
221
+ )
222
+
223
+ return hmac.compare_digest(hashed.hex(), expected_hash)
224
+
225
+ def _matches_md5(self, raw_password: str, encoded_password: str) -> bool:
226
+ """
227
+ 验证迁移模式密码;兼容读取旧 MD5,新的编码使用 PBKDF2-SHA256。
228
+
229
+ Args:
230
+ raw_password: 明文密码
231
+ encoded_password: 加密后的密码
232
+
233
+ Returns:
234
+ 是否匹配
235
+ """
236
+ parts = encoded_password.split('$')
237
+ if len(parts) == 3 and parts[0] == 'pbkdf2_sha256':
238
+ return self._matches_sha256(raw_password, '$'.join(parts[1:]))
239
+ if len(parts) != 2:
240
+ return False
241
+
242
+ salt = parts[0]
243
+ expected_hash = parts[1]
244
+
245
+ # 仅验证已存在的旧哈希;成功登录后应使用 encode() 重新哈希。
246
+ md5 = hashlib.md5(usedforsecurity=False)
247
+ md5.update(salt.encode('utf-8'))
248
+ md5.update(raw_password.encode('utf-8'))
249
+ hashed = md5.hexdigest()
250
+
251
+ return hmac.compare_digest(hashed, expected_hash)
252
+
253
+ def set_algorithm(self, algorithm: str) -> None:
254
+ """
255
+ 设置加密算法
256
+
257
+ Args:
258
+ algorithm: 加密算法名称
259
+ """
260
+ self.algorithm = EncryptionAlgorithm(algorithm.lower())
261
+ if self.algorithm == EncryptionAlgorithm.BCRYPT and self._bcrypt is None:
262
+ self._load_bcrypt()
263
+
264
+
265
+ # 全局默认密码加密器(使用BCrypt)
266
+ DEFAULT_PASSWORD_ENCODER = PasswordEncoder()
267
+
268
+
269
+ def encode_password(password: str) -> str:
270
+ """
271
+ 便捷函数:加密密码(使用默认加密器)
272
+
273
+ Args:
274
+ password: 明文密码
275
+
276
+ Returns:
277
+ 加密后的密码
278
+ """
279
+ return DEFAULT_PASSWORD_ENCODER.encode(password)
280
+
281
+
282
+ def verify_password(raw_password: str, encoded_password: str) -> bool:
283
+ """
284
+ 便捷函数:验证密码(使用默认加密器)
285
+
286
+ Args:
287
+ raw_password: 明文密码
288
+ encoded_password: 加密后的密码
289
+
290
+ Returns:
291
+ 是否匹配
292
+ """
293
+ return DEFAULT_PASSWORD_ENCODER.matches(raw_password, encoded_password)
@@ -0,0 +1,326 @@
1
+ """
2
+ PyMyBatis敏感数据脱敏模块
3
+
4
+ 实现对敏感数据(密码、身份证、手机号等)的脱敏处理
5
+ """
6
+
7
+ import re
8
+ from typing import Any, Dict, Optional, Callable
9
+ from enum import Enum
10
+
11
+
12
+ class SensitiveDataType(Enum):
13
+ """敏感数据类型"""
14
+ PASSWORD = 'password'
15
+ ID_CARD = 'id_card'
16
+ PHONE = 'phone'
17
+ EMAIL = 'email'
18
+ BANK_CARD = 'bank_card'
19
+ ADDRESS = 'address'
20
+ NAME = 'name'
21
+ CREDIT_CARD = 'credit_card'
22
+
23
+
24
+ class MaskStrategy:
25
+ """脱敏策略"""
26
+
27
+ def __init__(self, pattern: str, replacer: Callable[[str], str], description: str):
28
+ self.pattern = re.compile(pattern)
29
+ self.replacer = replacer
30
+ self.description = description
31
+
32
+
33
+ class SensitiveDataMasker:
34
+ """
35
+ 敏感数据脱敏器
36
+
37
+ 核心功能:
38
+ 1. 自动识别敏感数据类型
39
+ 2. 按类型进行脱敏处理
40
+ 3. 支持自定义脱敏规则
41
+ 4. 支持日志脱敏
42
+ """
43
+
44
+ # 默认脱敏策略
45
+ DEFAULT_STRATEGIES = {
46
+ SensitiveDataType.PASSWORD: MaskStrategy(
47
+ pattern=r'.+',
48
+ replacer=lambda x: '******',
49
+ description='密码全脱敏'
50
+ ),
51
+ SensitiveDataType.ID_CARD: MaskStrategy(
52
+ pattern=r'(\d{4})\d{10}(\d{4})',
53
+ replacer=lambda x: x.group(1) + '**********' + x.group(2),
54
+ description='身份证号保留前4后4'
55
+ ),
56
+ SensitiveDataType.PHONE: MaskStrategy(
57
+ pattern=r'(\d{3})\d{4}(\d{4})',
58
+ replacer=lambda x: x.group(1) + '****' + x.group(2),
59
+ description='手机号保留前3后4'
60
+ ),
61
+ SensitiveDataType.EMAIL: MaskStrategy(
62
+ pattern=r'([a-zA-Z0-9._%+-]{3})([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
63
+ replacer=lambda x: x.group(1) + '***@' + x.group(3),
64
+ description='邮箱用户名保留前3位'
65
+ ),
66
+ SensitiveDataType.BANK_CARD: MaskStrategy(
67
+ pattern=r'(\d{4})\d{8,12}(\d{4})',
68
+ replacer=lambda x: x.group(1) + '********' + x.group(2),
69
+ description='银行卡号保留前4后4'
70
+ ),
71
+ SensitiveDataType.ADDRESS: MaskStrategy(
72
+ pattern=r'(.{3}).+(.{2})',
73
+ replacer=lambda x: x.group(1) + '***' + x.group(2),
74
+ description='地址保留前3后2'
75
+ ),
76
+ SensitiveDataType.NAME: MaskStrategy(
77
+ pattern=r'([\u4e00-\u9fa5])([\u4e00-\u9fa5]+)',
78
+ replacer=lambda x: x.group(1) + '*' * len(x.group(2)),
79
+ description='姓名保留第一个字'
80
+ ),
81
+ SensitiveDataType.CREDIT_CARD: MaskStrategy(
82
+ pattern=r'(\d{4})\d{12}(\d{4})',
83
+ replacer=lambda x: x.group(1) + '************' + x.group(2),
84
+ description='信用卡号保留前4后4'
85
+ ),
86
+ }
87
+
88
+ # 自动检测模式
89
+ DETECTION_PATTERNS = {
90
+ SensitiveDataType.ID_CARD: re.compile(r'^\d{17}[\dXx]$'),
91
+ SensitiveDataType.PHONE: re.compile(r'^1[3-9]\d{9}$'),
92
+ SensitiveDataType.EMAIL: re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'),
93
+ SensitiveDataType.BANK_CARD: re.compile(r'^\d{16,19}$'),
94
+ SensitiveDataType.CREDIT_CARD: re.compile(r'^\d{16}$'),
95
+ }
96
+
97
+ def __init__(self, enabled: bool = True):
98
+ """
99
+ 初始化脱敏器
100
+
101
+ Args:
102
+ enabled: 是否启用脱敏
103
+ """
104
+ self.enabled = enabled
105
+ self.strategies = self.DEFAULT_STRATEGIES.copy()
106
+
107
+ def detect_type(self, value: Any) -> Optional[SensitiveDataType]:
108
+ """
109
+ 自动检测数据类型
110
+
111
+ Args:
112
+ value: 待检测的值
113
+
114
+ Returns:
115
+ 检测到的数据类型,未检测到返回None
116
+ """
117
+ if value is None:
118
+ return None
119
+
120
+ if not isinstance(value, str):
121
+ value = str(value)
122
+
123
+ for data_type, pattern in self.DETECTION_PATTERNS.items():
124
+ if pattern.match(value):
125
+ return data_type
126
+
127
+ return None
128
+
129
+ def mask(self, value: Any, data_type: Optional[SensitiveDataType] = None) -> Any:
130
+ """
131
+ 脱敏处理
132
+
133
+ Args:
134
+ value: 待脱敏的值(支持单个值或字典)
135
+ data_type: 指定数据类型,不指定则自动检测
136
+
137
+ Returns:
138
+ 脱敏后的值
139
+ """
140
+ # 如果是字典,调用mask_dict
141
+ if isinstance(value, dict):
142
+ return self.mask_dict(value)
143
+
144
+ if not self.enabled:
145
+ return value
146
+
147
+ if value is None:
148
+ return None
149
+
150
+ # 如果未指定类型,自动检测
151
+ if data_type is None:
152
+ data_type = self.detect_type(value)
153
+
154
+ if data_type is None:
155
+ return value
156
+
157
+ # 获取对应的脱敏策略
158
+ strategy = self.strategies.get(data_type)
159
+ if strategy is None:
160
+ return value
161
+
162
+ # 转换为字符串进行脱敏
163
+ if not isinstance(value, str):
164
+ value = str(value)
165
+
166
+ # 执行正则匹配
167
+ match_result = strategy.pattern.match(value)
168
+ if match_result:
169
+ return strategy.replacer(match_result)
170
+ # 如果匹配失败,使用替换策略或返回原值
171
+ if strategy.pattern.pattern == r'.+':
172
+ return strategy.replacer(None)
173
+ return value
174
+
175
+ def mask_dict(self, data: Dict[str, Any], field_types: Optional[Dict[str, SensitiveDataType]] = None) -> Dict[str, Any]:
176
+ """
177
+ 批量脱敏字典中的敏感字段
178
+
179
+ Args:
180
+ data: 待脱敏的字典
181
+ field_types: 字段名到数据类型的映射
182
+
183
+ Returns:
184
+ 脱敏后的字典
185
+ """
186
+ if not self.enabled:
187
+ return data
188
+
189
+ result = {}
190
+ field_types = field_types or {}
191
+
192
+ for key, value in data.items():
193
+ # 检查字段名是否包含敏感关键字
194
+ lower_key = key.lower()
195
+ if lower_key in ['password', 'pwd']:
196
+ data_type = SensitiveDataType.PASSWORD
197
+ elif lower_key in ['idcard', 'id_card', 'id', 'identity']:
198
+ data_type = SensitiveDataType.ID_CARD
199
+ elif lower_key in ['phone', 'mobile', 'tel']:
200
+ data_type = SensitiveDataType.PHONE
201
+ # 处理短手机号(少于11位)
202
+ if isinstance(value, str) and len(value) < 11:
203
+ result[key] = value[:3] + '****'
204
+ continue
205
+ elif lower_key in ['email', 'mail']:
206
+ data_type = SensitiveDataType.EMAIL
207
+ elif lower_key in ['bank_card', 'bankcard', 'card_no']:
208
+ data_type = SensitiveDataType.BANK_CARD
209
+ else:
210
+ data_type = field_types.get(key)
211
+
212
+ result[key] = self.mask(value, data_type)
213
+
214
+ return result
215
+
216
+ def mask_list(self, data_list: list, field_types: Optional[Dict[str, SensitiveDataType]] = None) -> list:
217
+ """
218
+ 批量脱敏列表中的字典
219
+
220
+ Args:
221
+ data_list: 待脱敏的列表
222
+ field_types: 字段名到数据类型的映射
223
+
224
+ Returns:
225
+ 脱敏后的列表
226
+ """
227
+ if not self.enabled:
228
+ return data_list
229
+
230
+ return [self.mask_dict(item, field_types) for item in data_list]
231
+
232
+ def mask_log(self, log_message: str) -> str:
233
+ """
234
+ 脱敏日志消息中的敏感数据
235
+
236
+ Args:
237
+ log_message: 日志消息
238
+
239
+ Returns:
240
+ 脱敏后的日志消息
241
+ """
242
+ return self.mask_message(log_message)
243
+
244
+ def mask_message(self, log_message: str) -> str:
245
+ """
246
+ 脱敏日志消息中的敏感数据(mask_log的别名)
247
+
248
+ Args:
249
+ log_message: 日志消息
250
+
251
+ Returns:
252
+ 脱敏后的日志消息
253
+ """
254
+ if not self.enabled:
255
+ return log_message
256
+
257
+ if not isinstance(log_message, str):
258
+ return log_message
259
+
260
+ # 使用简单的正则表达式直接匹配日志中的敏感数据
261
+ # 手机号
262
+ log_message = re.sub(r'1[3-9]\d{9}', lambda x: x.group()[:3] + '****' + x.group()[-4:], log_message)
263
+ # 身份证号
264
+ log_message = re.sub(r'\d{17}[\dXx]', lambda x: x.group()[:3] + '***' + x.group()[-4:], log_message)
265
+ # 邮箱
266
+ log_message = re.sub(
267
+ r'([a-zA-Z0-9._%+-])([a-zA-Z0-9._%+-]*)([a-zA-Z0-9._%+-])@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
268
+ lambda x: x.group(1) + '**' + x.group(3) + '@' + x.group(4), log_message)
269
+ # 银行卡号(16-19位)
270
+ log_message = re.sub(r'\d{16,19}', lambda x: x.group()[:4] + '********' + x.group()[-4:], log_message)
271
+ # 密码字段
272
+ log_message = re.sub(r'(password|pwd)\s*=\s*[^, \n]+', r'\1=********', log_message, flags=re.IGNORECASE)
273
+
274
+ return log_message
275
+
276
+ def add_strategy(self, data_type: SensitiveDataType, pattern: str, replacer: Callable[[str], str], description: str) -> None:
277
+ """
278
+ 添加自定义脱敏策略
279
+
280
+ Args:
281
+ data_type: 数据类型
282
+ pattern: 正则表达式模式
283
+ replacer: 替换函数
284
+ description: 策略描述
285
+ """
286
+ self.strategies[data_type] = MaskStrategy(pattern, replacer, description)
287
+
288
+ def remove_strategy(self, data_type: SensitiveDataType) -> None:
289
+ """
290
+ 移除脱敏策略
291
+
292
+ Args:
293
+ data_type: 数据类型
294
+ """
295
+ self.strategies.pop(data_type, None)
296
+
297
+
298
+ # 全局默认脱敏器实例
299
+ DEFAULT_MASKER = SensitiveDataMasker()
300
+
301
+
302
+ def mask_sensitive_data(value: Any, data_type: Optional[SensitiveDataType] = None) -> Any:
303
+ """
304
+ 便捷函数:脱敏敏感数据
305
+
306
+ Args:
307
+ value: 待脱敏的值
308
+ data_type: 数据类型
309
+
310
+ Returns:
311
+ 脱敏后的值
312
+ """
313
+ return DEFAULT_MASKER.mask(value, data_type)
314
+
315
+
316
+ def mask_log_message(log_message: str) -> str:
317
+ """
318
+ 便捷函数:脱敏日志消息
319
+
320
+ Args:
321
+ log_message: 日志消息
322
+
323
+ Returns:
324
+ 脱敏后的日志消息
325
+ """
326
+ return DEFAULT_MASKER.mask_log(log_message)