未验证 提交 190f0090 编写于 作者: W Weihao Li 提交者: GitHub

Fix code smells of UDF and Trigger in ConfigNode

上级 d0b6a661
......@@ -38,5 +38,7 @@ public class GetFunctionTablePlan extends ConfigPhysicalPlan {
}
@Override
protected void deserializeImpl(ByteBuffer buffer) throws IOException {}
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
// do nothing
}
}
......@@ -38,5 +38,7 @@ public class GetTransferringTriggersPlan extends ConfigPhysicalPlan {
}
@Override
protected void deserializeImpl(ByteBuffer buffer) throws IOException {}
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
// do nothing
}
}
......@@ -28,6 +28,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class GetTriggerJarPlan extends ConfigPhysicalPlan {
......@@ -65,4 +66,24 @@ public class GetTriggerJarPlan extends ConfigPhysicalPlan {
size--;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
GetTriggerJarPlan that = (GetTriggerJarPlan) o;
return Objects.equals(jarNames, that.jarNames);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), jarNames);
}
}
......@@ -26,6 +26,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class GetTriggerLocationPlan extends ConfigPhysicalPlan {
......@@ -59,4 +60,24 @@ public class GetTriggerLocationPlan extends ConfigPhysicalPlan {
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
this.triggerName = ReadWriteIOUtils.readString(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
GetTriggerLocationPlan that = (GetTriggerLocationPlan) o;
return Objects.equals(triggerName, that.triggerName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), triggerName);
}
}
......@@ -26,6 +26,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class GetTriggerTablePlan extends ConfigPhysicalPlan {
......@@ -59,4 +60,24 @@ public class GetTriggerTablePlan extends ConfigPhysicalPlan {
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
this.onlyStateful = ReadWriteIOUtils.readBool(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
GetTriggerTablePlan that = (GetTriggerTablePlan) o;
return onlyStateful == that.onlyStateful;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), onlyStateful);
}
}
......@@ -28,6 +28,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class GetUDFJarPlan extends ConfigPhysicalPlan {
......@@ -65,4 +66,24 @@ public class GetUDFJarPlan extends ConfigPhysicalPlan {
size--;
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
GetUDFJarPlan that = (GetUDFJarPlan) o;
return Objects.equals(jarNames, that.jarNames);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), jarNames);
}
}
......@@ -28,6 +28,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class CreateFunctionPlan extends ConfigPhysicalPlan {
......@@ -73,4 +74,24 @@ public class CreateFunctionPlan extends ConfigPhysicalPlan {
}
jarFile = ReadWriteIOUtils.readBinary(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
CreateFunctionPlan that = (CreateFunctionPlan) o;
return Objects.equals(udfInformation, that.udfInformation);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), udfInformation);
}
}
......@@ -26,6 +26,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class DropFunctionPlan extends ConfigPhysicalPlan {
......@@ -54,4 +55,24 @@ public class DropFunctionPlan extends ConfigPhysicalPlan {
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
functionName = ReadWriteIOUtils.readString(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
DropFunctionPlan that = (DropFunctionPlan) o;
return Objects.equals(functionName, that.functionName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), functionName);
}
}
......@@ -28,6 +28,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class AddTriggerInTablePlan extends ConfigPhysicalPlan {
......@@ -81,4 +82,24 @@ public class AddTriggerInTablePlan extends ConfigPhysicalPlan {
}
jarFile = ReadWriteIOUtils.readBinary(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
AddTriggerInTablePlan that = (AddTriggerInTablePlan) o;
return Objects.equals(triggerInformation, that.triggerInformation);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), triggerInformation);
}
}
......@@ -26,6 +26,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class DeleteTriggerInTablePlan extends ConfigPhysicalPlan {
......@@ -59,4 +60,24 @@ public class DeleteTriggerInTablePlan extends ConfigPhysicalPlan {
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
triggerName = ReadWriteIOUtils.readString(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
DeleteTriggerInTablePlan that = (DeleteTriggerInTablePlan) o;
return Objects.equals(triggerName, that.triggerName);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), triggerName);
}
}
......@@ -28,6 +28,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class UpdateTriggerLocationPlan extends ConfigPhysicalPlan {
......@@ -73,4 +74,25 @@ public class UpdateTriggerLocationPlan extends ConfigPhysicalPlan {
this.triggerName = ReadWriteIOUtils.readString(buffer);
this.dataNodeLocation = ThriftCommonsSerDeUtils.deserializeTDataNodeLocation(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
UpdateTriggerLocationPlan that = (UpdateTriggerLocationPlan) o;
return Objects.equals(triggerName, that.triggerName)
&& Objects.equals(dataNodeLocation, that.dataNodeLocation);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), triggerName, dataNodeLocation);
}
}
......@@ -27,6 +27,7 @@ import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
public class UpdateTriggerStateInTablePlan extends ConfigPhysicalPlan {
......@@ -72,4 +73,24 @@ public class UpdateTriggerStateInTablePlan extends ConfigPhysicalPlan {
triggerName = ReadWriteIOUtils.readString(buffer);
triggerState = TTriggerState.findByValue(ReadWriteIOUtils.readInt(buffer));
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
UpdateTriggerStateInTablePlan that = (UpdateTriggerStateInTablePlan) o;
return Objects.equals(triggerName, that.triggerName) && triggerState == that.triggerState;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), triggerName, triggerState);
}
}
......@@ -30,6 +30,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class UpdateTriggersOnTransferNodesPlan extends ConfigPhysicalPlan {
......@@ -37,6 +38,7 @@ public class UpdateTriggersOnTransferNodesPlan extends ConfigPhysicalPlan {
public UpdateTriggersOnTransferNodesPlan() {
super(ConfigPhysicalPlanType.UpdateTriggersOnTransferNodes);
this.dataNodeLocations = new ArrayList<>();
}
public UpdateTriggersOnTransferNodesPlan(List<TDataNodeLocation> dataNodeLocations) {
......@@ -65,11 +67,29 @@ public class UpdateTriggersOnTransferNodesPlan extends ConfigPhysicalPlan {
@Override
protected void deserializeImpl(ByteBuffer buffer) throws IOException {
int size = ReadWriteIOUtils.readInt(buffer);
List<TDataNodeLocation> dataNodeLocations = new ArrayList<>(size);
while (size > 0) {
dataNodeLocations.add(ThriftCommonsSerDeUtils.deserializeTDataNodeLocation(buffer));
size--;
}
this.dataNodeLocations = dataNodeLocations;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
UpdateTriggersOnTransferNodesPlan that = (UpdateTriggersOnTransferNodesPlan) o;
return Objects.equals(dataNodeLocations, that.dataNodeLocations);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), dataNodeLocations);
}
}
......@@ -33,13 +33,11 @@ public class FunctionTableResp implements DataSet {
private TSStatus status;
private List<UDFInformation> allUDFInformation;
private final List<UDFInformation> allUdfInformation;
public FunctionTableResp() {}
public FunctionTableResp(TSStatus status, List<UDFInformation> allUDFInformation) {
public FunctionTableResp(TSStatus status, List<UDFInformation> allUdfInformation) {
this.status = status;
this.allUDFInformation = allUDFInformation;
this.allUdfInformation = allUdfInformation;
}
public TSStatus getStatus() {
......@@ -50,18 +48,10 @@ public class FunctionTableResp implements DataSet {
this.status = status;
}
public List<UDFInformation> getAllUDFInformation() {
return allUDFInformation;
}
public void setAllUDFInformation(List<UDFInformation> allUDFInformation) {
this.allUDFInformation = allUDFInformation;
}
public TGetUDFTableResp convertToThriftResponse() throws IOException {
List<ByteBuffer> udfInformationByteBuffers = new ArrayList<>();
for (UDFInformation udfInformation : allUDFInformation) {
for (UDFInformation udfInformation : allUdfInformation) {
udfInformationByteBuffers.add(udfInformation.serialize());
}
......
......@@ -23,7 +23,6 @@ import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.confignode.rpc.thrift.TGetJarInListResp;
import org.apache.iotdb.consensus.common.DataSet;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
......@@ -46,7 +45,7 @@ public class JarResp implements DataSet {
this.status = status;
}
public TGetJarInListResp convertToThriftResponse() throws IOException {
public TGetJarInListResp convertToThriftResponse() {
return new TGetJarInListResp(status, jarList);
}
}
......@@ -107,8 +107,8 @@ public class TriggerManager {
}
}
final String triggerName = req.getTriggerName();
final boolean isUsingURI = req.isIsUsingURI(),
needToSaveJar = isUsingURI && triggerInfo.needToSaveJar(triggerName);
final boolean isUsingURI = req.isIsUsingURI();
final boolean needToSaveJar = isUsingURI && triggerInfo.needToSaveJar(triggerName);
TriggerInformation triggerInformation =
new TriggerInformation(
(PartialPath) PathDeserializeUtil.deserialize(req.pathPattern),
......@@ -159,20 +159,12 @@ public class TriggerManager {
}
public TGetJarInListResp getTriggerJar(TGetJarInListReq req) {
try {
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetTriggerJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
} catch (IOException e) {
LOGGER.error("Fail to get TriggerJar", e);
return new TGetJarInListResp(
new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
.setMessage(e.getMessage()),
Collections.emptyList());
}
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetTriggerJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
}
/**
......
......@@ -78,9 +78,9 @@ public class UDFManager {
udfInfo.acquireUDFTableLock();
try {
final boolean isUsingURI = req.isIsUsingURI();
final String udfName = req.udfName.toUpperCase(),
jarMD5 = req.getJarMD5(),
jarName = req.getJarName();
final String udfName = req.udfName.toUpperCase();
final String jarMD5 = req.getJarMD5();
final String jarName = req.getJarName();
final byte[] jarFile = req.getJarFile();
udfInfo.validate(udfName, jarName, jarMD5);
......@@ -183,19 +183,11 @@ public class UDFManager {
}
public TGetJarInListResp getUDFJar(TGetJarInListReq req) {
try {
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetUDFJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
} catch (IOException e) {
LOGGER.error("Fail to get TriggerJar", e);
return new TGetJarInListResp(
new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
.setMessage(e.getMessage()),
Collections.emptyList());
}
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetUDFJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
}
}
......@@ -93,19 +93,11 @@ public class PipePluginCoordinator {
}
public TGetJarInListResp getPipePluginJar(TGetJarInListReq req) {
try {
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetPipePluginJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
} catch (IOException e) {
LOGGER.error("Fail to get PipePluginJar", e);
return new TGetJarInListResp(
new TSStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode())
.setMessage(e.getMessage()),
Collections.emptyList());
}
return ((JarResp)
configManager
.getConsensusManager()
.read(new GetPipePluginJarPlan(req.getJarNameList()))
.getDataset())
.convertToThriftResponse();
}
}
......@@ -63,7 +63,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
public class TriggerInfo implements SnapshotProcessor {
......@@ -74,7 +73,6 @@ public class TriggerInfo implements SnapshotProcessor {
private final TriggerTable triggerTable;
private final Map<String, String> existedJarToMD5;
// private final Map<String, AtomicInteger> jarReferenceTable;
private final TriggerExecutableManager triggerExecutableManager;
......@@ -85,7 +83,6 @@ public class TriggerInfo implements SnapshotProcessor {
public TriggerInfo() throws IOException {
triggerTable = new TriggerTable();
existedJarToMD5 = new HashMap<>();
// jarReferenceTable = new ConcurrentHashMap<>();
triggerExecutableManager =
TriggerExecutableManager.setupAndGetInstance(
CONFIG_NODE_CONF.getTriggerTemporaryLibDir(), CONFIG_NODE_CONF.getTriggerDir());
......@@ -101,8 +98,9 @@ public class TriggerInfo implements SnapshotProcessor {
triggerTableLock.unlock();
}
/** Validate whether the trigger can be created */
public void validate(String triggerName, String jarName, String jarMD5) {
/** Validate whether the trigger can be created. */
public void validate(String triggerName, String jarName, String jarMD5)
throws TriggerManagementException {
if (triggerTable.containsTrigger(triggerName)) {
throw new TriggerManagementException(
String.format(
......@@ -118,8 +116,8 @@ public class TriggerInfo implements SnapshotProcessor {
}
}
/** Validate whether the trigger can be dropped */
public void validate(String triggerName) {
/** Validate whether the trigger can be dropped. */
public void validate(String triggerName) throws TriggerManagementException {
if (triggerTable.containsTrigger(triggerName)) {
return;
}
......@@ -248,29 +246,17 @@ public class TriggerInfo implements SnapshotProcessor {
snapshotFile.getAbsolutePath());
return false;
}
File tmpFile = new File(snapshotFile.getAbsolutePath() + "-" + UUID.randomUUID());
acquireTriggerTableLock();
try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) {
try (FileOutputStream fileOutputStream = new FileOutputStream(snapshotFile)) {
serializeExistedJarToMD5(fileOutputStream);
triggerTable.serializeTriggerTable(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
return tmpFile.renameTo(snapshotFile);
return true;
} finally {
releaseTriggerTableLock();
for (int retry = 0; retry < 5; retry++) {
if (!tmpFile.exists() || tmpFile.delete()) {
break;
} else {
LOGGER.warn(
"Can't delete temporary snapshot file: {}, retrying...", tmpFile.getAbsolutePath());
}
}
}
}
......
......@@ -69,7 +69,7 @@ public class UDFInfo implements SnapshotProcessor {
private final ReentrantLock udfTableLock = new ReentrantLock();
private static final String snapshotFileName = "udf_info.bin";
private static final String SNAPSHOT_FILENAME = "udf_info.bin";
public UDFInfo() throws IOException {
udfTable = new UDFTable();
......@@ -89,23 +89,24 @@ public class UDFInfo implements SnapshotProcessor {
udfTableLock.unlock();
}
/** Validate whether the UDF can be created */
public void validate(String UDFName, String jarName, String jarMD5) {
if (udfTable.containsUDF(UDFName)) {
/** Validate whether the UDF can be created. */
public void validate(String udfName, String jarName, String jarMD5)
throws UDFManagementException {
if (udfTable.containsUDF(udfName)) {
throw new UDFManagementException(
String.format("Failed to create UDF [%s], the same name UDF has been created", UDFName));
String.format("Failed to create UDF [%s], the same name UDF has been created", udfName));
}
if (existedJarToMD5.containsKey(jarName) && !existedJarToMD5.get(jarName).equals(jarMD5)) {
throw new UDFManagementException(
String.format(
"Failed to create UDF [%s], the same name Jar [%s] but different MD5 [%s] has existed",
UDFName, jarName, jarMD5));
udfName, jarName, jarMD5));
}
}
/** Validate whether the UDF can be dropped */
public void validate(String udfName) {
/** Validate whether the UDF can be dropped. */
public void validate(String udfName) throws UDFManagementException {
if (udfTable.containsUDF(udfName)) {
return;
}
......@@ -185,7 +186,7 @@ public class UDFInfo implements SnapshotProcessor {
@Override
public boolean processTakeSnapshot(File snapshotDir) throws IOException {
File snapshotFile = new File(snapshotDir, snapshotFileName);
File snapshotFile = new File(snapshotDir, SNAPSHOT_FILENAME);
if (snapshotFile.exists() && snapshotFile.isFile()) {
LOGGER.error(
"Failed to take snapshot, because snapshot file [{}] is already exist.",
......@@ -208,7 +209,7 @@ public class UDFInfo implements SnapshotProcessor {
@Override
public void processLoadSnapshot(File snapshotDir) throws IOException {
File snapshotFile = new File(snapshotDir, snapshotFileName);
File snapshotFile = new File(snapshotDir, SNAPSHOT_FILENAME);
if (!snapshotFile.exists() || !snapshotFile.isFile()) {
LOGGER.error(
"Failed to load snapshot,snapshot file [{}] is not exist.",
......
......@@ -44,8 +44,8 @@ import org.slf4j.LoggerFactory;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
/** create trigger procedure */
public class CreateTriggerProcedure extends AbstractNodeProcedure<CreateTriggerState> {
private static final Logger LOG = LoggerFactory.getLogger(CreateTriggerProcedure.class);
private static final int RETRY_THRESHOLD = 5;
......@@ -150,6 +150,9 @@ public class CreateTriggerProcedure extends AbstractNodeProcedure<CreateTriggerS
case CONFIG_NODE_ACTIVE:
env.getConfigManager().getTriggerManager().getTriggerInfo().releaseTriggerTableLock();
return Flow.NO_MORE_STATE;
default:
throw new IllegalArgumentException("Unknown CreateTriggerState: " + state);
}
} catch (Exception e) {
if (isRollbackSupported(state)) {
......@@ -285,4 +288,9 @@ public class CreateTriggerProcedure extends AbstractNodeProcedure<CreateTriggerS
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(getProcId(), getState(), triggerInformation);
}
}
......@@ -39,8 +39,8 @@ import org.slf4j.LoggerFactory;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Objects;
/** drop trigger procedure */
public class DropTriggerProcedure extends AbstractNodeProcedure<DropTriggerState> {
private static final Logger LOG = LoggerFactory.getLogger(DropTriggerProcedure.class);
private static final int RETRY_THRESHOLD = 5;
......@@ -102,6 +102,9 @@ public class DropTriggerProcedure extends AbstractNodeProcedure<DropTriggerState
case CONFIG_NODE_DROPPED:
env.getConfigManager().getTriggerManager().getTriggerInfo().releaseTriggerTableLock();
return Flow.NO_MORE_STATE;
default:
throw new IllegalArgumentException("Unknown DropTriggerState: " + state);
}
} catch (Exception e) {
if (isRollbackSupported(state)) {
......@@ -173,4 +176,9 @@ public class DropTriggerProcedure extends AbstractNodeProcedure<DropTriggerState
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(getProcId(), getState(), triggerName);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册