未验证 提交 e09c2435 编写于 作者: wu-sheng's avatar wu-sheng 提交者: GitHub

Merge pull request #803 from peng-yongsheng/feature/getTopNApplicationThroughput

Provide the getTopNApplicationThroughput query.
......@@ -104,6 +104,7 @@ import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMin
import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMonthMetricPersistenceDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationComponentUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMappingUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationReferenceMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.ICpuMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IGCMetricUIDAO;
......@@ -248,6 +249,7 @@ public class StorageModule extends Module {
classes.add(IInstanceMetricUIDAO.class);
classes.add(IApplicationComponentUIDAO.class);
classes.add(IApplicationMappingUIDAO.class);
classes.add(IApplicationMetricUIDAO.class);
classes.add(IApplicationReferenceMetricUIDAO.class);
classes.add(ISegmentDurationUIDAO.class);
classes.add(ISegmentUIDAO.class);
......
/*
* 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.skywalking.apm.collector.storage.dao.ui;
import java.util.List;
import org.apache.skywalking.apm.collector.storage.base.dao.DAO;
import org.apache.skywalking.apm.collector.storage.table.MetricSource;
import org.apache.skywalking.apm.collector.storage.ui.common.Step;
import org.apache.skywalking.apm.collector.storage.ui.overview.ApplicationTPS;
/**
* @author peng-yongsheng
*/
public interface IApplicationMetricUIDAO extends DAO {
List<ApplicationTPS> getTopNApplicationThroughput(Step step, long start, long end, long betweenSecond, int topN,
MetricSource metricSource);
}
/*
* 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.skywalking.apm.collector.storage.ui.overview;
/**
* @author peng-yongsheng
*/
public class ApplicationTPS {
private int applicationId;
private String applicationCode;
private int tps;
public int getApplicationId() {
return applicationId;
}
public void setApplicationId(int applicationId) {
this.applicationId = applicationId;
}
public String getApplicationCode() {
return applicationCode;
}
public void setApplicationCode(String applicationCode) {
this.applicationCode = applicationCode;
}
public int getTps() {
return tps;
}
public void setTps(int tps) {
this.tps = tps;
}
}
......@@ -113,6 +113,7 @@ import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMin
import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMonthMetricPersistenceDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationComponentUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMappingUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationReferenceMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.ICpuMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IGCMetricUIDAO;
......@@ -211,6 +212,7 @@ import org.apache.skywalking.apm.collector.storage.es.dao.srmp.ServiceReferenceM
import org.apache.skywalking.apm.collector.storage.es.dao.srmp.ServiceReferenceMonthMetricEsPersistenceDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.ApplicationComponentEsUIDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.ApplicationMappingEsUIDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.ApplicationMetricEsUIDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.ApplicationReferenceMetricEsUIDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.CpuMetricEsUIDAO;
import org.apache.skywalking.apm.collector.storage.es.dao.ui.GCMetricEsUIDAO;
......@@ -407,6 +409,7 @@ public class StorageModuleEsProvider extends ModuleProvider {
this.registerServiceImplementation(IInstanceMetricUIDAO.class, new InstanceMetricEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(IApplicationComponentUIDAO.class, new ApplicationComponentEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(IApplicationMappingUIDAO.class, new ApplicationMappingEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(IApplicationMetricUIDAO.class, new ApplicationMetricEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(IApplicationReferenceMetricUIDAO.class, new ApplicationReferenceMetricEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(ISegmentDurationUIDAO.class, new SegmentDurationEsUIDAO(elasticSearchClient));
this.registerServiceImplementation(ISegmentUIDAO.class, new SegmentEsUIDAO(elasticSearchClient));
......
/*
* 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.skywalking.apm.collector.storage.es.dao.ui;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.skywalking.apm.collector.client.elasticsearch.ElasticSearchClient;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.es.base.dao.EsDAO;
import org.apache.skywalking.apm.collector.storage.table.MetricSource;
import org.apache.skywalking.apm.collector.storage.table.application.ApplicationMetricTable;
import org.apache.skywalking.apm.collector.storage.ui.common.Step;
import org.apache.skywalking.apm.collector.storage.ui.overview.ApplicationTPS;
import org.apache.skywalking.apm.collector.storage.utils.TimePyramidTableNameBuilder;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.pipeline.InternalSimpleValue;
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregatorBuilders;
/**
* @author peng-yongsheng
*/
public class ApplicationMetricEsUIDAO extends EsDAO implements IApplicationMetricUIDAO {
public ApplicationMetricEsUIDAO(ElasticSearchClient client) {
super(client);
}
private static final String AVG_TPS = "avg_tps";
@Override
public List<ApplicationTPS> getTopNApplicationThroughput(Step step, long start, long end, long betweenSecond,
int topN,
MetricSource metricSource) {
String tableName = TimePyramidTableNameBuilder.build(step, ApplicationMetricTable.TABLE);
SearchRequestBuilder searchRequestBuilder = getClient().prepareSearch(tableName);
searchRequestBuilder.setTypes(ApplicationMetricTable.TABLE_TYPE);
searchRequestBuilder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must().add(QueryBuilders.rangeQuery(ApplicationMetricTable.COLUMN_TIME_BUCKET).gte(start).lte(end));
boolQuery.must().add(QueryBuilders.termQuery(ApplicationMetricTable.COLUMN_SOURCE_VALUE, metricSource.getValue()));
searchRequestBuilder.setQuery(boolQuery);
searchRequestBuilder.setSize(0);
TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms(ApplicationMetricTable.COLUMN_APPLICATION_ID).field(ApplicationMetricTable.COLUMN_APPLICATION_ID).size(topN);
aggregationBuilder.subAggregation(AggregationBuilders.sum(ApplicationMetricTable.COLUMN_TRANSACTION_CALLS).field(ApplicationMetricTable.COLUMN_TRANSACTION_CALLS));
aggregationBuilder.subAggregation(AggregationBuilders.sum(ApplicationMetricTable.COLUMN_TRANSACTION_ERROR_CALLS).field(ApplicationMetricTable.COLUMN_TRANSACTION_ERROR_CALLS));
Map<String, String> bucketsPathsMap = new HashMap<>();
bucketsPathsMap.put(ApplicationMetricTable.COLUMN_TRANSACTION_CALLS, ApplicationMetricTable.COLUMN_TRANSACTION_CALLS);
bucketsPathsMap.put(ApplicationMetricTable.COLUMN_TRANSACTION_ERROR_CALLS, ApplicationMetricTable.COLUMN_TRANSACTION_ERROR_CALLS);
String idOrCode = "(params." + ApplicationMetricTable.COLUMN_TRANSACTION_CALLS + " - params." + ApplicationMetricTable.COLUMN_TRANSACTION_ERROR_CALLS + ")"
+ " / "
+ "(" + betweenSecond + ")";
Script script = new Script(idOrCode);
aggregationBuilder.subAggregation(PipelineAggregatorBuilders.bucketScript(AVG_TPS, bucketsPathsMap, script));
searchRequestBuilder.addAggregation(aggregationBuilder);
SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
List<ApplicationTPS> applicationTPSs = new LinkedList<>();
Terms serviceIdTerms = searchResponse.getAggregations().get(ApplicationMetricTable.COLUMN_APPLICATION_ID);
serviceIdTerms.getBuckets().forEach(serviceIdTerm -> {
int applicationId = serviceIdTerm.getKeyAsNumber().intValue();
ApplicationTPS serviceMetric = new ApplicationTPS();
InternalSimpleValue simpleValue = serviceIdTerm.getAggregations().get(AVG_TPS);
serviceMetric.setApplicationId(applicationId);
serviceMetric.setTps((int)simpleValue.getValue());
applicationTPSs.add(serviceMetric);
});
return applicationTPSs;
}
}
......@@ -109,6 +109,7 @@ import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMin
import org.apache.skywalking.apm.collector.storage.dao.srmp.IServiceReferenceMonthMetricPersistenceDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationComponentUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMappingUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationReferenceMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.ICpuMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IGCMetricUIDAO;
......@@ -207,6 +208,7 @@ import org.apache.skywalking.apm.collector.storage.h2.dao.srmp.ServiceReferenceM
import org.apache.skywalking.apm.collector.storage.h2.dao.srmp.ServiceReferenceMonthMetricH2PersistenceDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.ApplicationComponentH2UIDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.ApplicationMappingH2UIDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.ApplicationMetricH2UIDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.ApplicationReferenceMetricH2UIDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.CpuMetricH2UIDAO;
import org.apache.skywalking.apm.collector.storage.h2.dao.ui.GCMetricH2UIDAO;
......@@ -383,6 +385,7 @@ public class StorageModuleH2Provider extends ModuleProvider {
this.registerServiceImplementation(IInstanceMetricUIDAO.class, new InstanceMetricH2UIDAO(h2Client));
this.registerServiceImplementation(IApplicationComponentUIDAO.class, new ApplicationComponentH2UIDAO(h2Client));
this.registerServiceImplementation(IApplicationMappingUIDAO.class, new ApplicationMappingH2UIDAO(h2Client));
this.registerServiceImplementation(IApplicationMetricUIDAO.class, new ApplicationMetricH2UIDAO(h2Client));
this.registerServiceImplementation(IApplicationReferenceMetricUIDAO.class, new ApplicationReferenceMetricH2UIDAO(h2Client));
this.registerServiceImplementation(ISegmentDurationUIDAO.class, new SegmentDurationH2UIDAO(h2Client));
this.registerServiceImplementation(ISegmentUIDAO.class, new SegmentH2UIDAO(h2Client));
......
/*
* 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.skywalking.apm.collector.storage.h2.dao.ui;
import java.util.List;
import org.apache.skywalking.apm.collector.client.h2.H2Client;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.h2.base.dao.H2DAO;
import org.apache.skywalking.apm.collector.storage.table.MetricSource;
import org.apache.skywalking.apm.collector.storage.ui.common.Step;
import org.apache.skywalking.apm.collector.storage.ui.overview.ApplicationTPS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author peng-yongsheng
*/
public class ApplicationMetricH2UIDAO extends H2DAO implements IApplicationMetricUIDAO {
private final Logger logger = LoggerFactory.getLogger(ApplicationMetricH2UIDAO.class);
public ApplicationMetricH2UIDAO(H2Client client) {
super(client);
}
@Override
public List<ApplicationTPS> getTopNApplicationThroughput(Step step, long start, long end, long betweenSecond,
int topN, MetricSource metricSource) {
return null;
}
}
......@@ -25,16 +25,15 @@ import org.apache.skywalking.apm.collector.core.util.ObjectUtils;
import org.apache.skywalking.apm.collector.storage.ui.common.Duration;
import org.apache.skywalking.apm.collector.storage.ui.common.Topology;
import org.apache.skywalking.apm.collector.storage.ui.overview.AlarmTrend;
import org.apache.skywalking.apm.collector.storage.ui.overview.ApplicationTPS;
import org.apache.skywalking.apm.collector.storage.ui.overview.ClusterBrief;
import org.apache.skywalking.apm.collector.storage.ui.overview.ConjecturalAppBrief;
import org.apache.skywalking.apm.collector.storage.ui.server.AppServerInfo;
import org.apache.skywalking.apm.collector.storage.ui.service.ServiceMetric;
import org.apache.skywalking.apm.collector.ui.graphql.Query;
import org.apache.skywalking.apm.collector.ui.service.AlarmService;
import org.apache.skywalking.apm.collector.ui.service.ApplicationService;
import org.apache.skywalking.apm.collector.ui.service.ClusterTopologyService;
import org.apache.skywalking.apm.collector.ui.service.NetworkAddressService;
import org.apache.skywalking.apm.collector.ui.service.ServerService;
import org.apache.skywalking.apm.collector.ui.service.ServiceNameService;
import org.apache.skywalking.apm.collector.ui.utils.DurationUtils;
......@@ -48,7 +47,6 @@ public class OverViewLayerQuery implements Query {
private ApplicationService applicationService;
private NetworkAddressService networkAddressService;
private ServiceNameService serviceNameService;
private ServerService serverService;
private AlarmService alarmService;
public OverViewLayerQuery(ModuleManager moduleManager) {
......@@ -90,13 +88,6 @@ public class OverViewLayerQuery implements Query {
return alarmService;
}
private ServerService getServerService() {
if (ObjectUtils.isEmpty(serverService)) {
this.serverService = new ServerService(moduleManager);
}
return serverService;
}
public Topology getClusterTopology(Duration duration) throws ParseException {
long start = DurationUtils.INSTANCE.durationToSecondTimeBucket(duration.getStep(), duration.getStart());
long end = DurationUtils.INSTANCE.durationToSecondTimeBucket(duration.getStep(), duration.getEnd());
......@@ -135,11 +126,11 @@ public class OverViewLayerQuery implements Query {
return getServiceNameService().getSlowService(duration.getStep(), start, end, topN);
}
public List<AppServerInfo> getTopNServerThroughput(int applicationId, Duration duration,
public List<ApplicationTPS> getTopNApplicationThroughput(Duration duration,
int topN) throws ParseException {
long start = DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getStart());
long end = DurationUtils.INSTANCE.exchangeToTimeBucket(duration.getEnd());
return getServerService().getTopNServerThroughput(applicationId, duration.getStep(), start, end, topN);
return getApplicationService().getTopNApplicationThroughput(duration.getStep(), start, end, topN);
}
}
......@@ -25,11 +25,13 @@ import org.apache.skywalking.apm.collector.cache.service.ApplicationCacheService
import org.apache.skywalking.apm.collector.cache.service.ServiceNameCacheService;
import org.apache.skywalking.apm.collector.core.module.ModuleManager;
import org.apache.skywalking.apm.collector.storage.StorageModule;
import org.apache.skywalking.apm.collector.storage.dao.ui.IApplicationMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IInstanceUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IServiceMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.table.MetricSource;
import org.apache.skywalking.apm.collector.storage.ui.application.Application;
import org.apache.skywalking.apm.collector.storage.ui.common.Step;
import org.apache.skywalking.apm.collector.storage.ui.overview.ApplicationTPS;
import org.apache.skywalking.apm.collector.storage.ui.service.ServiceMetric;
/**
......@@ -39,12 +41,14 @@ public class ApplicationService {
private final IInstanceUIDAO instanceDAO;
private final IServiceMetricUIDAO serviceMetricUIDAO;
private final IApplicationMetricUIDAO applicationMetricUIDAO;
private final ApplicationCacheService applicationCacheService;
private final ServiceNameCacheService serviceNameCacheService;
public ApplicationService(ModuleManager moduleManager) {
this.instanceDAO = moduleManager.find(StorageModule.NAME).getService(IInstanceUIDAO.class);
this.serviceMetricUIDAO = moduleManager.find(StorageModule.NAME).getService(IServiceMetricUIDAO.class);
this.applicationMetricUIDAO = moduleManager.find(StorageModule.NAME).getService(IApplicationMetricUIDAO.class);
this.applicationCacheService = moduleManager.find(CacheModule.NAME).getService(ApplicationCacheService.class);
this.serviceNameCacheService = moduleManager.find(CacheModule.NAME).getService(ServiceNameCacheService.class);
}
......@@ -69,4 +73,15 @@ public class ApplicationService {
});
return slowServices;
}
public List<ApplicationTPS> getTopNApplicationThroughput(Step step, long start, long end,
int topN) throws ParseException {
//TODO
List<ApplicationTPS> applicationThroughput = applicationMetricUIDAO.getTopNApplicationThroughput(step, start, end, 1000, topN, MetricSource.Callee);
applicationThroughput.forEach(applicationTPS -> {
String applicationCode = applicationCacheService.getApplicationById(applicationTPS.getApplicationId()).getApplicationCode();
applicationTPS.setApplicationCode(applicationCode);
});
return applicationThroughput;
}
}
......@@ -36,8 +36,6 @@ import org.apache.skywalking.apm.collector.storage.dao.ui.IGCMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IInstanceMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IInstanceUIDAO;
import org.apache.skywalking.apm.collector.storage.dao.ui.IMemoryMetricUIDAO;
import org.apache.skywalking.apm.collector.storage.table.MetricSource;
import org.apache.skywalking.apm.collector.storage.table.register.Instance;
import org.apache.skywalking.apm.collector.storage.ui.common.ResponseTimeTrend;
import org.apache.skywalking.apm.collector.storage.ui.common.Step;
import org.apache.skywalking.apm.collector.storage.ui.common.ThroughputTrend;
......@@ -72,18 +70,6 @@ public class ServerService {
this.applicationCacheService = moduleManager.find(CacheModule.NAME).getService(ApplicationCacheService.class);
}
public List<AppServerInfo> getTopNServerThroughput(int applicationId, Step step, long start, long end, int topN) {
//TODO
List<AppServerInfo> appServerInfos = instanceMetricUIDAO.getTopNServerThroughput(applicationId, step, start, end, 1000, topN, MetricSource.Callee);
appServerInfos.forEach(appServerInfo -> {
Instance instance = instanceUIDAO.getInstance(appServerInfo.getId());
appServerInfo.setOsInfo(instance.getOsInfo());
});
buildAppServerInfo(appServerInfos);
return appServerInfos;
}
public List<AppServerInfo> searchServer(String keyword, long start, long end) {
List<AppServerInfo> serverInfos = instanceUIDAO.searchServer(keyword, start, end);
serverInfos.forEach(serverInfo -> {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册