未验证 提交 4d459c22 编写于 作者: H Haoran Meng 提交者: GitHub

Move metadata to config center (#7385)

上级 3ef12e96
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.event.metadata;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.governance.core.event.GovernanceEvent;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import java.util.Collection;
/**
* Meta data changed event.
*/
@RequiredArgsConstructor
@Getter
public final class MetaDataChangedEvent implements GovernanceEvent {
// TODO Confirm whether it must be a collection
private final Collection<String> schemaNames;
private final RuleSchemaMetaData ruleSchemaMetaData;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.config.metadata;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.infra.yaml.config.YamlConfiguration;
/**
* Column meta data configuration for YAML.
*/
@Getter
@Setter
public final class YamlColumnMetaData implements YamlConfiguration {
private String name;
private int dataType;
private String dataTypeName;
private boolean primaryKey;
private boolean generated;
private boolean caseSensitive;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.config.metadata;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.infra.yaml.config.YamlConfiguration;
/**
* Index meta data configuration for YAML.
*/
@Getter
@Setter
public final class YamlIndexMetaData implements YamlConfiguration {
private String name;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.config.metadata;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.infra.yaml.config.YamlConfiguration;
import java.util.Map;
/**
* Rule schema meta data configuration for YAML.
*/
@Getter
@Setter
public final class YamlRuleSchemaMetaData implements YamlConfiguration {
private YamlSchemaMetaData configuredSchemaMetaData;
private Map<String, YamlSchemaMetaData> unconfiguredSchemaMetaDataMap;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.config.metadata;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.infra.yaml.config.YamlConfiguration;
import java.util.Map;
/**
* Schema meta data configuration for YAML.
*/
@Getter
@Setter
public final class YamlSchemaMetaData implements YamlConfiguration {
private Map<String, YamlTableMetaData> tables;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.config.metadata;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.infra.yaml.config.YamlConfiguration;
import java.util.Map;
/**
* Table meta data configuration for YAML.
*/
@Getter
@Setter
public final class YamlTableMetaData implements YamlConfiguration {
private Map<String, YamlColumnMetaData> columns;
private Map<String, YamlIndexMetaData> indexes;
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.swapper;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlColumnMetaData;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlIndexMetaData;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlRuleSchemaMetaData;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlSchemaMetaData;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlTableMetaData;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.yaml.swapper.YamlSwapper;
import org.apache.shardingsphere.sql.parser.binder.metadata.column.ColumnMetaData;
import org.apache.shardingsphere.sql.parser.binder.metadata.index.IndexMetaData;
import org.apache.shardingsphere.sql.parser.binder.metadata.schema.SchemaMetaData;
import org.apache.shardingsphere.sql.parser.binder.metadata.table.TableMetaData;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* Rule schema meta data configuration YAML swapper.
*/
public final class RuleSchemaMetaDataYamlSwapper implements YamlSwapper<YamlRuleSchemaMetaData, RuleSchemaMetaData> {
@Override
public YamlRuleSchemaMetaData swapToYamlConfiguration(final RuleSchemaMetaData metaData) {
YamlRuleSchemaMetaData result = new YamlRuleSchemaMetaData();
result.setConfiguredSchemaMetaData(convertYamlSchema(metaData.getConfiguredSchemaMetaData()));
Map<String, YamlSchemaMetaData> unconfigured = metaData.getUnconfiguredSchemaMetaDataMap().entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> convertYamlSchema(entry.getValue())));
result.setUnconfiguredSchemaMetaDataMap(unconfigured);
return result;
}
@Override
public RuleSchemaMetaData swapToObject(final YamlRuleSchemaMetaData yamlConfig) {
SchemaMetaData configured = Optional.ofNullable(yamlConfig.getConfiguredSchemaMetaData()).map(this::convertSchema).orElse(new SchemaMetaData());
Map<String, SchemaMetaData> unconfigured = Optional.ofNullable(yamlConfig.getUnconfiguredSchemaMetaDataMap()).map(e -> e.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, entry -> convertSchema(entry.getValue())))).orElse(new HashMap<>());
return new RuleSchemaMetaData(configured, unconfigured);
}
private SchemaMetaData convertSchema(final YamlSchemaMetaData schema) {
return new SchemaMetaData(schema.getTables().entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> convertTable(entry.getValue()))));
}
private TableMetaData convertTable(final YamlTableMetaData table) {
return new TableMetaData(convertColumns(table.getColumns()), convertIndexes(table.getIndexes()));
}
private Collection<IndexMetaData> convertIndexes(final Map<String, YamlIndexMetaData> indexes) {
return null == indexes ? Collections.emptyList() : indexes.values().stream().map(this::convertIndex).collect(Collectors.toList());
}
private IndexMetaData convertIndex(final YamlIndexMetaData index) {
return new IndexMetaData(index.getName());
}
private Collection<ColumnMetaData> convertColumns(final Map<String, YamlColumnMetaData> indexes) {
return indexes.values().stream().map(this::convertColumn).collect(Collectors.toList());
}
private ColumnMetaData convertColumn(final YamlColumnMetaData column) {
return new ColumnMetaData(column.getName(), column.getDataType(), column.getDataTypeName(), column.isPrimaryKey(), column.isGenerated(), column.isCaseSensitive());
}
private YamlSchemaMetaData convertYamlSchema(final SchemaMetaData schema) {
Map<String, YamlTableMetaData> tables = schema.getAllTableNames().stream().collect(Collectors.toMap(each -> each, each -> convertYamlTable(schema.get(each))));
YamlSchemaMetaData result = new YamlSchemaMetaData();
result.setTables(tables);
return result;
}
private YamlTableMetaData convertYamlTable(final TableMetaData table) {
YamlTableMetaData result = new YamlTableMetaData();
result.setColumns(convertYamlColumns(table.getColumns()));
result.setIndexes(convertYamlIndexes(table.getIndexes()));
return result;
}
private Map<String, YamlIndexMetaData> convertYamlIndexes(final Map<String, IndexMetaData> indexes) {
return indexes.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> convertYamlIndex(entry.getValue())));
}
private YamlIndexMetaData convertYamlIndex(final IndexMetaData index) {
YamlIndexMetaData result = new YamlIndexMetaData();
result.setName(index.getName());
return result;
}
private Map<String, YamlColumnMetaData> convertYamlColumns(final Map<String, ColumnMetaData> columns) {
return columns.entrySet().stream().collect(Collectors.toMap(Entry::getKey, entry -> convertYamlColumn(entry.getValue())));
}
private YamlColumnMetaData convertYamlColumn(final ColumnMetaData column) {
YamlColumnMetaData result = new YamlColumnMetaData();
result.setName(column.getName());
result.setCaseSensitive(column.isCaseSensitive());
result.setGenerated(column.isGenerated());
result.setPrimaryKey(column.isPrimaryKey());
result.setDataType(result.getDataType());
result.setDataTypeName(result.getDataTypeName());
return result;
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.shardingsphere.governance.core.yaml.swapper;
import lombok.SneakyThrows;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlRuleSchemaMetaData;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.yaml.engine.YamlEngine;
import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
public final class RuleSchemaMetaDataYamlSwapperTest {
private static final String META_DATA_YAM = "yaml/metadata.yaml";
@Test
public void assertSwapToYamlRuleSchemaMetaData() {
RuleSchemaMetaData ruleSchemaMetaData = new RuleSchemaMetaDataYamlSwapper().swapToObject(YamlEngine.unmarshal(readYAML(META_DATA_YAM), YamlRuleSchemaMetaData.class));
YamlRuleSchemaMetaData yamlRuleSchemaMetaData = new RuleSchemaMetaDataYamlSwapper().swapToYamlConfiguration(ruleSchemaMetaData);
assertNotNull(yamlRuleSchemaMetaData);
assertNotNull(yamlRuleSchemaMetaData.getConfiguredSchemaMetaData());
assertNotNull(yamlRuleSchemaMetaData.getUnconfiguredSchemaMetaDataMap());
assertThat(yamlRuleSchemaMetaData.getConfiguredSchemaMetaData().getTables().keySet(), is(Collections.singleton("t_order")));
assertThat(yamlRuleSchemaMetaData.getConfiguredSchemaMetaData().getTables().get("t_order").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(yamlRuleSchemaMetaData.getConfiguredSchemaMetaData().getTables().get("t_order").getColumns().keySet(), is(Collections.singleton("id")));
assertThat(yamlRuleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().keySet(), is(Collections.singleton("ds_0")));
assertThat(yamlRuleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").getTables().keySet(), is(Collections.singleton("t_user")));
assertThat(yamlRuleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").getTables().get("t_user").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(yamlRuleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").getTables().get("t_user").getColumns().keySet(), is(Collections.singleton("id")));
}
@Test
public void assertSwapToRuleSchemaMetaData() {
YamlRuleSchemaMetaData yamlRuleSchemaMetaData = YamlEngine.unmarshal(readYAML(META_DATA_YAM), YamlRuleSchemaMetaData.class);
RuleSchemaMetaData ruleSchemaMetaData = new RuleSchemaMetaDataYamlSwapper().swapToObject(yamlRuleSchemaMetaData);
assertNotNull(ruleSchemaMetaData);
assertNotNull(ruleSchemaMetaData.getConfiguredSchemaMetaData());
assertNotNull(ruleSchemaMetaData.getConfiguredSchemaMetaData());
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().getAllTableNames(), is(Collections.singleton("t_order")));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().get("t_order").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().getAllColumnNames("t_order").size(), is(1));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().get("t_order").getColumns().keySet(), is(Collections.singleton("id")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().keySet(), is(Collections.singleton("ds_0")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").getAllTableNames(), is(Collections.singleton("t_user")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").get("t_user").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").get("t_user").getColumns().keySet(), is(Collections.singleton("id")));
}
@SneakyThrows
private String readYAML(final String yamlFile) {
return Files.readAllLines(Paths.get(ClassLoader.getSystemResource(yamlFile).toURI()))
.stream().filter(each -> !each.startsWith("#")).map(each -> each + System.lineSeparator()).collect(Collectors.joining());
}
}
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
configuredSchemaMetaData:
tables:
t_order:
columns:
id:
caseSensitive: false
dataType: 0
generated: false
name: id
primaryKey: true
indexes:
primary:
name: PRIMARY
unconfiguredSchemaMetaDataMap:
ds_0:
tables:
t_user:
columns:
id:
caseSensitive: false
dataType: 0
generated: false
name: id
primaryKey: true
indexes:
primary:
name: PRIMARY
......@@ -24,19 +24,23 @@ import com.google.common.base.Strings;
import com.google.common.eventbus.Subscribe;
import org.apache.shardingsphere.encrypt.algorithm.config.AlgorithmProvidedEncryptRuleConfiguration;
import org.apache.shardingsphere.encrypt.api.config.EncryptRuleConfiguration;
import org.apache.shardingsphere.governance.core.event.persist.DataSourcePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.MetaDataPersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.RulePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.SchemaNamePersistEvent;
import org.apache.shardingsphere.governance.core.eventbus.ShardingSphereEventBus;
import org.apache.shardingsphere.governance.core.yaml.config.YamlDataSourceConfiguration;
import org.apache.shardingsphere.governance.core.yaml.config.YamlDataSourceConfigurationWrap;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlRuleSchemaMetaData;
import org.apache.shardingsphere.governance.core.yaml.swapper.DataSourceConfigurationYamlSwapper;
import org.apache.shardingsphere.governance.core.yaml.swapper.RuleSchemaMetaDataYamlSwapper;
import org.apache.shardingsphere.governance.repository.api.ConfigurationRepository;
import org.apache.shardingsphere.infra.auth.Authentication;
import org.apache.shardingsphere.infra.auth.yaml.config.YamlAuthenticationConfiguration;
import org.apache.shardingsphere.infra.auth.yaml.swapper.AuthenticationYamlSwapper;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.datasource.DataSourceConfiguration;
import org.apache.shardingsphere.governance.core.eventbus.ShardingSphereEventBus;
import org.apache.shardingsphere.governance.core.event.persist.DataSourcePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.RulePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.SchemaNamePersistEvent;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.yaml.config.YamlRootRuleConfigurations;
import org.apache.shardingsphere.infra.yaml.engine.YamlEngine;
import org.apache.shardingsphere.infra.yaml.swapper.YamlRuleConfigurationSwapperEngine;
......@@ -54,6 +58,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
......@@ -129,6 +134,16 @@ public final class ConfigCenter {
persistSchema(event.getSchemaName(), event.isDrop());
}
/**
* Persist meta data.
*
* @param event Meta data event.
*/
@Subscribe
public synchronized void renew(final MetaDataPersistEvent event) {
persistMetaData(event.getSchemaName(), event.getMetaData());
}
private void persistDataSourceConfigurations(final String schemaName, final Map<String, DataSourceConfiguration> dataSourceConfigurations, final boolean isOverwrite) {
if (!dataSourceConfigurations.isEmpty() && (isOverwrite || !hasDataSourceConfiguration(schemaName))) {
persistDataSourceConfigurations(schemaName, dataSourceConfigurations);
......@@ -317,6 +332,30 @@ public final class ConfigCenter {
return !Strings.isNullOrEmpty(repository.get(node.getDataSourcePath(schemaName)));
}
/**
* Persist rule schema meta data.
*
* @param schemaName schema name
* @param ruleSchemaMetaData rule schema meta data of the schema
*/
public void persistMetaData(final String schemaName, final RuleSchemaMetaData ruleSchemaMetaData) {
repository.persist(node.getTablePath(schemaName), YamlEngine.marshal(new RuleSchemaMetaDataYamlSwapper().swapToYamlConfiguration(ruleSchemaMetaData)));
}
/**
* Load rule schema meta data.
*
* @param schemaName schema name
* @return rule schema meta data of the schema
*/
public Optional<RuleSchemaMetaData> loadMetaData(final String schemaName) {
String path = repository.get(node.getTablePath(schemaName));
if (Strings.isNullOrEmpty(path)) {
return Optional.empty();
}
return Optional.of(new RuleSchemaMetaDataYamlSwapper().swapToObject(YamlEngine.unmarshal(path, YamlRuleSchemaMetaData.class)));
}
private boolean hasAuthentication() {
return !Strings.isNullOrEmpty(repository.get(node.getAuthenticationPath()));
}
......
......@@ -159,6 +159,7 @@ public final class ConfigCenterNode {
result.add(getSchemaNamePath(schemaName));
result.add(getRulePath(schemaName));
result.add(getDataSourcePath(schemaName));
result.add(getTablePath(schemaName));
}
return result;
}
......
......@@ -20,19 +20,23 @@ package org.apache.shardingsphere.governance.core.config.listener;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.apache.commons.collections4.SetUtils;
import org.apache.shardingsphere.governance.core.config.ConfigCenter;
import org.apache.shardingsphere.governance.core.config.ConfigCenterNode;
import org.apache.shardingsphere.governance.core.event.GovernanceEvent;
import org.apache.shardingsphere.governance.core.event.datasource.DataSourceChangedEvent;
import org.apache.shardingsphere.governance.core.event.metadata.MetaDataChangedEvent;
import org.apache.shardingsphere.governance.core.event.rule.RuleConfigurationsChangedEvent;
import org.apache.shardingsphere.governance.core.event.schema.SchemaAddedEvent;
import org.apache.shardingsphere.governance.core.event.schema.SchemaDeletedEvent;
import org.apache.shardingsphere.governance.core.listener.PostGovernanceRepositoryEventListener;
import org.apache.shardingsphere.governance.core.yaml.config.YamlDataSourceConfigurationWrap;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlRuleSchemaMetaData;
import org.apache.shardingsphere.governance.core.yaml.swapper.DataSourceConfigurationYamlSwapper;
import org.apache.shardingsphere.governance.core.config.ConfigCenter;
import org.apache.shardingsphere.governance.core.config.ConfigCenterNode;
import org.apache.shardingsphere.governance.core.yaml.swapper.RuleSchemaMetaDataYamlSwapper;
import org.apache.shardingsphere.governance.repository.api.ConfigurationRepository;
import org.apache.shardingsphere.governance.repository.api.listener.DataChangedEvent;
import org.apache.shardingsphere.governance.repository.api.listener.DataChangedEvent.ChangedType;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.yaml.config.YamlRootRuleConfigurations;
import org.apache.shardingsphere.infra.yaml.engine.YamlEngine;
import org.apache.shardingsphere.infra.yaml.swapper.YamlRuleConfigurationSwapperEngine;
......@@ -103,7 +107,9 @@ public final class SchemaChangedListener extends PostGovernanceRepositoryEventLi
}
private boolean isValidNodeChangedEvent(final String schemaName, final String nodeFullPath) {
return !existedSchemaNames.contains(schemaName) || configurationNode.getDataSourcePath(schemaName).equals(nodeFullPath) || configurationNode.getRulePath(schemaName).equals(nodeFullPath);
return !existedSchemaNames.contains(schemaName) || configurationNode.getDataSourcePath(schemaName).equals(nodeFullPath)
|| configurationNode.getRulePath(schemaName).equals(nodeFullPath)
|| configurationNode.getTablePath(schemaName).equals(nodeFullPath);
}
private GovernanceEvent createAddedEvent(final String schemaName) {
......@@ -119,7 +125,12 @@ public final class SchemaChangedListener extends PostGovernanceRepositoryEventLi
}
private GovernanceEvent createUpdatedEventForExistedSchema(final String schemaName, final DataChangedEvent event) {
return event.getKey().equals(configurationNode.getDataSourcePath(schemaName)) ? createDataSourceChangedEvent(schemaName, event) : createRuleChangedEvent(schemaName, event);
if (event.getKey().equals(configurationNode.getDataSourcePath(schemaName))) {
return createDataSourceChangedEvent(schemaName, event);
} else if (event.getKey().equals(configurationNode.getRulePath(schemaName))) {
return createRuleChangedEvent(schemaName, event);
}
return createMetaDataChangedEvent(event);
}
private DataSourceChangedEvent createDataSourceChangedEvent(final String schemaName, final DataChangedEvent event) {
......@@ -135,6 +146,11 @@ public final class SchemaChangedListener extends PostGovernanceRepositoryEventLi
return new RuleConfigurationsChangedEvent(schemaName, new YamlRuleConfigurationSwapperEngine().swapToRuleConfigurations(configurations.getRules()));
}
private GovernanceEvent createMetaDataChangedEvent(final DataChangedEvent event) {
RuleSchemaMetaData ruleSchemaMetaData = new RuleSchemaMetaDataYamlSwapper().swapToObject(YamlEngine.unmarshal(event.getValue(), YamlRuleSchemaMetaData.class));
return new MetaDataChangedEvent(existedSchemaNames, ruleSchemaMetaData);
}
private boolean isOwnCompleteConfigurations(final String schemaName) {
return configCenter.hasDataSourceConfiguration(schemaName) && configCenter.hasRuleConfiguration(schemaName);
}
......
......@@ -64,11 +64,12 @@ public final class ConfigCenterNodeTest {
@Test
public void assertGetAllSchemaConfigPaths() {
Collection<String> actual = configurationNode.getAllSchemaConfigPaths(Collections.singletonList(DefaultSchema.LOGIC_NAME));
assertThat(actual.size(), is(4));
assertThat(actual.size(), is(5));
assertThat(actual, hasItems("/schemas"));
assertThat(actual, hasItems("/schemas/logic_db"));
assertThat(actual, hasItems("/schemas/logic_db/rule"));
assertThat(actual, hasItems("/schemas/logic_db/datasource"));
assertThat(actual, hasItems("/schemas/logic_db/table"));
}
@Test
......
......@@ -20,6 +20,12 @@ package org.apache.shardingsphere.governance.core.config;
import lombok.SneakyThrows;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.shardingsphere.encrypt.api.config.EncryptRuleConfiguration;
import org.apache.shardingsphere.governance.core.event.persist.DataSourcePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.MetaDataPersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.RulePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.SchemaNamePersistEvent;
import org.apache.shardingsphere.governance.core.yaml.config.metadata.YamlRuleSchemaMetaData;
import org.apache.shardingsphere.governance.core.yaml.swapper.RuleSchemaMetaDataYamlSwapper;
import org.apache.shardingsphere.governance.repository.api.ConfigurationRepository;
import org.apache.shardingsphere.infra.auth.Authentication;
import org.apache.shardingsphere.infra.auth.yaml.config.YamlAuthenticationConfiguration;
......@@ -28,9 +34,7 @@ import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.infra.config.datasource.DataSourceConfiguration;
import org.apache.shardingsphere.infra.config.properties.ConfigurationPropertyKey;
import org.apache.shardingsphere.governance.core.event.persist.DataSourcePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.RulePersistEvent;
import org.apache.shardingsphere.governance.core.event.persist.SchemaNamePersistEvent;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.yaml.config.YamlRootRuleConfigurations;
import org.apache.shardingsphere.infra.yaml.engine.YamlEngine;
import org.apache.shardingsphere.infra.yaml.swapper.YamlRuleConfigurationSwapperEngine;
......@@ -52,12 +56,15 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
......@@ -85,6 +92,8 @@ public final class ConfigCenterTest {
private static final String DATA_SOURCE_YAML_WITH_CONNECTION_INIT_SQL = "yaml/configCenter/data-source-init-sql.yaml";
private static final String META_DATA_YAML = "yaml/metadata.yaml";
@Mock
private ConfigurationRepository configurationRepository;
......@@ -485,4 +494,44 @@ public final class ConfigCenterTest {
configCenter.renew(event);
verify(configurationRepository).persist(eq("/schemas"), eq("sharding_db,master_slave_db"));
}
@Test
public void assertPersistMetaData() {
RuleSchemaMetaData ruleSchemaMetaData = new RuleSchemaMetaDataYamlSwapper().swapToObject(YamlEngine.unmarshal(readYAML(META_DATA_YAML), YamlRuleSchemaMetaData.class));
ConfigCenter configCenter = new ConfigCenter(configurationRepository);
configCenter.persistMetaData("sharding_db", ruleSchemaMetaData);
verify(configurationRepository).persist(eq("/schemas/sharding_db/table"), anyString());
}
@Test
public void assertLoadMetaData() {
when(configurationRepository.get("/schemas/sharding_db/table")).thenReturn(readYAML(META_DATA_YAML));
ConfigCenter configCenter = new ConfigCenter(configurationRepository);
Optional<RuleSchemaMetaData> optionalRuleSchemaMetaData = configCenter.loadMetaData("sharding_db");
assertTrue(optionalRuleSchemaMetaData.isPresent());
Optional<RuleSchemaMetaData> empty = configCenter.loadMetaData("test");
assertThat(empty, is(Optional.empty()));
RuleSchemaMetaData ruleSchemaMetaData = optionalRuleSchemaMetaData.get();
verify(configurationRepository).get(eq("/schemas/sharding_db/table"));
assertNotNull(ruleSchemaMetaData);
assertNotNull(ruleSchemaMetaData.getConfiguredSchemaMetaData());
assertNotNull(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap());
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().getAllTableNames(), is(Collections.singleton("t_order")));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().get("t_order").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().getAllColumnNames("t_order").size(), is(1));
assertThat(ruleSchemaMetaData.getConfiguredSchemaMetaData().get("t_order").getColumns().keySet(), is(Collections.singleton("id")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().keySet(), is(Collections.singleton("ds_0")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").getAllTableNames(), is(Collections.singleton("t_user")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").get("t_user").getIndexes().keySet(), is(Collections.singleton("primary")));
assertThat(ruleSchemaMetaData.getUnconfiguredSchemaMetaDataMap().get("ds_0").get("t_user").getColumns().keySet(), is(Collections.singleton("id")));
}
@Test
public void assertRenewMetaDataPersistEvent() {
MetaDataPersistEvent event = new MetaDataPersistEvent("sharding_db",
new RuleSchemaMetaDataYamlSwapper().swapToObject(YamlEngine.unmarshal(readYAML(META_DATA_YAML), YamlRuleSchemaMetaData.class)));
ConfigCenter configCenter = new ConfigCenter(configurationRepository);
configCenter.renew(event);
verify(configurationRepository).persist(eq("/schemas/sharding_db/table"), anyString());
}
}
......@@ -19,17 +19,18 @@ package org.apache.shardingsphere.governance.core.config.listener;
import lombok.SneakyThrows;
import org.apache.shardingsphere.encrypt.api.config.EncryptRuleConfiguration;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.masterslave.api.config.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.governance.core.event.datasource.DataSourceChangedEvent;
import org.apache.shardingsphere.governance.core.event.GovernanceEvent;
import org.apache.shardingsphere.governance.core.event.datasource.DataSourceChangedEvent;
import org.apache.shardingsphere.governance.core.event.metadata.MetaDataChangedEvent;
import org.apache.shardingsphere.governance.core.event.rule.RuleConfigurationsChangedEvent;
import org.apache.shardingsphere.governance.core.event.schema.SchemaAddedEvent;
import org.apache.shardingsphere.governance.core.event.schema.SchemaDeletedEvent;
import org.apache.shardingsphere.governance.repository.api.ConfigurationRepository;
import org.apache.shardingsphere.governance.repository.api.listener.DataChangedEvent;
import org.apache.shardingsphere.governance.repository.api.listener.DataChangedEvent.ChangedType;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.algorithm.ShardingSphereAlgorithmConfiguration;
import org.apache.shardingsphere.masterslave.api.config.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import org.junit.Before;
import org.junit.Test;
......@@ -62,6 +63,8 @@ public final class SchemaChangedListenerTest {
private static final String ENCRYPT_RULE_FILE = "yaml/encrypt-rule.yaml";
private static final String META_DATA_FILE = "yaml/metadata.yaml";
private SchemaChangedListener schemaChangedListener;
@Mock
......@@ -273,6 +276,14 @@ public final class SchemaChangedListenerTest {
assertThat(((SchemaAddedEvent) actual.get()).getSchemaName(), is("shadow_db"));
}
@Test
public void assertCreateMetaDataChangedEvent() {
DataChangedEvent dataChangedEvent = new DataChangedEvent("/schemas/sharding_db/table", readYAML(META_DATA_FILE), ChangedType.UPDATED);
Optional<GovernanceEvent> actual = schemaChangedListener.createGovernanceEvent(dataChangedEvent);
assertTrue(actual.isPresent());
assertTrue(((MetaDataChangedEvent) actual.get()).getRuleSchemaMetaData().getConfiguredSchemaMetaData().getAllTableNames().contains("t_order"));
}
@SneakyThrows
private String readYAML(final String yamlFile) {
return Files.readAllLines(Paths.get(ClassLoader.getSystemResource(yamlFile).toURI())).stream().map(each -> each + System.lineSeparator()).collect(Collectors.joining());
......
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
configuredSchemaMetaData:
tables:
t_order:
columns:
id:
caseSensitive: false
dataType: 0
generated: false
name: id
primaryKey: true
indexes:
primary:
name: PRIMARY
unconfiguredSchemaMetaDataMap:
ds_0:
tables:
t_user:
columns:
id:
caseSensitive: false
dataType: 0
generated: false
name: id
primaryKey: true
indexes:
primary:
name: PRIMARY
......@@ -97,8 +97,8 @@ public final class GovernanceSchemaContexts implements SchemaContexts {
}
private void persistMetaData() {
schemaContexts.getSchemaContexts().forEach((key, value) -> governanceFacade.getMetaDataCenter()
.persistMetaDataCenterNode(key, value.getSchema().getMetaData().getRuleSchemaMetaData()));
schemaContexts.getSchemaContexts().forEach((key, value) -> governanceFacade.getConfigCenter()
.persistMetaData(key, value.getSchema().getMetaData().getRuleSchemaMetaData()));
}
@Override
......@@ -158,7 +158,7 @@ public final class GovernanceSchemaContexts implements SchemaContexts {
Map<String, SchemaContext> schemas = new HashMap<>(schemaContexts.getSchemaContexts());
schemas.put(event.getSchemaName(), createAddedSchemaContext(event));
schemaContexts = new StandardSchemaContexts(schemas, schemaContexts.getAuthentication(), schemaContexts.getProps(), schemaContexts.getDatabaseType());
governanceFacade.getMetaDataCenter().persistMetaDataCenterNode(event.getSchemaName(),
governanceFacade.getConfigCenter().persistMetaData(event.getSchemaName(),
schemaContexts.getSchemaContexts().get(event.getSchemaName()).getSchema().getMetaData().getRuleSchemaMetaData());
ShardingSphereEventBus.getInstance().post(
new DataSourceChangeCompletedEvent(event.getSchemaName(), schemaContexts.getDatabaseType(), schemas.get(event.getSchemaName()).getSchema().getDataSources()));
......@@ -229,7 +229,7 @@ public final class GovernanceSchemaContexts implements SchemaContexts {
newSchemaContexts.remove(schemaName);
newSchemaContexts.put(schemaName, getChangedSchemaContext(schemaContexts.getSchemaContexts().get(schemaName), event.getRuleConfigurations()));
schemaContexts = new StandardSchemaContexts(newSchemaContexts, schemaContexts.getAuthentication(), schemaContexts.getProps(), schemaContexts.getDatabaseType());
governanceFacade.getMetaDataCenter().persistMetaDataCenterNode(schemaName, newSchemaContexts.get(schemaName).getSchema().getMetaData().getRuleSchemaMetaData());
governanceFacade.getConfigCenter().persistMetaData(schemaName, newSchemaContexts.get(schemaName).getSchema().getMetaData().getRuleSchemaMetaData());
}
/**
......
......@@ -18,6 +18,7 @@
package org.apache.shardingsphere.governance.context.schema;
import lombok.SneakyThrows;
import org.apache.shardingsphere.governance.core.config.ConfigCenter;
import org.apache.shardingsphere.governance.core.event.auth.AuthenticationChangedEvent;
import org.apache.shardingsphere.governance.core.event.datasource.DataSourceChangedEvent;
import org.apache.shardingsphere.governance.core.event.props.PropertiesChangedEvent;
......@@ -25,7 +26,6 @@ import org.apache.shardingsphere.governance.core.event.rule.RuleConfigurationsCh
import org.apache.shardingsphere.governance.core.event.schema.SchemaAddedEvent;
import org.apache.shardingsphere.governance.core.event.schema.SchemaDeletedEvent;
import org.apache.shardingsphere.governance.core.facade.GovernanceFacade;
import org.apache.shardingsphere.governance.core.metadata.MetaDataCenter;
import org.apache.shardingsphere.governance.core.metadata.event.MetaDataChangedEvent;
import org.apache.shardingsphere.governance.core.registry.RegistryCenter;
import org.apache.shardingsphere.governance.core.registry.event.CircuitStateChangedEvent;
......@@ -43,8 +43,8 @@ import org.apache.shardingsphere.infra.database.type.DatabaseType;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import org.apache.shardingsphere.infra.metadata.schema.RuleSchemaMetaData;
import org.apache.shardingsphere.infra.rule.event.RuleChangedEvent;
import org.apache.shardingsphere.masterslave.rule.MasterSlaveRule;
import org.apache.shardingsphere.jdbc.test.MockedDataSource;
import org.apache.shardingsphere.masterslave.rule.MasterSlaveRule;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -83,7 +83,7 @@ public final class GovernanceSchemaContextsTest {
private RegistryCenter registryCenter;
@Mock
private MetaDataCenter metaDataCenter;
private ConfigCenter configCenter;
@Mock
private SchemaContext schemaContext;
......@@ -102,8 +102,8 @@ public final class GovernanceSchemaContextsTest {
when(databaseType.getName()).thenReturn("H2");
when(databaseType.getDataSourceMetaData(any(), any())).thenReturn(mock(DataSourceMetaData.class));
when(governanceFacade.getRegistryCenter()).thenReturn(registryCenter);
when(governanceFacade.getConfigCenter()).thenReturn(configCenter);
when(registryCenter.loadDisabledDataSources("schema")).thenReturn(Collections.singletonList("schema.ds_1"));
when(governanceFacade.getMetaDataCenter()).thenReturn(metaDataCenter);
governanceSchemaContexts = new GovernanceSchemaContexts(new StandardSchemaContexts(getSchemaContextMap(), authentication, configurationProperties, databaseType), governanceFacade);
}
......
......@@ -21,7 +21,6 @@ import lombok.Getter;
import org.apache.shardingsphere.governance.core.config.ConfigCenter;
import org.apache.shardingsphere.governance.core.facade.listener.GovernanceListenerManager;
import org.apache.shardingsphere.governance.core.facade.repository.GovernanceRepositoryFacade;
import org.apache.shardingsphere.governance.core.metadata.MetaDataCenter;
import org.apache.shardingsphere.governance.core.registry.RegistryCenter;
import org.apache.shardingsphere.governance.repository.api.config.GovernanceConfiguration;
import org.apache.shardingsphere.infra.auth.Authentication;
......@@ -48,9 +47,6 @@ public final class GovernanceFacade implements AutoCloseable {
@Getter
private RegistryCenter registryCenter;
@Getter
private MetaDataCenter metaDataCenter;
private GovernanceListenerManager listenerManager;
/**
......@@ -64,7 +60,6 @@ public final class GovernanceFacade implements AutoCloseable {
repositoryFacade = new GovernanceRepositoryFacade(config);
registryCenter = new RegistryCenter(repositoryFacade.getRegistryRepository());
configCenter = new ConfigCenter(repositoryFacade.getConfigurationRepository());
metaDataCenter = new MetaDataCenter(repositoryFacade.getConfigurationRepository());
listenerManager = new GovernanceListenerManager(repositoryFacade.getRegistryRepository(),
repositoryFacade.getConfigurationRepository(), schemaNames.isEmpty() ? configCenter.getAllSchemaNames() : schemaNames);
}
......
......@@ -17,18 +17,17 @@
package org.apache.shardingsphere.governance.core.facade;
import org.apache.shardingsphere.infra.auth.Authentication;
import org.apache.shardingsphere.infra.auth.ProxyUser;
import org.apache.shardingsphere.infra.config.datasource.DataSourceConfiguration;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.governance.core.config.ConfigCenter;
import org.apache.shardingsphere.governance.core.facade.listener.GovernanceListenerManager;
import org.apache.shardingsphere.governance.core.facade.repository.GovernanceRepositoryFacade;
import org.apache.shardingsphere.governance.core.facade.util.FieldUtil;
import org.apache.shardingsphere.governance.core.metadata.MetaDataCenter;
import org.apache.shardingsphere.governance.core.registry.RegistryCenter;
import org.apache.shardingsphere.governance.repository.api.config.GovernanceCenterConfiguration;
import org.apache.shardingsphere.governance.repository.api.config.GovernanceConfiguration;
import org.apache.shardingsphere.infra.auth.Authentication;
import org.apache.shardingsphere.infra.auth.ProxyUser;
import org.apache.shardingsphere.infra.config.RuleConfiguration;
import org.apache.shardingsphere.infra.config.datasource.DataSourceConfiguration;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -58,9 +57,6 @@ public final class GovernanceFacadeTest {
@Mock
private RegistryCenter registryCenter;
@Mock
private MetaDataCenter metaDataCenter;
@Mock
private GovernanceListenerManager listenerManager;
......@@ -71,7 +67,6 @@ public final class GovernanceFacadeTest {
FieldUtil.setField(governanceFacade, "repositoryFacade", repositoryFacade);
FieldUtil.setField(governanceFacade, "configCenter", configCenter);
FieldUtil.setField(governanceFacade, "registryCenter", registryCenter);
FieldUtil.setField(governanceFacade, "metaDataCenter", metaDataCenter);
FieldUtil.setField(governanceFacade, "listenerManager", listenerManager);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册