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.
- spring/__init__.py +66 -0
- spring/ai/__init__.py +78 -0
- spring/ai/advisors.py +139 -0
- spring/ai/annotations.py +74 -0
- spring/ai/autoconfig.py +481 -0
- spring/ai/core.py +391 -0
- spring/ai/etl.py +188 -0
- spring/ai/memory.py +109 -0
- spring/ai/observability.py +129 -0
- spring/ai/providers.py +789 -0
- spring/ai/resilience.py +258 -0
- spring/ai/tools.py +106 -0
- spring/ai/vectorstore.py +303 -0
- spring/annotations/__init__.py +188 -0
- spring/annotations/cache.py +126 -0
- spring/annotations/cloud.py +207 -0
- spring/annotations/conditional.py +272 -0
- spring/annotations/core.py +864 -0
- spring/annotations/messaging.py +107 -0
- spring/aop/__init__.py +4 -0
- spring/aop/cloud_aop.py +404 -0
- spring/aop/comprehensive_aop.py +1015 -0
- spring/aop/method_interceptor.py +19 -0
- spring/aop/proxy_factory.py +55 -0
- spring/cloud/__init__.py +76 -0
- spring/cloud/discovery.py +364 -0
- spring/cloud/feign.py +469 -0
- spring/cloud/gateway.py +452 -0
- spring/cloud/load_balancer.py +149 -0
- spring/cloud/seata.py +557 -0
- spring/cloud/sentinel.py +525 -0
- spring/cloud/tracer.py +337 -0
- spring/config/__init__.py +21 -0
- spring/config/binding.py +206 -0
- spring/config/config_loader.py +405 -0
- spring/context/__init__.py +13 -0
- spring/context/application_context.py +589 -0
- spring/context/bean_definition.py +70 -0
- spring/context/bean_factory.py +1052 -0
- spring/context/registry.py +58 -0
- spring/context/scanner.py +106 -0
- spring/core/__init__.py +3 -0
- spring/core/graceful_shutdown.py +196 -0
- spring/core/typing_utils.py +50 -0
- spring/csv/__init__.py +52 -0
- spring/csv/annotations.py +402 -0
- spring/csv/converters.py +69 -0
- spring/csv/easy_csv.py +95 -0
- spring/csv/exceptions.py +27 -0
- spring/csv/reader.py +195 -0
- spring/csv/writer.py +155 -0
- spring/data/__init__.py +54 -0
- spring/data/page.py +181 -0
- spring/data/repository.py +274 -0
- spring/data/specification.py +228 -0
- spring/datasource/__init__.py +66 -0
- spring/datasource/annotations.py +133 -0
- spring/datasource/context.py +69 -0
- spring/datasource/dynamic.py +148 -0
- spring/event/__init__.py +7 -0
- spring/event/publisher.py +69 -0
- spring/excel/__init__.py +51 -0
- spring/excel/annotations.py +405 -0
- spring/excel/converters.py +231 -0
- spring/excel/easy_excel.py +94 -0
- spring/excel/exceptions.py +31 -0
- spring/excel/reader.py +254 -0
- spring/excel/style.py +95 -0
- spring/excel/writer.py +197 -0
- spring/i18n/__init__.py +97 -0
- spring/i18n/accessor.py +94 -0
- spring/i18n/auto_config.py +177 -0
- spring/i18n/holder.py +106 -0
- spring/i18n/locale.py +152 -0
- spring/i18n/locale_resolver.py +367 -0
- spring/i18n/message_source.py +250 -0
- spring/i18n/middleware.py +79 -0
- spring/i18n/properties.py +168 -0
- spring/i18n/sources.py +255 -0
- spring/logging/__init__.py +1 -0
- spring/logging/loguru_logger.py +228 -0
- spring/main.py +378 -0
- spring/messaging/__init__.py +1 -0
- spring/messaging/rabbitmq.py +302 -0
- spring/monitoring/__init__.py +1 -0
- spring/monitoring/prometheus.py +199 -0
- spring/orm/__init__.py +258 -0
- spring/orm/database.py +222 -0
- spring/orm/ddl_auto.py +1217 -0
- spring/orm/migration.py +419 -0
- spring/orm/mybatis_integration.py +400 -0
- spring/orm/pymybatis/__init__.py +86 -0
- spring/orm/pymybatis/annotations/__init__.py +30 -0
- spring/orm/pymybatis/annotations/annotations.py +332 -0
- spring/orm/pymybatis/cache/__init__.py +47 -0
- spring/orm/pymybatis/cache/cache.py +371 -0
- spring/orm/pymybatis/cache/redis_cache.py +434 -0
- spring/orm/pymybatis/circuit_breaker/__init__.py +21 -0
- spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +424 -0
- spring/orm/pymybatis/configuration.py +525 -0
- spring/orm/pymybatis/core/__init__.py +10 -0
- spring/orm/pymybatis/core/sql_session.py +1382 -0
- spring/orm/pymybatis/core/sql_session_factory.py +76 -0
- spring/orm/pymybatis/dialect/__init__.py +9 -0
- spring/orm/pymybatis/dialect/dialect.py +445 -0
- spring/orm/pymybatis/dynamic_sql/__init__.py +9 -0
- spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +900 -0
- spring/orm/pymybatis/interceptor/__init__.py +31 -0
- spring/orm/pymybatis/interceptor/interceptor.py +427 -0
- spring/orm/pymybatis/mapper/__init__.py +9 -0
- spring/orm/pymybatis/mapper/mapper.py +540 -0
- spring/orm/pymybatis/metrics/__init__.py +41 -0
- spring/orm/pymybatis/metrics/metrics.py +595 -0
- spring/orm/pymybatis/pool/__init__.py +9 -0
- spring/orm/pymybatis/pool/connection_pool.py +711 -0
- spring/orm/pymybatis/security/__init__.py +19 -0
- spring/orm/pymybatis/security/access_control.py +415 -0
- spring/orm/pymybatis/security/password_encoder.py +293 -0
- spring/orm/pymybatis/security/sensitive_data_masker.py +326 -0
- spring/orm/pymybatis/security/sql_injection_detector.py +675 -0
- spring/orm/pymybatis/transaction/__init__.py +9 -0
- spring/orm/pymybatis/transaction/transaction.py +288 -0
- spring/orm/pymybatis/type_handler/__init__.py +37 -0
- spring/orm/pymybatis/type_handler/type_handler.py +473 -0
- spring/orm/pymybatis/version.py +9 -0
- spring/orm/pymybatis/xml_parser/__init__.py +9 -0
- spring/orm/pymybatis/xml_parser/xml_parser.py +761 -0
- spring/retry/__init__.py +12 -0
- spring/retry/retry_annotations.py +71 -0
- spring/retry/retry_decorator.py +155 -0
- spring/scheduling/__init__.py +3 -0
- spring/scheduling/scheduler.py +389 -0
- spring/security/__init__.py +39 -0
- spring/security/jwt_utils.py +281 -0
- spring/security/replay_protection.py +206 -0
- spring/security/secret_manager.py +226 -0
- spring/security/security_aop.py +248 -0
- spring/security/security_context.py +172 -0
- spring/test/__init__.py +45 -0
- spring/test/slicing.py +341 -0
- spring/tracing/__init__.py +11 -0
- spring/tracing/skywalking.py +229 -0
- spring/tx/__init__.py +52 -0
- spring/tx/events.py +172 -0
- spring/tx/synchronization.py +143 -0
- spring/utils/__init__.py +5 -0
- spring/utils/banner.py +32 -0
- spring/utils/logger.py +73 -0
- spring/utils/redis_client.py +526 -0
- spring/validation/__init__.py +55 -0
- spring/validation/aop.py +141 -0
- spring/validation/constraints.py +357 -0
- spring/validation/exceptions.py +55 -0
- spring/validation/validator.py +139 -0
- spring/web/__init__.py +12 -0
- spring/web/actuator.py +319 -0
- spring/web/exception_handler.py +61 -0
- spring/web/health.py +399 -0
- spring/web/interceptor.py +91 -0
- spring/web/result.py +44 -0
- spring/web/swagger.py +601 -0
- spring/web/web_context.py +755 -0
- spring/websocket/__init__.py +86 -0
- spring/websocket/annotations.py +169 -0
- spring/websocket/broker.py +238 -0
- spring/websocket/exceptions.py +26 -0
- spring/websocket/handler.py +243 -0
- spring/websocket/router.py +526 -0
- spring/websocket/session.py +216 -0
- springbootai-1.8.0.dist-info/METADATA +2796 -0
- springbootai-1.8.0.dist-info/RECORD +175 -0
- springbootai-1.8.0.dist-info/WHEEL +5 -0
- springbootai-1.8.0.dist-info/entry_points.txt +2 -0
- springbootai-1.8.0.dist-info/licenses/LICENSE +7 -0
- springbootai-1.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
"""
|
|
2
|
+
配置加载器
|
|
3
|
+
支持从YAML文件和环境变量加载配置
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import yaml
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from copy import deepcopy
|
|
11
|
+
from typing import Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("Spring.Config")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigurationError(ValueError):
|
|
17
|
+
"""应用配置缺失、格式错误或不满足生产安全要求。"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConfigLoader:
|
|
21
|
+
"""配置加载器"""
|
|
22
|
+
|
|
23
|
+
# ApplicationContext updates this after startup so later ``ConfigLoader()``
|
|
24
|
+
# calls resolve the same application.yml instead of depending on CWD.
|
|
25
|
+
_default_base_path: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
# 环境变量替换模式:${ENV_VAR} 或 ${ENV_VAR:default}
|
|
28
|
+
_ENV_VAR_PATTERN = re.compile(r'\$\{([^}]+)\}')
|
|
29
|
+
_VALUE_EXPRESSION_PATTERN = re.compile(r'^\$\{([^}:]+)(?::(.*))?\}$')
|
|
30
|
+
_MISSING = object()
|
|
31
|
+
|
|
32
|
+
def __init__(self, config_path: str = "application.yml", base_path: str = None, _test_mode: bool = False):
|
|
33
|
+
if base_path is None and config_path == "application.yml":
|
|
34
|
+
base_path = self.__class__._default_base_path
|
|
35
|
+
# 如果提供了base_path,则在该路径下查找配置文件
|
|
36
|
+
if base_path and config_path == "application.yml":
|
|
37
|
+
direct_path = os.path.join(base_path, config_path)
|
|
38
|
+
config_dir_path = os.path.join(base_path, "config", config_path)
|
|
39
|
+
self.config_path = direct_path if os.path.exists(direct_path) else config_dir_path
|
|
40
|
+
else:
|
|
41
|
+
self.config_path = config_path
|
|
42
|
+
self._config: Dict[str, Any] = {}
|
|
43
|
+
self._load_config()
|
|
44
|
+
|
|
45
|
+
def _resolve_env_var(self, value: str) -> Any:
|
|
46
|
+
"""
|
|
47
|
+
解析环境变量引用
|
|
48
|
+
|
|
49
|
+
支持格式:
|
|
50
|
+
- ${ENV_VAR} - 直接读取环境变量
|
|
51
|
+
- ${ENV_VAR:default} - 读取环境变量,不存在使用默认值
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
value: 包含环境变量引用的字符串
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
解析后的字符串
|
|
58
|
+
"""
|
|
59
|
+
if not isinstance(value, str):
|
|
60
|
+
return value
|
|
61
|
+
|
|
62
|
+
exact_placeholder = self._ENV_VAR_PATTERN.fullmatch(value)
|
|
63
|
+
|
|
64
|
+
def replace_env(match):
|
|
65
|
+
env_spec = match.group(1)
|
|
66
|
+
|
|
67
|
+
# 检查是否有默认值
|
|
68
|
+
if ':' in env_spec:
|
|
69
|
+
env_name, default_value = env_spec.split(':', 1)
|
|
70
|
+
else:
|
|
71
|
+
env_name = env_spec
|
|
72
|
+
default_value = None
|
|
73
|
+
|
|
74
|
+
# 从环境变量获取值
|
|
75
|
+
env_value = os.environ.get(env_name.strip())
|
|
76
|
+
|
|
77
|
+
# 如果环境变量不存在,使用默认值
|
|
78
|
+
if env_value is None:
|
|
79
|
+
if default_value is None:
|
|
80
|
+
raise ConfigurationError(f"必需的环境变量 {env_name.strip()} 未设置")
|
|
81
|
+
return default_value
|
|
82
|
+
|
|
83
|
+
return env_value
|
|
84
|
+
|
|
85
|
+
resolved = self._ENV_VAR_PATTERN.sub(replace_env, value)
|
|
86
|
+
if exact_placeholder:
|
|
87
|
+
parsed = yaml.safe_load(resolved)
|
|
88
|
+
if not isinstance(parsed, (dict, list)):
|
|
89
|
+
return parsed
|
|
90
|
+
return resolved
|
|
91
|
+
|
|
92
|
+
def _resolve_config_recursive(self, config: Any) -> Any:
|
|
93
|
+
"""
|
|
94
|
+
递归解析配置中的环境变量
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
config: 配置值(可能是字典、列表或字符串)
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
解析后的配置值
|
|
101
|
+
"""
|
|
102
|
+
if isinstance(config, str):
|
|
103
|
+
return self._resolve_env_var(config)
|
|
104
|
+
elif isinstance(config, dict):
|
|
105
|
+
return {key: self._resolve_config_recursive(value) for key, value in config.items()}
|
|
106
|
+
elif isinstance(config, list):
|
|
107
|
+
return [self._resolve_config_recursive(item) for item in config]
|
|
108
|
+
else:
|
|
109
|
+
return config
|
|
110
|
+
|
|
111
|
+
def _load_config(self):
|
|
112
|
+
"""加载配置"""
|
|
113
|
+
# 1. 尝试从YAML文件加载
|
|
114
|
+
if os.path.exists(self.config_path):
|
|
115
|
+
try:
|
|
116
|
+
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
117
|
+
self._config = yaml.safe_load(f) or {}
|
|
118
|
+
if not isinstance(self._config, dict):
|
|
119
|
+
raise ConfigurationError("配置文件根节点必须是对象")
|
|
120
|
+
logger.info(f"Loaded config from {self.config_path}")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Failed to load config from {self.config_path}: {e}")
|
|
123
|
+
raise ConfigurationError(f"无法加载配置文件 {self.config_path}") from e
|
|
124
|
+
|
|
125
|
+
# 2. 解析配置中的环境变量占位符
|
|
126
|
+
self._config = self._resolve_config_recursive(self._config)
|
|
127
|
+
|
|
128
|
+
# 3. 从环境变量覆盖配置
|
|
129
|
+
self._override_with_env()
|
|
130
|
+
self._validate_config()
|
|
131
|
+
|
|
132
|
+
def _override_with_env(self):
|
|
133
|
+
"""使用环境变量覆盖配置"""
|
|
134
|
+
self._config.setdefault('spring', {})
|
|
135
|
+
self._config['spring'].setdefault('profiles', {})
|
|
136
|
+
self._config['spring']['profiles']['active'] = os.getenv(
|
|
137
|
+
'SPRING_PROFILES_ACTIVE',
|
|
138
|
+
self._config['spring']['profiles'].get('active', 'default'),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
self._config.setdefault('startup', {})
|
|
142
|
+
fail_fast_env = os.getenv('STARTUP_FAIL_FAST')
|
|
143
|
+
configured_fail_fast = self._config['startup'].get('fail_fast')
|
|
144
|
+
if fail_fast_env is not None:
|
|
145
|
+
self._config['startup']['fail_fast'] = fail_fast_env.lower() == 'true'
|
|
146
|
+
elif configured_fail_fast is None:
|
|
147
|
+
self._config['startup'].pop('fail_fast', None)
|
|
148
|
+
else:
|
|
149
|
+
self._config['startup']['fail_fast'] = bool(configured_fail_fast)
|
|
150
|
+
|
|
151
|
+
# Redis配置
|
|
152
|
+
self._config.setdefault('redis', {})
|
|
153
|
+
self._config['redis']['host'] = os.getenv('REDIS_HOST', self._config['redis'].get('host', 'localhost'))
|
|
154
|
+
self._config['redis']['port'] = int(os.getenv('REDIS_PORT', str(self._config['redis'].get('port', 6379))))
|
|
155
|
+
self._config['redis']['db'] = int(os.getenv('REDIS_DB', str(self._config['redis'].get('db', 0))))
|
|
156
|
+
self._config['redis']['password'] = os.getenv('REDIS_PASSWORD', self._config['redis'].get('password'))
|
|
157
|
+
self._config['redis']['enabled'] = os.getenv('REDIS_ENABLED', str(self._config['redis'].get('enabled', False))).lower() == 'true'
|
|
158
|
+
|
|
159
|
+
# JWT配置
|
|
160
|
+
self._config.setdefault('jwt', {})
|
|
161
|
+
self._config['jwt']['secret_key'] = os.getenv('JWT_SECRET_KEY', self._config['jwt'].get('secret_key', 'spring-python-secret-key-change-in-production'))
|
|
162
|
+
self._config['jwt']['algorithm'] = os.getenv('JWT_ALGORITHM', self._config['jwt'].get('algorithm', 'HS256'))
|
|
163
|
+
|
|
164
|
+
# 数据库配置
|
|
165
|
+
self._config.setdefault('database', {})
|
|
166
|
+
self._config['database']['url'] = os.getenv('DB_URL', self._config['database'].get('url', 'sqlite:///./test.db'))
|
|
167
|
+
self._config['database']['echo'] = os.getenv('DB_ECHO', str(self._config['database'].get('echo', False))).lower() == 'true'
|
|
168
|
+
self._config['database']['enabled'] = os.getenv('DB_ENABLED', str(self._config['database'].get('enabled', False))).lower() == 'true'
|
|
169
|
+
# PyMyBatis原生数据源配置(host/port/driver等)
|
|
170
|
+
self._config['database']['driver'] = os.getenv('DB_DRIVER', self._config['database'].get('driver', 'sqlite'))
|
|
171
|
+
self._config['database']['host'] = os.getenv('DB_HOST', self._config['database'].get('host', 'localhost'))
|
|
172
|
+
self._config['database']['port'] = int(os.getenv('DB_PORT', str(self._config['database'].get('port', 3306))))
|
|
173
|
+
self._config['database']['database'] = os.getenv('DB_NAME', self._config['database'].get('database', 'test'))
|
|
174
|
+
self._config['database']['username'] = os.getenv('DB_USERNAME', self._config['database'].get('username', ''))
|
|
175
|
+
self._config['database']['password'] = os.getenv('DB_PASSWORD', self._config['database'].get('password', ''))
|
|
176
|
+
|
|
177
|
+
# 服务发现配置
|
|
178
|
+
self._config.setdefault('discovery', {})
|
|
179
|
+
self._config['discovery']['server_addr'] = os.getenv('DISCOVERY_SERVER_ADDR', self._config['discovery'].get('server_addr', 'localhost:8848'))
|
|
180
|
+
self._config['discovery']['namespace'] = os.getenv('DISCOVERY_NAMESPACE', self._config['discovery'].get('namespace', ''))
|
|
181
|
+
self._config['discovery']['group'] = os.getenv('DISCOVERY_GROUP', self._config['discovery'].get('group', 'DEFAULT_GROUP'))
|
|
182
|
+
self._config['discovery']['username'] = os.getenv('NACOS_USERNAME', self._config['discovery'].get('username', ''))
|
|
183
|
+
self._config['discovery']['password'] = os.getenv('NACOS_PASSWORD', self._config['discovery'].get('password', ''))
|
|
184
|
+
self._config['discovery']['enabled'] = os.getenv('DISCOVERY_ENABLED', str(self._config['discovery'].get('enabled', False))).lower() == 'true'
|
|
185
|
+
|
|
186
|
+
# Seata配置
|
|
187
|
+
self._config.setdefault('seata', {})
|
|
188
|
+
self._config['seata']['server_addr'] = os.getenv('SEATA_SERVER_ADDR', self._config['seata'].get('server_addr', 'localhost:8091'))
|
|
189
|
+
self._config['seata']['application_id'] = os.getenv('SEATA_APPLICATION_ID', self._config['seata'].get('application_id', ''))
|
|
190
|
+
self._config['seata']['transaction_group'] = os.getenv('SEATA_TRANSACTION_GROUP', self._config['seata'].get('transaction_group', 'my_tx_group'))
|
|
191
|
+
self._config['seata']['enabled'] = os.getenv('SEATA_ENABLED', str(self._config['seata'].get('enabled', False))).lower() == 'true'
|
|
192
|
+
|
|
193
|
+
# RabbitMQ配置
|
|
194
|
+
self._config.setdefault('rabbitmq', {})
|
|
195
|
+
self._config['rabbitmq']['host'] = os.getenv('RABBITMQ_HOST', self._config['rabbitmq'].get('host', 'localhost'))
|
|
196
|
+
self._config['rabbitmq']['port'] = int(os.getenv('RABBITMQ_PORT', str(self._config['rabbitmq'].get('port', 5672))))
|
|
197
|
+
self._config['rabbitmq']['username'] = os.getenv('RABBITMQ_USERNAME', self._config['rabbitmq'].get('username', 'guest'))
|
|
198
|
+
self._config['rabbitmq']['password'] = os.getenv('RABBITMQ_PASSWORD', self._config['rabbitmq'].get('password', 'guest'))
|
|
199
|
+
self._config['rabbitmq']['virtual_host'] = os.getenv('RABBITMQ_VIRTUAL_HOST', self._config['rabbitmq'].get('virtual_host', '/'))
|
|
200
|
+
self._config['rabbitmq']['enabled'] = os.getenv('RABBITMQ_ENABLED', str(self._config['rabbitmq'].get('enabled', False))).lower() == 'true'
|
|
201
|
+
|
|
202
|
+
# Prometheus配置
|
|
203
|
+
self._config.setdefault('prometheus', {})
|
|
204
|
+
self._config['prometheus']['namespace'] = os.getenv('PROMETHEUS_NAMESPACE', self._config['prometheus'].get('namespace', 'spring'))
|
|
205
|
+
self._config['prometheus']['subsystem'] = os.getenv('PROMETHEUS_SUBSYSTEM', self._config['prometheus'].get('subsystem', 'python'))
|
|
206
|
+
self._config['prometheus']['port'] = int(os.getenv('PROMETHEUS_PORT', str(self._config['prometheus'].get('port', 8000))))
|
|
207
|
+
self._config['prometheus']['enabled'] = os.getenv('PROMETHEUS_ENABLED', str(self._config['prometheus'].get('enabled', False))).lower() == 'true'
|
|
208
|
+
|
|
209
|
+
# 日志配置
|
|
210
|
+
self._config.setdefault('logging', {})
|
|
211
|
+
self._config['logging']['level'] = os.getenv('LOG_LEVEL', self._config['logging'].get('level', 'INFO'))
|
|
212
|
+
self._config['logging']['log_dir'] = os.getenv('LOG_DIR', self._config['logging'].get('log_dir', 'logs'))
|
|
213
|
+
self._config['logging']['retention'] = os.getenv('LOG_RETENTION', self._config['logging'].get('retention', '30 days'))
|
|
214
|
+
self._config['logging']['rotation'] = os.getenv('LOG_ROTATION', self._config['logging'].get('rotation', '100 MB'))
|
|
215
|
+
|
|
216
|
+
# 服务器配置
|
|
217
|
+
self._config.setdefault('server', {})
|
|
218
|
+
self._config['server']['port'] = int(os.getenv('SERVER_PORT', str(self._config['server'].get('port', 8080))))
|
|
219
|
+
self._config['server']['host'] = os.getenv('SERVER_HOST', self._config['server'].get('host', '0.0.0.0'))
|
|
220
|
+
|
|
221
|
+
self._config['server'].setdefault('cors', {})
|
|
222
|
+
cors_config = self._config['server']['cors']
|
|
223
|
+
origins_env = os.getenv('CORS_ALLOW_ORIGINS')
|
|
224
|
+
if origins_env is not None:
|
|
225
|
+
cors_config['allow_origins'] = [
|
|
226
|
+
origin.strip() for origin in origins_env.split(',') if origin.strip()
|
|
227
|
+
]
|
|
228
|
+
else:
|
|
229
|
+
cors_config.setdefault('allow_origins', [])
|
|
230
|
+
cors_config['allow_credentials'] = os.getenv(
|
|
231
|
+
'CORS_ALLOW_CREDENTIALS', str(cors_config.get('allow_credentials', False))
|
|
232
|
+
).lower() == 'true'
|
|
233
|
+
|
|
234
|
+
def _validate_config(self) -> None:
|
|
235
|
+
algorithm = str(self._config.get('jwt', {}).get('algorithm', 'HS256')).upper()
|
|
236
|
+
if algorithm not in {'HS256', 'HS384', 'HS512'}:
|
|
237
|
+
raise ConfigurationError(f"不允许的 JWT 算法: {algorithm}")
|
|
238
|
+
|
|
239
|
+
cors = self._config.get('server', {}).get('cors', {})
|
|
240
|
+
if cors.get('allow_credentials') and '*' in cors.get('allow_origins', []):
|
|
241
|
+
raise ConfigurationError("CORS 开启凭证时不能使用通配来源 *")
|
|
242
|
+
|
|
243
|
+
profile = str(
|
|
244
|
+
os.getenv('SPRING_PROFILES_ACTIVE')
|
|
245
|
+
or os.getenv('APP_ENV')
|
|
246
|
+
or self.get_active_profile()
|
|
247
|
+
).lower()
|
|
248
|
+
if profile not in {'prod', 'production'}:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
secret = self._config.get('jwt', {}).get('secret_key')
|
|
252
|
+
insecure_secret = 'spring-python-secret-key-change-in-production'
|
|
253
|
+
if not secret or secret == insecure_secret or len(str(secret)) < 32:
|
|
254
|
+
raise ConfigurationError("生产环境 JWT_SECRET_KEY 必须设置为至少 32 个字符的随机密钥")
|
|
255
|
+
|
|
256
|
+
seata_config = self._config.get('seata', {}) or {}
|
|
257
|
+
if seata_config.get('enabled'):
|
|
258
|
+
seata_mode = str(seata_config.get('mode', 'local')).lower()
|
|
259
|
+
if seata_mode != 'distributed':
|
|
260
|
+
raise ConfigurationError(
|
|
261
|
+
"生产环境启用 Seata 时只允许 mode=distributed;"
|
|
262
|
+
"实验性 HTTP/local 模式不能提供跨服务一致性"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def get_config(self) -> Dict[str, Any]:
|
|
266
|
+
"""获取完整配置"""
|
|
267
|
+
return deepcopy(self._config)
|
|
268
|
+
|
|
269
|
+
def get_prefix_config(self, prefix: str) -> Dict[str, Any]:
|
|
270
|
+
"""
|
|
271
|
+
获取指定前缀的配置
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
prefix: 配置前缀(如 'server', 'jwt')
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
前缀对应的配置字典
|
|
278
|
+
"""
|
|
279
|
+
value = self.get(prefix, {})
|
|
280
|
+
return deepcopy(value) if isinstance(value, dict) else {}
|
|
281
|
+
|
|
282
|
+
def resolve_value_expression(self, expression: Any, default: Any = None) -> Any:
|
|
283
|
+
"""Resolve an ``@Value`` / ``@NacosValue`` expression.
|
|
284
|
+
|
|
285
|
+
Both the concise Python form (``"server.port"``) and the familiar
|
|
286
|
+
Spring form (``"${server.port:8080}"``) are accepted. Property
|
|
287
|
+
lookup happens first, then an environment variable with the same name,
|
|
288
|
+
followed by the expression default. A missing value returns the
|
|
289
|
+
caller-provided *default* instead of leaking an annotation object into
|
|
290
|
+
a constructor argument.
|
|
291
|
+
"""
|
|
292
|
+
if not isinstance(expression, str):
|
|
293
|
+
return expression
|
|
294
|
+
|
|
295
|
+
match = self._VALUE_EXPRESSION_PATTERN.fullmatch(expression.strip())
|
|
296
|
+
if match:
|
|
297
|
+
key = match.group(1).strip()
|
|
298
|
+
expression_default = match.group(2)
|
|
299
|
+
else:
|
|
300
|
+
key = expression.strip()
|
|
301
|
+
expression_default = None
|
|
302
|
+
|
|
303
|
+
value = self.get(key, self._MISSING)
|
|
304
|
+
if value is not self._MISSING:
|
|
305
|
+
return value
|
|
306
|
+
|
|
307
|
+
env_value = os.getenv(key)
|
|
308
|
+
if env_value is not None:
|
|
309
|
+
parsed = yaml.safe_load(env_value)
|
|
310
|
+
return parsed if not isinstance(parsed, (dict, list)) else env_value
|
|
311
|
+
|
|
312
|
+
if expression_default is not None:
|
|
313
|
+
parsed = yaml.safe_load(expression_default)
|
|
314
|
+
return parsed if not isinstance(parsed, (dict, list)) else expression_default
|
|
315
|
+
return default
|
|
316
|
+
|
|
317
|
+
def get_value(self, key: str, default: Any = None) -> Any:
|
|
318
|
+
"""
|
|
319
|
+
获取配置值(支持点分隔路径)
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
key: 配置键,支持点分隔(如 'server.port')
|
|
323
|
+
default: 默认值
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
配置值
|
|
327
|
+
"""
|
|
328
|
+
return self.get(key, default)
|
|
329
|
+
|
|
330
|
+
def get_active_profile(self) -> str:
|
|
331
|
+
"""
|
|
332
|
+
获取当前激活的配置文件
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
激活的配置文件名(不含.yml后缀)
|
|
336
|
+
"""
|
|
337
|
+
return self._config.get('spring', {}).get('profiles', {}).get('active', 'default')
|
|
338
|
+
|
|
339
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
340
|
+
"""
|
|
341
|
+
获取配置值
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
key: 配置键,支持点分隔(如 redis.host)
|
|
345
|
+
default: 默认值
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
配置值
|
|
349
|
+
"""
|
|
350
|
+
keys = key.split('.')
|
|
351
|
+
value = self._config
|
|
352
|
+
|
|
353
|
+
for k in keys:
|
|
354
|
+
if isinstance(value, dict) and k in value:
|
|
355
|
+
value = value[k]
|
|
356
|
+
else:
|
|
357
|
+
return default
|
|
358
|
+
|
|
359
|
+
return value
|
|
360
|
+
|
|
361
|
+
def load_config(self):
|
|
362
|
+
"""加载配置(公共方法,供外部调用)"""
|
|
363
|
+
self._load_config()
|
|
364
|
+
|
|
365
|
+
def reload(self):
|
|
366
|
+
"""重新加载配置"""
|
|
367
|
+
self._config = {}
|
|
368
|
+
self._load_config()
|
|
369
|
+
logger.info("Config reloaded")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# 创建全局配置加载器实例
|
|
373
|
+
config_loader = ConfigLoader()
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def set_global_config_loader(loader: ConfigLoader) -> ConfigLoader:
|
|
377
|
+
"""Bind global configuration access to an application context loader."""
|
|
378
|
+
if not isinstance(loader, ConfigLoader):
|
|
379
|
+
raise TypeError("loader must be a ConfigLoader instance")
|
|
380
|
+
|
|
381
|
+
if loader is not config_loader:
|
|
382
|
+
shared_state = config_loader.__dict__
|
|
383
|
+
loader_state = dict(loader.__dict__)
|
|
384
|
+
shared_state.clear()
|
|
385
|
+
shared_state.update(loader_state)
|
|
386
|
+
loader.__dict__ = shared_state
|
|
387
|
+
|
|
388
|
+
ConfigLoader._default_base_path = os.path.dirname(
|
|
389
|
+
os.path.abspath(config_loader.config_path)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
config_package = sys.modules.get('spring.config')
|
|
393
|
+
if config_package is not None:
|
|
394
|
+
setattr(config_package, 'config_loader', config_loader)
|
|
395
|
+
return config_loader
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def get_config() -> Dict[str, Any]:
|
|
399
|
+
"""获取全局配置"""
|
|
400
|
+
return config_loader.get_config()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_config_value(key: str, default: Any = None) -> Any:
|
|
404
|
+
"""获取配置值"""
|
|
405
|
+
return config_loader.get(key, default)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .application_context import ApplicationContext
|
|
2
|
+
from .bean_definition import BeanDefinition
|
|
3
|
+
from .bean_factory import BeanFactory
|
|
4
|
+
from .scanner import ComponentScanner
|
|
5
|
+
from .registry import BeanRegistry
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ApplicationContext",
|
|
9
|
+
"BeanDefinition",
|
|
10
|
+
"BeanFactory",
|
|
11
|
+
"ComponentScanner",
|
|
12
|
+
"BeanRegistry",
|
|
13
|
+
]
|