Browse Source

代码优化、增加资源存储配置

mxd 4 years ago
parent
commit
781d8c063c

+ 74 - 95
src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIAutoConfiguration.java

@@ -7,11 +7,13 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.core.Ordered;
 import org.springframework.core.env.Environment;
@@ -32,7 +34,7 @@ import org.ssssssss.magicapi.cache.DefaultSqlCache;
 import org.ssssssss.magicapi.cache.SqlCache;
 import org.ssssssss.magicapi.config.*;
 import org.ssssssss.magicapi.controller.*;
-import org.ssssssss.magicapi.dialect.*;
+import org.ssssssss.magicapi.dialect.Dialect;
 import org.ssssssss.magicapi.interceptor.RequestInterceptor;
 import org.ssssssss.magicapi.interceptor.SQLInterceptor;
 import org.ssssssss.magicapi.logging.LoggerManager;
@@ -40,6 +42,7 @@ import org.ssssssss.magicapi.modules.*;
 import org.ssssssss.magicapi.provider.*;
 import org.ssssssss.magicapi.provider.impl.*;
 import org.ssssssss.magicapi.utils.ClassScanner;
+import org.ssssssss.magicapi.utils.PathUtils;
 import org.ssssssss.script.MagicResourceLoader;
 import org.ssssssss.script.MagicScript;
 import org.ssssssss.script.MagicScriptEngine;
@@ -58,6 +61,7 @@ import java.util.*;
 @Configuration
 @ConditionalOnClass({RequestMappingHandlerMapping.class})
 @EnableConfigurationProperties(MagicAPIProperties.class)
+@Import({MagicRedisAutoConfiguration.class, MagicMongoAutoConfiguration.class, MagicSwaggerConfiguration.class})
 public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 
 	private static final Logger logger = LoggerFactory.getLogger(MagicAPIAutoConfiguration.class);
@@ -66,10 +70,10 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	private MagicAPIProperties properties;
 
 	@Autowired(required = false)
-	private List<RequestInterceptor> requestInterceptors = Collections.emptyList();
+	private final List<RequestInterceptor> requestInterceptors = Collections.emptyList();
 
 	@Autowired(required = false)
-	private List<SQLInterceptor> sqlInterceptors = Collections.emptyList();
+	private final List<SQLInterceptor> sqlInterceptors = Collections.emptyList();
 
 	@Autowired
 	@Lazy
@@ -82,19 +86,19 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	 * 自定义的类型扩展
 	 */
 	@Autowired(required = false)
-	private List<ExtensionMethod> extensionMethods = Collections.emptyList();
+	private final List<ExtensionMethod> extensionMethods = Collections.emptyList();
 
 	/**
 	 * 内置的消息转换
 	 */
 	@Autowired(required = false)
-	private List<HttpMessageConverter<?>> httpMessageConverters = Collections.emptyList();
+	private final List<HttpMessageConverter<?>> httpMessageConverters = Collections.emptyList();
 
 	/**
 	 * 自定义的方言
 	 */
 	@Autowired(required = false)
-	private List<Dialect> dialects = Collections.emptyList();
+	private final List<Dialect> dialects = Collections.emptyList();
 
 	/**
 	 * 自定义的列名转换
@@ -120,9 +124,6 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	@Autowired
 	FunctionServiceProvider functionServiceProvider;
 
-	@Autowired(required = false)
-	MagicDynamicDataSource magicDynamicDataSource;
-
 	@Autowired
 	MappingHandlerMapping mappingHandlerMapping;
 
@@ -162,10 +163,36 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 		return ALL_CLASS_TXT;
 	}
 
+	/**
+	 * 注入动态数据源
+	 */
+	@Bean
+	@ConditionalOnMissingBean(MagicDynamicDataSource.class)
+	@ConditionalOnBean({DataSource.class})
+	public MagicDynamicDataSource magicDynamicDataSource(DataSource dataSource) {
+		MagicDynamicDataSource dynamicDataSource = new MagicDynamicDataSource();
+		dynamicDataSource.put(dataSource);
+		return dynamicDataSource;
+	}
+
 	@Bean
 	@ConditionalOnMissingBean(Resource.class)
