springbootAI 2.2.0__tar.gz → 2.2.2__tar.gz
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.
- {springbootai-2.2.0 → springbootai-2.2.2}/CHANGELOG.md +24 -0
- {springbootai-2.2.0/springbootAI.egg-info → springbootai-2.2.2}/PKG-INFO +6 -6
- {springbootai-2.2.0 → springbootai-2.2.2}/README.md +5 -5
- {springbootai-2.2.0 → springbootai-2.2.2}/pyproject.toml +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/__init__.py +2 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/ddl_auto.py +147 -36
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/mybatis_integration.py +2 -2
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sensitive_data_masker.py +19 -2
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sql_injection_detector.py +40 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/version.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/xml_parser/xml_parser.py +13 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/test/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/banner.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/swagger.py +2 -2
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/web_context.py +6 -3
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/__init__.py +1 -1
- {springbootai-2.2.0 → springbootai-2.2.2/springbootAI.egg-info}/PKG-INFO +6 -6
- {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/SOURCES.txt +2 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ddl_auto.py +82 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security.py +44 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security_hardening.py +14 -0
- springbootai-2.2.2/tests/test_web_root_mapping.py +22 -0
- springbootai-2.2.2/tests/test_web_static_files.py +22 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/LICENSE +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/MANIFEST.in +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/SECURITY.md +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/requirements-ai.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/requirements-langgraph.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/requirements-mcp.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/requirements.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/setup.cfg +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/setup.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/advisors.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/etl.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/memory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/observability.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/providers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/resilience.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/tools.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/ai/vectorstore.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/cloud.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/conditional.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/langchain.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/langgraph.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/annotations/messaging.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/aspect.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/cloud_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/comprehensive_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/method_interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/aop/proxy_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/discovery.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/feign.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/gateway.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/load_balancer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/seata.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/seata_bridge.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/sentinel.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/tracer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/cloud/transaction_store.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/binding.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/config/config_loader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/application_context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/bean_definition.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/bean_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/registry.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/context/scanner.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/graceful_shutdown.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/core/typing_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/converters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/easy_csv.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/reader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/csv/writer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/page.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/repository.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/data/specification.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/datasource/dynamic.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/event/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/event/publisher.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/converters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/easy_excel.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/reader.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/style.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/excel/writer.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/accessor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/auto_config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/holder.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/locale.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/locale_resolver.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/message_source.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/middleware.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/properties.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/i18n/sources.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/adapters.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/agents/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/agents/services.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/callbacks/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/callbacks/handlers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/chains/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/chains/services.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/core.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/indexes/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/indexes/index.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/loaders/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/loaders/loaders.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/memory/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/memory/memory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/parsers/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/parsers/parsers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/partners.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/prompts/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/prompts/templates.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/retrievers/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/retrievers/retrievers.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/tools/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/tools/tools.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/utilities/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/utilities/utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/vectorstores/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langchain/vectorstores/stores.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/annotation_runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/checkpoint.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/langgraph/runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/logging/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/logging/loguru_logger.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/main.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/annotation_runtime.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/autoconfig.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/client.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/mcp/server.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/messaging/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/messaging/rabbitmq.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/monitoring/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/monitoring/prometheus.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/database.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/migration.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/annotations/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/annotations/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/cache/redis_cache.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/circuit_breaker/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/circuit_breaker/circuit_breaker.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/configuration.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/sql_session.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/core/sql_session_factory.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dialect/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dialect/dialect.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dynamic_sql/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/dynamic_sql/dynamic_sql.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/interceptor/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/interceptor/interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/mapper/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/mapper/mapper.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/metrics/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/metrics/metrics.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/pool/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/pool/connection_pool.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/access_control.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/password_encoder.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/transaction/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/transaction/transaction.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/type_handler/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/type_handler/type_handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/xml_parser/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/recovery.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/retry_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/retry/retry_decorator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/scheduling/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/scheduling/scheduler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/jwt_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/replay_protection.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/secret_manager.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/security_aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/security/security_context.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/test/slicing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/tracing/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/tracing/skywalking.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/events.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/tx/synchronization.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/bean_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/logger.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/utils/redis_client.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/aop.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/constraints.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/validation/validator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/__init__.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/actuator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/exception_handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/health.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/interceptor.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/web/result.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/broker.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/exceptions.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/handler.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/router.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/spring/websocket/session.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/dependency_links.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/entry_points.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/requires.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/springbootAI.egg-info/top_level.txt +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/sql/seata_undo_log.sql +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_actuator.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_declarative_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_ai_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_annotation_combinations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_annotations_contract.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_aop_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_audit_time.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_bean_utils.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_cache_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_cloud_embedded_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_component_init_error.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_conditional_annotations.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_config_binding.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_config_fixes_185.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_connection_resilience.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_core_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_csv_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_data_repository.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_datasource_routing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_declarative_aop_post_authorize_recover.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_di_config_event_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_excel_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_i18n_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_jpa_version_transient.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_ext.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langchain_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langgraph_comprehensive.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_langgraph_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_logging_config.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_mcp_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_new_features.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_orm_pymybatis_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_production_readiness.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_pymybatis_contract.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_seata_bridge.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_seata_durable_store.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_security_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_swagger_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_test_slicing.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_transactional_events.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_validation_module.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_web_annotations_full.py +0 -0
- {springbootai-2.2.0 → springbootai-2.2.2}/tests/test_websocket_module.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.2.2] - 2026-08-13
|
|
4
|
+
|
|
5
|
+
### 新增
|
|
6
|
+
|
|
7
|
+
- ORM `@Entity` / `@Table` 注解对齐 Java JPA 分离风格,支持三种写法(均向后兼容):
|
|
8
|
+
|
|
9
|
+
| 写法 | 说明 | 对齐 Java |
|
|
10
|
+
|------|------|-----------|
|
|
11
|
+
| `@Entity` + `@Table(...)` | 分离风格(推荐) | `@Entity` + `@Table` |
|
|
12
|
+
| `@Entity` 无括号 | 表名自动推导为 snake_case | `@Entity`(无 `@Table`) |
|
|
13
|
+
| `@Entity("name", ...)` | 一体化风格(完全兼容) | 原有写法不变 |
|
|
14
|
+
|
|
15
|
+
- `Table` 类新增 `__call__`,可直接作为类装饰器使用(`@Table(name=..., indexes=[...], comment=...)`)。
|
|
16
|
+
- `@Entity` 支持无括号形式(`@Entity`),检测到已有 `@Table` 时不覆盖表元数据。
|
|
17
|
+
- 提取 `_auto_generate_init` 辅助函数,消除 `Entity` 与 `Table.__call__` 间的重复代码。
|
|
18
|
+
|
|
19
|
+
## [2.2.1] - 2026-08-13
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- 修复 ORM `@Entity`/`@entity` 实体解析、DDL 字段推断和 MyBatis 连接池初始化。
|
|
24
|
+
- 加强 SQL 注入检测、敏感数据脱敏、Mapper XML 解析和 Web 根路由映射。
|
|
25
|
+
- 增加对应回归测试,确保实体描述符、容器参数和静态资源路由行为稳定。
|
|
26
|
+
|
|
3
27
|
本项目从 `2.1.0` 开始按 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 记录面向使用者的变化,并使用语义化版本号。
|
|
4
28
|
|
|
5
29
|
## [2.2.0] - 2026-08-12
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: springbootAI
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Spring-style Python framework with Web, ORM, Cloud and AI integrations
|
|
5
5
|
Author-email: YuConggen <1516933915@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -151,7 +151,7 @@ SpringBootAI 是一个采用 Spring 风格注解和分层结构的 Python 应用
|
|
|
151
151
|
| AI 与编排 | 模型调用、Tools、RAG、Chain、状态图、MCP client/server | LangChain / LangGraph / 官方 MCP SDK |
|
|
152
152
|
| 生产治理 | 健康检查、Prometheus、限流熔断、追踪、Swagger | prometheus-client / OpenTelemetry / OpenAPI |
|
|
153
153
|
|
|
154
|
-
当前版本是 `2.2.
|
|
154
|
+
当前版本是 `2.2.1`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
|
|
155
155
|
|
|
156
156
|
[新手指南](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/BEGINNER_GUIDE.md) | [全部文档](https://github.com/YUCONGGEN/springbootAI/tree/master/doc) | [变更日志](https://github.com/YUCONGGEN/springbootAI/blob/master/CHANGELOG.md) | [安全报告](https://github.com/YUCONGGEN/springbootAI/blob/master/SECURITY.md) | [发布检查](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/RELEASE_CHECKLIST.md)
|
|
157
157
|
|
|
@@ -315,10 +315,10 @@ SpringBootAI 是一个 **Python Web 框架**。它把 Java Spring Boot 的"注
|
|
|
315
315
|
|
|
316
316
|
| 组件 | 当前版本 |
|
|
317
317
|
|------|----------|
|
|
318
|
-
| `spring` 框架 API | 2.2.
|
|
319
|
-
| `spring.orm.pymybatis` | 2.2.
|
|
320
|
-
| `spring.ai` AI 模块 | 2.2.
|
|
321
|
-
| `spring.langchain` LangChain 模块 | 2.2.
|
|
318
|
+
| `spring` 框架 API | 2.2.1 |
|
|
319
|
+
| `spring.orm.pymybatis` | 2.2.1 |
|
|
320
|
+
| `spring.ai` AI 模块 | 2.2.1 |
|
|
321
|
+
| `spring.langchain` LangChain 模块 | 2.2.1 |
|
|
322
322
|
| Python | 3.10+ |
|
|
323
323
|
|
|
324
324
|
### 1.4 适合什么场景
|
|
@@ -17,7 +17,7 @@ SpringBootAI 是一个采用 Spring 风格注解和分层结构的 Python 应用
|
|
|
17
17
|
| AI 与编排 | 模型调用、Tools、RAG、Chain、状态图、MCP client/server | LangChain / LangGraph / 官方 MCP SDK |
|
|
18
18
|
| 生产治理 | 健康检查、Prometheus、限流熔断、追踪、Swagger | prometheus-client / OpenTelemetry / OpenAPI |
|
|
19
19
|
|
|
20
|
-
当前版本是 `2.2.
|
|
20
|
+
当前版本是 `2.2.1`;支持 Python 3.10、3.11 和 3.12,许可证为 MIT。项目仍标记为 Beta。用于公网高并发、合规敏感或支付/订单/库存等核心系统前,必须完成目标数据库、流量模型、故障恢复和安全基线验证。内嵌 Gateway 适合内部路由,不替代公网 Nginx/Kong/WAF;Seata `distributed` 当前验证的是官方 TC + TCC 回调,不会为 Python 数据库操作自动生成 AT `undo_log`。
|
|
21
21
|
|
|
22
22
|
[新手指南](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/BEGINNER_GUIDE.md) | [全部文档](https://github.com/YUCONGGEN/springbootAI/tree/master/doc) | [变更日志](https://github.com/YUCONGGEN/springbootAI/blob/master/CHANGELOG.md) | [安全报告](https://github.com/YUCONGGEN/springbootAI/blob/master/SECURITY.md) | [发布检查](https://github.com/YUCONGGEN/springbootAI/blob/master/doc/RELEASE_CHECKLIST.md)
|
|
23
23
|
|
|
@@ -181,10 +181,10 @@ SpringBootAI 是一个 **Python Web 框架**。它把 Java Spring Boot 的"注
|
|
|
181
181
|
|
|
182
182
|
| 组件 | 当前版本 |
|
|
183
183
|
|------|----------|
|
|
184
|
-
| `spring` 框架 API | 2.2.
|
|
185
|
-
| `spring.orm.pymybatis` | 2.2.
|
|
186
|
-
| `spring.ai` AI 模块 | 2.2.
|
|
187
|
-
| `spring.langchain` LangChain 模块 | 2.2.
|
|
184
|
+
| `spring` 框架 API | 2.2.1 |
|
|
185
|
+
| `spring.orm.pymybatis` | 2.2.1 |
|
|
186
|
+
| `spring.ai` AI 模块 | 2.2.1 |
|
|
187
|
+
| `spring.langchain` LangChain 模块 | 2.2.1 |
|
|
188
188
|
| Python | 3.10+ |
|
|
189
189
|
|
|
190
190
|
### 1.4 适合什么场景
|
|
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
|
|
|
12
12
|
[project]
|
|
13
13
|
# 发行名 springbootAI(PyPI 包名,pip install springbootAI);导入包名仍为 spring
|
|
14
14
|
name = "springbootAI"
|
|
15
|
-
version = "2.2.
|
|
15
|
+
version = "2.2.2"
|
|
16
16
|
description = "Spring-style Python framework with Web, ORM, Cloud and AI integrations"
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
license = "MIT"
|
|
@@ -65,7 +65,7 @@ from .properties import load_properties, parse_properties
|
|
|
65
65
|
from .middleware import LocaleResolverMiddleware, get_request_locale
|
|
66
66
|
from .auto_config import MessageSourceAutoConfiguration, configure_message_source
|
|
67
67
|
|
|
68
|
-
__version__ = "2.2.
|
|
68
|
+
__version__ = "2.2.1"
|
|
69
69
|
|
|
70
70
|
__all__ = [
|
|
71
71
|
# Locale
|
|
@@ -146,6 +146,7 @@ from spring.orm.ddl_auto import (
|
|
|
146
146
|
UpdateTime,
|
|
147
147
|
Transient,
|
|
148
148
|
Index,
|
|
149
|
+
Entity,
|
|
149
150
|
entity,
|
|
150
151
|
table as table_decorator,
|
|
151
152
|
column as column_decorator,
|
|
@@ -247,6 +248,7 @@ __all__ = [
|
|
|
247
248
|
'UpdateTime',
|
|
248
249
|
'Transient',
|
|
249
250
|
'Index',
|
|
251
|
+
'Entity',
|
|
250
252
|
'entity',
|
|
251
253
|
'table_decorator',
|
|
252
254
|
'column_decorator',
|
|
@@ -244,12 +244,47 @@ class Transient:
|
|
|
244
244
|
|
|
245
245
|
|
|
246
246
|
class Table:
|
|
247
|
-
"""
|
|
247
|
+
"""``@Table`` 表注解(对齐 JPA ``javax.persistence.Table``)。
|
|
248
|
+
|
|
249
|
+
既可作为元数据类使用(内部 ``EntityTable`` 解析),也可直接作为类装饰器使用。
|
|
250
|
+
作为装饰器时等价于 JPA 的 ``@Table``,与 ``@Entity`` 组合使用::
|
|
251
|
+
|
|
252
|
+
@Entity
|
|
253
|
+
@Table(name="sys_user", indexes=[Index("idx_name", ["name"])], comment="用户表")
|
|
254
|
+
class User:
|
|
255
|
+
id: int = Id()
|
|
256
|
+
name: str = Column(length=50)
|
|
257
|
+
|
|
258
|
+
也可单独使用(隐含 ``@Entity`` 语义,与 ``@Entity("sys_user")`` 等价)::
|
|
259
|
+
|
|
260
|
+
@Table(name="sys_user", comment="用户表")
|
|
261
|
+
class User:
|
|
262
|
+
id: int = Id()
|
|
263
|
+
"""
|
|
248
264
|
def __init__(self, name: str = "", indexes: List['Index'] = None, comment: str = ""):
|
|
249
265
|
self.name = name
|
|
250
266
|
self.indexes = indexes or []
|
|
251
267
|
self.comment = comment
|
|
252
268
|
|
|
269
|
+
def __call__(self, cls):
|
|
270
|
+
"""装饰器形式:将表元数据挂载到类上并标记为实体,返回类本身。
|
|
271
|
+
|
|
272
|
+
装饰器应用顺序(Python 底向上)::
|
|
273
|
+
|
|
274
|
+
@Entity # 外层,后执行
|
|
275
|
+
@Table(...) # 内层,先执行
|
|
276
|
+
class User: ...
|
|
277
|
+
|
|
278
|
+
``@Table`` 先设置 ``__table__`` 与 ``__entity__``,随后 ``@Entity``
|
|
279
|
+
检测到已有 ``__table__`` 则不覆盖。
|
|
280
|
+
"""
|
|
281
|
+
if not self.name:
|
|
282
|
+
self.name = _camel_to_snake(cls.__name__)
|
|
283
|
+
setattr(cls, '__table__', self)
|
|
284
|
+
setattr(cls, '__entity__', True)
|
|
285
|
+
_auto_generate_init(cls)
|
|
286
|
+
return cls
|
|
287
|
+
|
|
253
288
|
|
|
254
289
|
class Index:
|
|
255
290
|
"""索引定义"""
|
|
@@ -480,37 +515,53 @@ class DdlAutoManager:
|
|
|
480
515
|
col_meta = self._get_field_meta(df)
|
|
481
516
|
columns.append(col_meta)
|
|
482
517
|
else:
|
|
483
|
-
#
|
|
518
|
+
# 普通实体同时支持显式 self.xxx 赋值、类型注解和类级 Column 描述符。
|
|
519
|
+
# 只依赖源码中的 self.xxx 会漏掉循环赋值或动态赋值的实体。
|
|
484
520
|
init_fields = self._extract_init_fields(cls)
|
|
485
|
-
# 同时检查类级别的类型注解(__init__ 的参数注解)
|
|
486
521
|
init_hints = {}
|
|
487
522
|
try:
|
|
488
523
|
init_hints = get_type_hints(cls.__init__)
|
|
489
524
|
except Exception:
|
|
490
525
|
pass
|
|
491
|
-
|
|
492
|
-
|
|
526
|
+
try:
|
|
527
|
+
cls_annotations = get_type_hints(cls)
|
|
528
|
+
except Exception:
|
|
529
|
+
cls_annotations = getattr(cls, '__annotations__', {})
|
|
530
|
+
|
|
531
|
+
descriptor_fields = {}
|
|
532
|
+
for cls_base in reversed(cls.__mro__):
|
|
533
|
+
for attr_name, value in cls_base.__dict__.items():
|
|
534
|
+
if isinstance(value, (Column, Transient)) or hasattr(value, '__column__'):
|
|
535
|
+
descriptor_fields[attr_name] = value
|
|
536
|
+
|
|
537
|
+
field_names = dict(init_fields)
|
|
538
|
+
for attr_name in cls_annotations:
|
|
539
|
+
field_names.setdefault(attr_name, None)
|
|
540
|
+
for attr_name in descriptor_fields:
|
|
541
|
+
field_names.setdefault(attr_name, None)
|
|
542
|
+
|
|
543
|
+
for attr_name, default_val in field_names.items():
|
|
493
544
|
# @Transient 字段不持久化,跳过
|
|
494
545
|
if _is_transient_field(cls, attr_name):
|
|
495
546
|
continue
|
|
496
|
-
# 优先取 __init__ 注解,其次取类注解
|
|
497
547
|
py_type = init_hints.get(attr_name) or cls_annotations.get(attr_name)
|
|
498
548
|
# 解包 Optional[X]:Python 3.10 的 get_type_hints 会把带 None 默认值的
|
|
499
549
|
# 参数注解自动包装为 Optional[X],3.11+ 不再包装。此处统一解包为承载类型,
|
|
500
550
|
# 否则 _get_sql_type 在 3.10 上识别失败回退到 TEXT,导致数值列被当字符串存储。
|
|
501
551
|
py_type = unwrap_optional_type(py_type)
|
|
552
|
+
descriptor = descriptor_fields.get(attr_name)
|
|
553
|
+
if py_type is None and isinstance(descriptor, Id):
|
|
554
|
+
py_type = int
|
|
555
|
+
elif py_type is None and isinstance(descriptor, (CreateTime, UpdateTime)):
|
|
556
|
+
py_type = str
|
|
557
|
+
elif py_type is None and isinstance(descriptor, Column) \
|
|
558
|
+
and descriptor.default is not None:
|
|
559
|
+
py_type = type(descriptor.default)
|
|
502
560
|
if py_type is None:
|
|
503
561
|
py_type = type(default_val) if default_val is not None and default_val != "" else str
|
|
504
|
-
|
|
505
|
-
col_info
|
|
506
|
-
|
|
507
|
-
if attr_name in cls_base.__dict__:
|
|
508
|
-
cval = cls_base.__dict__[attr_name]
|
|
509
|
-
if isinstance(cval, Column):
|
|
510
|
-
col_info = cval
|
|
511
|
-
elif hasattr(cval, '__column__'):
|
|
512
|
-
col_info = getattr(cval, '__column__')
|
|
513
|
-
break
|
|
562
|
+
col_info = descriptor
|
|
563
|
+
if not isinstance(col_info, Column) and hasattr(col_info, '__column__'):
|
|
564
|
+
col_info = getattr(col_info, '__column__')
|
|
514
565
|
col_meta = self._build_column_meta(attr_name, py_type, col_info)
|
|
515
566
|
columns.append(col_meta)
|
|
516
567
|
|
|
@@ -1030,31 +1081,97 @@ class DdlAutoManager:
|
|
|
1030
1081
|
|
|
1031
1082
|
# ==================== 装饰器API(便捷使用) ====================
|
|
1032
1083
|
|
|
1033
|
-
def
|
|
1084
|
+
def _auto_generate_init(cls):
|
|
1085
|
+
"""为描述符风格实体类自动生成 ``__init__``(若类未显式声明)。
|
|
1086
|
+
|
|
1087
|
+
扫描 MRO 中所有 ``Column`` 实例属性,生成关键字构造器。
|
|
1088
|
+
若类已显式声明 ``__init__`` 则跳过,保持兼容。
|
|
1034
1089
|
"""
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1090
|
+
if '__init__' in cls.__dict__:
|
|
1091
|
+
return
|
|
1092
|
+
fields = {}
|
|
1093
|
+
for cls_base in reversed(cls.__mro__):
|
|
1094
|
+
for name, value in cls_base.__dict__.items():
|
|
1095
|
+
if isinstance(value, Column):
|
|
1096
|
+
fields[name] = value
|
|
1097
|
+
|
|
1098
|
+
def __init__(self, **values):
|
|
1099
|
+
unknown = set(values) - set(fields)
|
|
1100
|
+
if unknown:
|
|
1101
|
+
names = ', '.join(sorted(unknown))
|
|
1102
|
+
raise TypeError(f"Unexpected entity field(s): {names}")
|
|
1103
|
+
for name, column in fields.items():
|
|
1104
|
+
setattr(self, name, values.get(name, column.default))
|
|
1105
|
+
|
|
1106
|
+
cls.__init__ = __init__
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def Entity(table_name: str = "", indexes: List[Index] = None, comment: str = ""):
|
|
1110
|
+
"""``@Entity`` 装饰器,标注一个类为 JPA 风格的实体类。
|
|
1111
|
+
|
|
1112
|
+
三种用法(均向后兼容):
|
|
1113
|
+
|
|
1114
|
+
1. **Java JPA 风格**(``@Entity`` + ``@Table`` 分离,推荐)::
|
|
1115
|
+
|
|
1116
|
+
@Entity
|
|
1117
|
+
@Table(name="sys_user", indexes=[Index("idx_name", ["name"])], comment="用户表")
|
|
1118
|
+
class User:
|
|
1119
|
+
id: int = Id()
|
|
1120
|
+
name: str = Column(length=50)
|
|
1121
|
+
|
|
1122
|
+
2. **简化风格**(仅 ``@Entity``,表名自动推导为类名 snake_case)::
|
|
1123
|
+
|
|
1124
|
+
@Entity
|
|
1125
|
+
class User:
|
|
1126
|
+
id: int = Id()
|
|
1127
|
+
name: str = Column(length=50)
|
|
1128
|
+
# 表名自动推导为 "user"
|
|
1129
|
+
|
|
1130
|
+
3. **一体化风格**(当前写法,完全兼容)::
|
|
1131
|
+
|
|
1132
|
+
@Entity("sys_user", indexes=[...], comment="用户表")
|
|
1039
1133
|
class User:
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
self.username = username
|
|
1043
|
-
self.email = email
|
|
1134
|
+
id: int = Id()
|
|
1135
|
+
name: str = Column(length=50)
|
|
1044
1136
|
"""
|
|
1045
|
-
|
|
1137
|
+
# 支持 @Entity 无括号形式:直接接收类
|
|
1138
|
+
if inspect.isclass(table_name):
|
|
1139
|
+
cls = table_name
|
|
1140
|
+
setattr(cls, '__entity__', True)
|
|
1141
|
+
# 未显式指定表信息时,不覆盖已有 @Table 设置的 __table__
|
|
1142
|
+
existing = getattr(cls, '__table__', None)
|
|
1143
|
+
if not isinstance(existing, Table):
|
|
1144
|
+
setattr(cls, '__table__', Table(name=_camel_to_snake(cls.__name__)))
|
|
1145
|
+
_auto_generate_init(cls)
|
|
1146
|
+
return cls
|
|
1147
|
+
|
|
1148
|
+
# @Entity("table_name", ...) 或 @Entity() 形式
|
|
1149
|
+
has_explicit_meta = bool(table_name or indexes or comment)
|
|
1150
|
+
t = Table(name=table_name, indexes=indexes, comment=comment) if has_explicit_meta else None
|
|
1151
|
+
|
|
1046
1152
|
def decorator(cls):
|
|
1047
1153
|
setattr(cls, '__entity__', True)
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1154
|
+
if t is not None:
|
|
1155
|
+
if not t.name:
|
|
1156
|
+
t.name = _camel_to_snake(cls.__name__)
|
|
1157
|
+
setattr(cls, '__table__', t)
|
|
1158
|
+
else:
|
|
1159
|
+
# 无显式表信息:不覆盖已有 @Table 设置
|
|
1160
|
+
existing = getattr(cls, '__table__', None)
|
|
1161
|
+
if not isinstance(existing, Table):
|
|
1162
|
+
setattr(cls, '__table__', Table(name=_camel_to_snake(cls.__name__)))
|
|
1163
|
+
_auto_generate_init(cls)
|
|
1051
1164
|
return cls
|
|
1052
1165
|
return decorator
|
|
1053
1166
|
|
|
1054
1167
|
|
|
1168
|
+
# Backward-compatible alias. New code should use the Spring-style @Entity name.
|
|
1169
|
+
entity = Entity
|
|
1170
|
+
|
|
1171
|
+
|
|
1055
1172
|
def table(name: str = "", indexes: List[Index] = None, comment: str = ""):
|
|
1056
1173
|
"""@Table 装饰器,标注实体类对应的表(与@entity功能相同,别名)"""
|
|
1057
|
-
return
|
|
1174
|
+
return Entity(name, indexes, comment)
|
|
1058
1175
|
|
|
1059
1176
|
|
|
1060
1177
|
# ==================== 全局集成 ====================
|
|
@@ -1106,12 +1223,6 @@ def init_ddl_auto(connection_pool, config: dict = None) -> Optional[DdlAutoManag
|
|
|
1106
1223
|
|
|
1107
1224
|
# 执行DDL
|
|
1108
1225
|
if manager._entities:
|
|
1109
|
-
# 临时关闭DDL阻断
|
|
1110
|
-
try:
|
|
1111
|
-
if hasattr(connection_pool, 'config'):
|
|
1112
|
-
connection_pool.config['security_block_ddl'] = False
|
|
1113
|
-
except Exception:
|
|
1114
|
-
pass
|
|
1115
1226
|
manager.execute()
|
|
1116
1227
|
|
|
1117
1228
|
_global_ddl_manager = manager
|
|
@@ -247,9 +247,9 @@ class MyBatisConfigurer:
|
|
|
247
247
|
try:
|
|
248
248
|
from spring.orm.ddl_auto import init_ddl_auto
|
|
249
249
|
# 获取连接池
|
|
250
|
-
pool = None
|
|
250
|
+
pool = getattr(self.sql_session_factory, 'connection_pool', None)
|
|
251
251
|
if hasattr(self.sql_session_factory, 'configuration'):
|
|
252
|
-
pool = getattr(self.sql_session_factory.configuration, 'pool', None)
|
|
252
|
+
pool = pool or getattr(self.sql_session_factory.configuration, 'pool', None)
|
|
253
253
|
if pool is None and hasattr(self.sql_session_factory, '_pool'):
|
|
254
254
|
pool = self.sql_session_factory._pool
|
|
255
255
|
if pool is not None:
|
{springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sensitive_data_masker.py
RENAMED
|
@@ -5,6 +5,7 @@ PyMyBatis敏感数据脱敏模块
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
from copy import copy
|
|
8
9
|
from typing import Any, Dict, Optional, Callable
|
|
9
10
|
from enum import Enum
|
|
10
11
|
|
|
@@ -159,6 +160,7 @@ class SensitiveDataMasker:
|
|
|
159
160
|
if strategy is None:
|
|
160
161
|
return value
|
|
161
162
|
|
|
163
|
+
original_value = value
|
|
162
164
|
# 转换为字符串进行脱敏
|
|
163
165
|
if not isinstance(value, str):
|
|
164
166
|
value = str(value)
|
|
@@ -170,7 +172,7 @@ class SensitiveDataMasker:
|
|
|
170
172
|
# 如果匹配失败,使用替换策略或返回原值
|
|
171
173
|
if strategy.pattern.pattern == r'.+':
|
|
172
174
|
return strategy.replacer(None)
|
|
173
|
-
return
|
|
175
|
+
return original_value
|
|
174
176
|
|
|
175
177
|
def mask_dict(self, data: Dict[str, Any], field_types: Optional[Dict[str, SensitiveDataType]] = None) -> Dict[str, Any]:
|
|
176
178
|
"""
|
|
@@ -227,7 +229,22 @@ class SensitiveDataMasker:
|
|
|
227
229
|
if not self.enabled:
|
|
228
230
|
return data_list
|
|
229
231
|
|
|
230
|
-
|
|
232
|
+
masked = []
|
|
233
|
+
for item in data_list:
|
|
234
|
+
if isinstance(item, dict):
|
|
235
|
+
masked.append(self.mask_dict(item, field_types))
|
|
236
|
+
elif hasattr(item, '__dict__'):
|
|
237
|
+
clone = copy(item)
|
|
238
|
+
attributes = {
|
|
239
|
+
key: value for key, value in vars(item).items()
|
|
240
|
+
if not key.startswith('_')
|
|
241
|
+
}
|
|
242
|
+
for key, value in self.mask_dict(attributes, field_types).items():
|
|
243
|
+
setattr(clone, key, value)
|
|
244
|
+
masked.append(clone)
|
|
245
|
+
else:
|
|
246
|
+
masked.append(item)
|
|
247
|
+
return masked
|
|
231
248
|
|
|
232
249
|
def mask_log(self, log_message: str) -> str:
|
|
233
250
|
"""
|
{springbootai-2.2.0 → springbootai-2.2.2}/spring/orm/pymybatis/security/sql_injection_detector.py
RENAMED
|
@@ -11,6 +11,7 @@ PyMyBatis SQL注入检测模块
|
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
13
|
import logging
|
|
14
|
+
from collections.abc import Mapping, Sequence, Set as AbstractSet
|
|
14
15
|
from typing import Optional, Any, Dict, Set
|
|
15
16
|
from enum import Enum
|
|
16
17
|
|
|
@@ -458,7 +459,11 @@ class SQLInjectionDetector:
|
|
|
458
459
|
|
|
459
460
|
def is_safe(self, value: Any) -> bool:
|
|
460
461
|
"""
|
|
461
|
-
|
|
462
|
+
判断值是否安全。
|
|
463
|
+
|
|
464
|
+
Mapper XML 支持通过 ``#{entity.field}`` 绑定实体对象,因此这里需要检查
|
|
465
|
+
容器中的叶子值,而不是检查对象的 ``repr``(其中通常包含内存地址)。
|
|
466
|
+
循环引用通过对象 id 集合截断。
|
|
462
467
|
|
|
463
468
|
Args:
|
|
464
469
|
value: 待检测的值
|
|
@@ -466,6 +471,40 @@ class SQLInjectionDetector:
|
|
|
466
471
|
Returns:
|
|
467
472
|
是否安全
|
|
468
473
|
"""
|
|
474
|
+
return self._is_safe_value(value, set())
|
|
475
|
+
|
|
476
|
+
def _is_safe_value(self, value: Any, visited: Set[int]) -> bool:
|
|
477
|
+
if value is None or isinstance(value, (str, bytes, int, float, bool)):
|
|
478
|
+
return not self.is_blocked(value)
|
|
479
|
+
|
|
480
|
+
if isinstance(value, Mapping):
|
|
481
|
+
object_id = id(value)
|
|
482
|
+
if object_id in visited:
|
|
483
|
+
return True
|
|
484
|
+
visited.add(object_id)
|
|
485
|
+
return all(self._is_safe_value(item, visited) for item in value.values())
|
|
486
|
+
|
|
487
|
+
if isinstance(value, (Sequence, AbstractSet)) and not isinstance(
|
|
488
|
+
value, (str, bytes, bytearray)
|
|
489
|
+
):
|
|
490
|
+
object_id = id(value)
|
|
491
|
+
if object_id in visited:
|
|
492
|
+
return True
|
|
493
|
+
visited.add(object_id)
|
|
494
|
+
return all(self._is_safe_value(item, visited) for item in value)
|
|
495
|
+
|
|
496
|
+
attributes = getattr(value, '__dict__', None)
|
|
497
|
+
if isinstance(attributes, Mapping):
|
|
498
|
+
object_id = id(value)
|
|
499
|
+
if object_id in visited:
|
|
500
|
+
return True
|
|
501
|
+
visited.add(object_id)
|
|
502
|
+
return all(
|
|
503
|
+
self._is_safe_value(item, visited)
|
|
504
|
+
for key, item in attributes.items()
|
|
505
|
+
if not str(key).startswith('_')
|
|
506
|
+
)
|
|
507
|
+
|
|
469
508
|
return not self.is_blocked(value)
|
|
470
509
|
|
|
471
510
|
def sanitize(self, value: Any) -> Any:
|
|
@@ -282,6 +282,19 @@ class XmlParser:
|
|
|
282
282
|
|
|
283
283
|
@classmethod
|
|
284
284
|
def _secure_fromstring(cls, xml_content: str):
|
|
285
|
+
# MyBatis mapper files conventionally contain this fixed public DTD.
|
|
286
|
+
# It is metadata only for this parser, so strip the exact allowlisted
|
|
287
|
+
# declaration without resolving the remote system identifier. Any
|
|
288
|
+
# other DTD or entity declaration remains forbidden by defusedxml.
|
|
289
|
+
xml_content = re.sub(
|
|
290
|
+
r'<!DOCTYPE\s+mapper\s+PUBLIC\s+'
|
|
291
|
+
r'["\']-//mybatis\.org//DTD Mapper 3\.0//EN["\']\s+'
|
|
292
|
+
r'["\']https?://mybatis\.org/dtd/mybatis-3-mapper\.dtd["\']\s*>',
|
|
293
|
+
'',
|
|
294
|
+
xml_content,
|
|
295
|
+
count=1,
|
|
296
|
+
flags=re.IGNORECASE,
|
|
297
|
+
)
|
|
285
298
|
return DefusedET.fromstring(
|
|
286
299
|
cls._normalize_comparison_operators(xml_content),
|
|
287
300
|
forbid_dtd=True,
|
|
@@ -11,7 +11,7 @@ class BannerPrinter:
|
|
|
11
11
|
|_| |___/
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, version: str = "2.2.
|
|
14
|
+
def __init__(self, version: str = "2.2.1"): # v1.8.7 修复 import 时提前创建 logs/ 目录
|
|
15
15
|
self.version = version
|
|
16
16
|
self.logger = SpringLogger()
|
|
17
17
|
|