-	public Resource magicWorkspaceResource() throws IOException {
-		return ResourceAdapter.getResource(properties.getWorkspace());
+	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "database")
+	public Resource magicDatabaseResource(MagicDynamicDataSource magicDynamicDataSource) throws IOException {
+		ResourceConfig resourceConfig = properties.getResource();
+		MagicDynamicDataSource.DataSourceNode dataSourceNode = magicDynamicDataSource.getDataSource(resourceConfig.getDatasource());
+		if (dataSourceNode == null) {
+			throw new IllegalArgumentException(String.format("找不到数据源:%s", resourceConfig.getDatasource()));
+		}
+		return ResourceAdapter.getResource(properties.getWorkspace(), resourceConfig.isReadonly());
+	}
+
+	@Bean
+	@ConditionalOnMissingBean(Resource.class)
+	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "file", matchIfMissing = true)
+	public Resource magicResource() throws IOException {
+		ResourceConfig resourceConfig = properties.getResource();
+		return ResourceAdapter.getResource(properties.getWorkspace(), resourceConfig.isReadonly());
 	}
 
 	@Override
@@ -190,8 +217,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 
 	@Override
 	public void addInterceptors(InterceptorRegistry registry) {
-		String web = properties.getWeb();
-		if (web != null) {
+		if (properties.isSupportCrossDomain()) {
 			registry.addInterceptor(new HandlerInterceptor() {
 				@Override
 				public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
@@ -208,7 +234,14 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	}
 
 	@Bean
-	public FilterRegistrationBean magicCorsFilterRegistrationBean() {
+	public MagicCorsFilter magicCorsFilter() {
+		return new MagicCorsFilter();
+	}
+
+
+	@Bean
+	@ConditionalOnProperty(prefix = "magic-api", value = "cors", havingValue = "true", matchIfMissing = true)
+	public FilterRegistrationBean<MagicCorsFilter> magicCorsFilterRegistrationBean() {
 		FilterRegistrationBean<MagicCorsFilter> registration = new FilterRegistrationBean<>(magicCorsFilter);
 		registration.addUrlPatterns("/*");
 		registration.setName("Magic Cors Filter");
@@ -217,12 +250,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	}
 
 	@Bean
-	public MagicCorsFilter magicCorsFilter() {
-		return new MagicCorsFilter();
-	}
-
 	@ConditionalOnMissingBean(PageProvider.class)
-	@Bean
 	public PageProvider pageProvider() {
 		PageConfig pageConfig = properties.getPageConfig();
 		logger.info("未找到分页实现,采用默认分页实现,分页配置:(页码={},页大小={},默认首页={},默认页大小={})", pageConfig.getPage(), pageConfig.getSize(), pageConfig.getDefaultPage(), pageConfig.getDefaultSize());
@@ -232,8 +260,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	/**
 	 * 注入结果构建方法
 	 */
-	@ConditionalOnMissingBean(ResultProvider.class)
 	@Bean
+	@ConditionalOnMissingBean(ResultProvider.class)
 	public ResultProvider resultProvider() {
 		return new DefaultResultProvider();
 	}
@@ -241,8 +269,8 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	/**
 	 * 注入SQL缓存实现
 	 */
-	@ConditionalOnMissingBean(SqlCache.class)
 	@Bean
+	@ConditionalOnMissingBean(SqlCache.class)
 	public SqlCache sqlCache() {
 		CacheConfig cacheConfig = properties.getCacheConfig();
 		logger.info("未找到SQL缓存实现,采用默认缓存实现(LRU+TTL),缓存配置:(容量={},TTL={})", cacheConfig.getCapacity(), cacheConfig.getTtl());
@@ -254,26 +282,15 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	 */
 	@Bean
 	public MappingHandlerMapping mappingHandlerMapping() throws NoSuchMethodException {
-		MappingHandlerMapping handlerMapping = new MappingHandlerMapping();
-		if (StringUtils.isNotBlank(properties.getPrefix())) {
-			String prefix = properties.getPrefix().trim();
-			if (!prefix.startsWith("/")) {
-				prefix = "/" + prefix;
-			}
-			if (!prefix.endsWith("/")) {
-				prefix = prefix + "/";
-			}
-			handlerMapping.setPrefix(prefix);
-		}
-		handlerMapping.setAllowOverride(properties.isAllowOverride());
-		return handlerMapping;
+		String prefix = StringUtils.isNotBlank(properties.getPrefix()) ? PathUtils.replaceSlash("/" + properties.getPrefix() + "/") : null;
+		return new MappingHandlerMapping(prefix, properties.isAllowOverride());
 	}
 
 
 	@Bean
 	@ConditionalOnMissingBean(FunctionServiceProvider.class)
-	public FunctionServiceProvider functionServiceProvider(GroupServiceProvider groupServiceProvider,Resource magicWorkspaceResource) {
-		return new DefaultFunctionServiceProvider(groupServiceProvider, magicWorkspaceResource);
+	public FunctionServiceProvider functionServiceProvider(GroupServiceProvider groupServiceProvider, Resource magicResource) {
+		return new DefaultFunctionServiceProvider(groupServiceProvider, magicResource);
 	}
 
 	/**
@@ -281,17 +298,17 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 	 */
 	@Bean
 	@ConditionalOnMissingBean(GroupServiceProvider.class)
-	public GroupServiceProvider groupServiceProvider(Resource magicWorkspaceResource) {
-		return new DefaultGroupServiceProvider(magicWorkspaceResource);
+	public GroupServiceProvider groupServiceProvider(Resource magicResource) {
+		return new DefaultGroupServiceProvider(magicResource);
 	}
 
 	/**
 	 * 注入接口存储service
 	 */
-	@ConditionalOnMissingBean(ApiServiceProvider.class)
 	@Bean
-	public ApiServiceProvider apiServiceProvider(GroupServiceProvider groupServiceProvider,Resource magicWorkspaceResource) {
-		return new DefaultApiServiceProvider(groupServiceProvider, magicWorkspaceResource);
+	@ConditionalOnMissingBean(ApiServiceProvider.class)
+	public ApiServiceProvider apiServiceProvider(GroupServiceProvider groupServiceProvider, Resource magicResource) {
+		return new DefaultApiServiceProvider(groupServiceProvider, magicResource);
 	}
 
 
@@ -308,7 +325,6 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 		try {
 			clazz = Class.forName("org.springframework.security.core.context.SecurityContextHolder");
 		} catch (ClassNotFoundException ignored) {
-
 		}
 		if (clazz != null) {
 			try {
@@ -333,30 +349,13 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 		sqlModule.setPageProvider(pageProvider);
 		sqlModule.setSqlInterceptors(sqlInterceptors);
 		ColumnMapperAdapter columnMapperAdapter = new ColumnMapperAdapter();
-		columnMapperAdapter.setDefault(new DefaultColumnMapperProvider());
-		columnMapperAdapter.add(new CamelColumnMapperProvider());
-		columnMapperAdapter.add(new PascalColumnMapperProvider());
-		columnMapperAdapter.add(new LowerColumnMapperProvider());
-		columnMapperAdapter.add(new UpperColumnMapperProvider());
-		for (ColumnMapperProvider mapperProvider : this.columnMapperProviders) {
-			if (!"default".equals(mapperProvider.name())) {
-				columnMapperAdapter.add(mapperProvider);
-			}
-		}
+		this.columnMapperProviders.stream().filter(mapperProvider -> !"default".equals(mapperProvider.name())).forEach(columnMapperAdapter::add);
 		columnMapperAdapter.setDefault(properties.getSqlColumnCase());
 		sqlModule.setColumnMapperProvider(columnMapperAdapter);
 		sqlModule.setColumnMapRowMapper(columnMapperAdapter.getDefaultColumnMapRowMapper());
 		sqlModule.setRowMapColumnMapper(columnMapperAdapter.getDefaultRowMapColumnMapper());
 		sqlModule.setSqlCache(sqlCache);
 		DialectAdapter dialectAdapter = new DialectAdapter();
-		dialectAdapter.add(new MySQLDialect());
-		dialectAdapter.add(new OracleDialect());
-		dialectAdapter.add(new PostgreSQLDialect());
-		dialectAdapter.add(new ClickhouseDialect());
-		dialectAdapter.add(new DB2Dialect());
-		dialectAdapter.add(new SQLServerDialect());
-		dialectAdapter.add(new SQLServer2005Dialect());
-		dialectAdapter.add(new DmDialect());
 		dialects.forEach(dialectAdapter::add);
 		sqlModule.setDialectAdapter(dialectAdapter);
 		return sqlModule;
@@ -391,47 +390,27 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 		MagicResourceLoader.addModule("response", new ResponseModule(resultProvider));
 		logger.info("注册模块:{} -> {}", "assert", AssertModule.class);
 		MagicResourceLoader.addModule("assert", AssertModule.class);
-		for (MagicModule module : magicModules) {
+		magicModules.forEach(module -> {
 			logger.info("注册模块:{} -> {}", module.getModuleName(), module.getClass());
 			MagicResourceLoader.addModule(module.getModuleName(), module);
-		}
-		Set<String> moduleNames = MagicResourceLoader.getModuleNames();
-		for (String moduleName : moduleNames) {
-			if (importModules.contains(moduleName)) {
-				logger.info("自动导入模块:{}", moduleName);
-				MagicScriptEngine.addDefaultImport(moduleName, MagicResourceLoader.loadModule(moduleName));
-			}
-		}
-		List<String> importPackages = properties.getAutoImportPackageList();
-		for (String importPackage : importPackages) {
+		});
+		MagicResourceLoader.getModuleNames().stream().filter(importModules::contains).forEach(moduleName -> {
+			logger.info("自动导入模块:{}", moduleName);
+			MagicScriptEngine.addDefaultImport(moduleName, MagicResourceLoader.loadModule(moduleName));
+		});
+		properties.getAutoImportPackageList().forEach(importPackage -> {
 			logger.info("自动导包:{}", importPackage);
 			MagicResourceLoader.addPackage(importPackage);
-		}
-		for (ExtensionMethod extension : extensionMethods) {
-			List<Class<?>> supports = extension.supports();
-			for (Class<?> support : supports) {
-				logger.info("注册扩展:{} -> {}", support, extension.getClass());
-				AbstractReflection.getInstance().registerMethodExtension(support, extension);
-			}
-		}
-	}
-
-
-	/**
-	 * 注入动态数据源
-	 */
-	@Bean
-	@ConditionalOnMissingBean(MagicDynamicDataSource.class)
-	@ConditionalOnBean({DataSource.class})
-	public MagicDynamicDataSource magicDynamicDataSource(DataSource dataSource) {
-		MagicDynamicDataSource dynamicDataSource = new MagicDynamicDataSource();
-		dynamicDataSource.put(dataSource);
-		return dynamicDataSource;
+		});
+		extensionMethods.forEach(extension -> extension.supports().forEach(support -> {
+			logger.info("注册扩展:{} -> {}", support, extension.getClass());
+			AbstractReflection.getInstance().registerMethodExtension(support, extension);
+		}));
 	}
 
 	@Bean
-	public MagicConfiguration magicConfiguration(@Autowired List<MagicModule> magicModules,@Autowired  Resource magicWorkspaceResource) {
-		logger.info("magic-api工作目录:{}",magicWorkspaceResource);
+	public MagicConfiguration magicConfiguration(List<MagicModule> magicModules, @Autowired(required = false) MagicDynamicDataSource magicDynamicDataSource, Resource magicResource) {
+		logger.info("magic-api工作目录:{}", magicResource);
 		setupSpringSecurity();
 		AsyncCall.setThreadPoolExecutorSize(properties.getThreadPoolExecutorSize());
 		// 设置模块和扩展方法
@@ -450,7 +429,7 @@ public class MagicAPIAutoConfiguration implements WebMvcConfigurer {
 		configuration.setThrowException(properties.isThrowException());
 		configuration.setMagicDynamicDataSource(magicDynamicDataSource);
 		configuration.setEditorConfig(properties.getEditorConfig());
-		configuration.setWorkspace(magicWorkspaceResource);
+		configuration.setWorkspace(magicResource);
 		// 注册函数
 		this.magicFunctions.forEach(JavaReflection::registerFunction);
 		// 向页面传递配置信息时不传递用户名密码,增强安全性

+ 27 - 1
src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicAPIProperties.java

@@ -1,7 +1,6 @@
 package org.ssssssss.magicapi.spring.boot.starter;
 
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Required;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.boot.context.properties.NestedConfigurationProperty;
 import org.ssssssss.magicapi.controller.RequestHandler;
@@ -87,6 +86,13 @@ public class MagicAPIProperties {
 	 */
 	private final String version = RequestHandler.class.getPackage().getImplementationVersion();
 
+	/**
+	 * 是否启用跨域支持
+	 *
+	 * @since 1.0.0
+	 */
+	private boolean supportCrossDomain = true;
+
 
 	@NestedConfigurationProperty
 	private SecurityConfig securityConfig = new SecurityConfig();
@@ -103,6 +109,9 @@ public class MagicAPIProperties {
 	@NestedConfigurationProperty
 	private SwaggerConfig swaggerConfig = new SwaggerConfig();
 
+	@NestedConfigurationProperty
+	private ResourceConfig resource = new ResourceConfig();
+
 	public String getEditorConfig() {
 		return editorConfig;
 	}
@@ -265,4 +274,21 @@ public class MagicAPIProperties {
 	public String getVersion() {
 		return version;
 	}
+
+
+	public ResourceConfig getResource() {
+		return resource;
+	}
+
+	public void setResource(ResourceConfig resource) {
+		this.resource = resource;
+	}
+
+	public boolean isSupportCrossDomain() {
+		return supportCrossDomain;
+	}
+
+	public void setSupportCrossDomain(boolean supportCrossDomain) {
+		this.supportCrossDomain = supportCrossDomain;
+	}
 }

+ 22 - 0
src/main/java/org/ssssssss/magicapi/spring/boot/starter/MagicRedisAutoConfiguration.java

@@ -1,9 +1,14 @@
 package org.ssssssss.magicapi.spring.boot.starter;
 
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.ssssssss.magicapi.adapter.Resource;
+import org.ssssssss.magicapi.adapter.resource.RedisResource;
 import org.ssssssss.magicapi.modules.RedisModule;
 
 /**
@@ -13,6 +18,12 @@ import org.ssssssss.magicapi.modules.RedisModule;
 @Configuration
 public class MagicRedisAutoConfiguration {
 
+	private MagicAPIProperties properties;
+
+	public MagicRedisAutoConfiguration(MagicAPIProperties properties) {
+		this.properties = properties;
+	}
+
 	/**
 	 * 注入redis模块
 	 */
@@ -20,4 +31,15 @@ public class MagicRedisAutoConfiguration {
 	public RedisModule redisFunctions(RedisConnectionFactory connectionFactory) {
 		return new RedisModule(connectionFactory);
 	}
+
+	/**
+	 * 使用Redis存储
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(prefix = "magic-api", name = "resource.type", havingValue = "redis")
+	public Resource magicRedisResource(RedisConnectionFactory connectionFactory) {
+		ResourceConfig resource = properties.getResource();
+		return new RedisResource(new StringRedisTemplate(connectionFactory), resource.getPrefix(), resource.getSeparator(), resource.isReadonly());
+	}
 }

+ 98 - 0
src/main/java/org/ssssssss/magicapi/spring/boot/starter/ResourceConfig.java

@@ -0,0 +1,98 @@
+package org.ssssssss.magicapi.spring.boot.starter;
+
+/**
+ * 接口存储配置
+ */
+public class ResourceConfig {
+
+	/**
+	 * 存储类型,默认是文件
+	 */
+	private String type = "file";
+
+	/**
+	 * 文件存储位置
+	 */
+	private String location = "/data/magic-api/";
+
+	/**
+	 * 是否是只读模式
+	 */
+	private boolean readonly = false;
+
+	/**
+	 * 使用Redis时使用的前缀
+	 */
+	private String prefix = "magic-api";
+
+	/**
+	 * 分隔符
+	 */
+	private String separator = ":";
+
+	/**
+	 * 使用数据库存储时的表名
+	 */
+	private String tableName = "magic_api_file";
+
+	/**
+	 * 使用数据库存储时使用的数据源
+	 */
+	private String datasource;
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getLocation() {
+		return location;
+	}
+
+	public void setLocation(String location) {
+		this.location = location;
+	}
+
+	public boolean isReadonly() {
+		return readonly;
+	}
+
+	public void setReadonly(boolean readonly) {
+		this.readonly = readonly;
+	}
+
+	public String getPrefix() {
+		return prefix;
+	}
+
+	public void setPrefix(String prefix) {
+		this.prefix = prefix;
+	}
+
+	public String getSeparator() {
+		return separator;
+	}
+
+	public void setSeparator(String separator) {
+		this.separator = separator;
+	}
+
+	public String getTableName() {
+		return tableName;
+	}
+
+	public void setTableName(String tableName) {
+		this.tableName = tableName;
+	}
+
+	public String getDatasource() {
+		return datasource;
+	}
+
+	public void setDatasource(String datasource) {
+		this.datasource = datasource;
+	}
+}

+ 1 - 5
src/main/resources/META-INF/spring.factories

@@ -1,5 +1 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-org.ssssssss.magicapi.spring.boot.starter.MagicAPIAutoConfiguration,\
-org.ssssssss.magicapi.spring.boot.starter.MagicRedisAutoConfiguration,\
-org.ssssssss.magicapi.spring.boot.starter.MagicMongoAutoConfiguration,\
-org.ssssssss.magicapi.spring.boot.starter.MagicSwaggerConfiguration
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.spring.boot.starter.MagicAPIAutoConfiguration