提交 d11f0363 编写于 作者: S shiziyuan9527
......@@ -497,6 +497,15 @@
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
<destFileName>ApacheJMeter_functions.jar</destFileName>
</artifactItem>
<artifactItem>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
<type>jar</type>
<overWrite>true</overWrite>
<outputDirectory>src/main/resources/jmeter/lib/ext</outputDirectory>
<destFileName>jython-standalone.jar</destFileName>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}/wars</outputDirectory>
<overWriteReleases>false</overWriteReleases>
......
......@@ -16,6 +16,7 @@ public class Scenario {
private List<KeyValue> headers;
private List<Request> requests;
private DubboConfig dubboConfig;
private TCPConfig tcpConfig;
private List<DatabaseConfig> databaseConfigs;
private Boolean enable;
}
package io.metersphere.api.dto.scenario;
import lombok.Data;
@Data
public class TCPConfig {
private String classname;
private String server;
private Integer port;
private Integer ctimeout;
private Integer timeout;
private Boolean reUseConnection;
private Boolean nodelay;
private Boolean closeConnection;
private String soLinger;
private String eolByte;
private String username;
private String password;
}
......@@ -11,7 +11,7 @@ import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jorphan.collections.HashTree;
import org.python.core.Options;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
......@@ -34,10 +34,6 @@ public class JMeterService {
JMeterUtils.setJMeterHome(JMETER_HOME);
JMeterUtils.setLocale(LocaleContextHolder.getLocale());
//解决无法加载 PyScriptEngineFactory
Options.importSite = false;
try {
Object scriptWrapper = SaveService.loadElement(is);
HashTree testPlan = getHashTree(scriptWrapper);
......@@ -51,7 +47,7 @@ public class JMeterService {
}
}
private String getJmeterHome() {
public String getJmeterHome() {
String home = getClass().getResource("/").getPath() + "jmeter";
try {
File file = new File(home);
......
......@@ -173,6 +173,12 @@
#{value}
</foreach>
</when>
<when test="key=='executor'">
and test_plan_test_case.executor in
<foreach collection="values" item="value" separator="," open="(" close=")">
#{value}
</foreach>
</when>
<otherwise>
and test_plan_test_case.status in
<foreach collection="values" item="value" separator="," open="(" close=")">
......
package io.metersphere.listener;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.service.ScheduleService;
import org.python.core.Options;
import org.python.util.PythonInterpreter;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
......@@ -12,12 +16,16 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
@Resource
private ScheduleService scheduleService;
@Resource
private JMeterService jMeterService;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("================= 应用启动 =================");
initPythonEnv();
try {
Thread.sleep(3 * 60 * 1000);
} catch (InterruptedException e) {
......@@ -25,6 +33,24 @@ public class AppStartListener implements ApplicationListener<ApplicationReadyEve
}
scheduleService.startEnableSchedules();
}
/**
* 解决接口测试-无法导入内置python包
*/
private void initPythonEnv() {
//解决无法加载 PyScriptEngineFactory
Options.importSite = false;
try {
PythonInterpreter interp = new PythonInterpreter();
String path = jMeterService.getJmeterHome();
System.out.println("sys.path: " + path);
path += "/lib/ext/jython-standalone.jar/Lib";
interp.exec("import sys");
interp.exec("sys.path.append(\"" + path + "\")");
} catch (Exception e) {
e.printStackTrace();
LogUtil.error(e.getMessage(), e);
}
}
}
......@@ -8,7 +8,7 @@
<el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60"
:placeholder="$t('api_test.input_name')"
show-word-limit>
<el-select :disabled="isReadOnly" class="test-project" v-model="test.projectId" slot="prepend"
<el-select filterable class="test-project" v-model="test.projectId" slot="prepend"
:placeholder="$t('api_test.select_project')">
<el-option v-for="project in projects" :key="project.id" :label="project.name" :value="project.id"/>
</el-select>
......
<template>
<el-dialog :close-on-click-modal="false" :title="$t('api_test.environment.environment_config')"
:visible.sync="visible" class="environment-dialog"
:visible.sync="visible" class="environment-dialog" width="60%"
@close="close" append-to-body ref="environmentConfig">
<el-container v-loading="result.loading">
<ms-aside-item :enable-aside-hidden="false" :title="$t('api_test.environment.environment_list')"
......
<template>
<div class="variable-input">
<el-input :disabled="isReadOnly" :value="value" v-bind="$attrs" :size="size" @change="change" @input="input"/>
<el-input class="el-input__inner_pd" :disabled="isReadOnly" :value="value" v-bind="$attrs" :size="size" @change="change" @input="input"/>
<div :class="{'hidden': !showVariable}" class="variable-combine" v-if="value">
<div class="variable">{{variable}}</div>
<el-tooltip :content="$t('api_test.copied')" manual v-model="visible" placement="top" :visible-arrow="false">
......@@ -70,6 +70,9 @@
.variable-input {
position: relative;
}
.el-input__inner_pd >>> .el-input__inner {
padding-right: 135px;
}
.variable-combine {
color: #7F7F7F;
......
......@@ -1052,10 +1052,21 @@ class JMXTCPRequest {
obj.set(scenario.environment.config.tcpConfig, true);
return obj;
}
obj.set(scenario.tcpConfig, true);
this.copy(this, scenario.tcpConfig);
return obj;
}
copy(target, source) {
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] !== undefined && !target[key]) {
target[key] = source[key];
}
}
}
}
}
class JMeterTestPlan extends Element {
......
......@@ -9,7 +9,7 @@
maxlength="30" show-word-limit
>
<template v-slot:prepend>
<el-select :disabled="isReadOnly" v-model="testPlan.projectId"
<el-select filterable v-model="testPlan.projectId"
:placeholder="$t('load_test.select_project')">
<el-option
v-for="item in projects"
......
......@@ -101,7 +101,7 @@
<el-form :model="memberForm" ref="form" :rules="orgMemberRule" label-position="right" label-width="100px"
size="small">
<el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
<el-select filterable v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
class="select-width">
<el-option
v-for="item in memberForm.userList"
......@@ -114,7 +114,7 @@
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.role')" prop="roleIds">
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
class="select-width">
<el-option
v-for="item in memberForm.roles"
......@@ -151,7 +151,7 @@
</el-form-item>
<el-form-item :label="$t('commons.role')" prop="roleIds"
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}">
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
class="select-width">
<el-option
v-for="item in memberForm.allroles"
......
......@@ -37,7 +37,7 @@
<el-input type="textarea" v-model="form.description"></el-input>
</el-form-item>
<el-form-item :label="$t('workspace.organization_name')" prop="organizationId">
<el-select v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
<el-select filterable v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
class="select-width">
<el-option
v-for="item in form.orgList"
......@@ -65,7 +65,7 @@
<el-input type="textarea" v-model="form.description"></el-input>
</el-form-item>
<el-form-item :label="$t('workspace.organization_name')" prop="organizationId">
<el-select v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
<el-select filterable v-model="form.organizationId" :placeholder="$t('organization.select_organization')"
class="select-width">
<el-option
v-for="item in form.orgList1"
......@@ -117,7 +117,7 @@
<el-form :model="memberForm" ref="form" :rules="wsMemberRule" label-position="right" label-width="100px"
size="small">
<el-form-item :label="$t('commons.member')" prop="userIds">
<el-select v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
<el-select filterable v-model="memberForm.userIds" multiple :placeholder="$t('member.please_choose_member')"
class="select-width">
<el-option
v-for="item in memberForm.userList"
......@@ -130,7 +130,7 @@
</el-select>
</el-form-item>
<el-form-item :label="$t('commons.role')" prop="roleIds">
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
class="select-width">
<el-option
v-for="item in memberForm.roles"
......@@ -166,7 +166,7 @@
<el-input v-model="memberForm.phone" autocomplete="off" :disabled="true"/>
</el-form-item>
<el-form-item :label="$t('commons.role')" prop="roleIds" :rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}">
<el-select v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
<el-select filterable v-model="memberForm.roleIds" multiple :placeholder="$t('role.please_choose_role')"
class="select-width">
<el-option
v-for="item in memberForm.allroles"
......
......@@ -74,7 +74,7 @@
:prop="'roles.' + index + '.id'"
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}"
>
<el-select v-model="role.id" :placeholder="$t('role.please_choose_role')">
<el-select filterable v-model="role.id" :placeholder="$t('role.please_choose_role')">
<el-option
v-for="item in activeRole(role)"
:key="item.id"
......@@ -93,7 +93,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-option
v-for="item in form.orgList"
:key="item.id"
......@@ -108,7 +108,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......@@ -123,7 +123,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......@@ -138,7 +138,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......@@ -187,7 +187,7 @@
:prop="'roles.' + index + '.id'"
:rules="{required: true, message: $t('role.please_choose_role'), trigger: 'change'}"
>
<el-select v-model="role.id" :placeholder="$t('role.please_choose_role')" :disabled="!!role.id">
<el-select filterable v-model="role.id" :placeholder="$t('role.please_choose_role')" :disabled="!!role.id">
<el-option
v-for="item in activeRole(role)"
:key="item.id"
......@@ -204,7 +204,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('organization.select_organization'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('organization.select_organization')" multiple>
<el-option
v-for="item in form.orgList"
:key="item.id"
......@@ -219,7 +219,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......@@ -234,7 +234,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......@@ -249,7 +249,7 @@
:prop="'roles.' + index + '.ids'"
:rules="{required: true, message: $t('workspace.select'), trigger: 'change'}"
>
<el-select v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-select filterable v-model="role.ids" :placeholder="$t('workspace.select')" multiple>
<el-option
v-for="item in form.wsList"
:key="item.id"
......
<template>
<el-form :model="form" ref="caseFrom" v-loading="result.loading">
<el-row>
<el-col :span="8" :offset="1">
<el-form-item
:placeholder="$t('test_track.case.input_name')"
:label="$t('test_track.case.name')"
:label-width="formLabelWidth"
prop="name">
<el-input class="case-name" :disabled="readOnly" v-model="testCase.name"></el-input>
</el-form-item>
</el-col>
<el-col :span="11" :offset="2">
<el-form-item :label="$t('test_track.case.module')" :label-width="formLabelWidth" prop="module">
<el-input class="case-name" :disabled="readOnly" v-model="testCase.nodePath"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10" :offset="1">
<el-form-item :label="$t('test_track.case.maintainer')" :label-width="formLabelWidth" prop="maintainer">
<el-select :disabled="readOnly" v-model="testCase.maintainer"
:placeholder="$t('test_track.case.input_maintainer')" filterable>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('test_track.case.priority')" :label-width="formLabelWidth" prop="priority">
<el-select :disabled="readOnly" v-model="testCase.priority" clearable
:placeholder="$t('test_track.case.input_priority')">
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="10" :offset="1">
<el-form-item :label="$t('test_track.case.type')" :label-width="formLabelWidth" prop="type">
<el-select :disabled="readOnly" v-model="testCase.type"
:placeholder="$t('test_track.case.input_type')">
<el-option :label="$t('commons.functional')" value="functional"></el-option>
<el-option :label="$t('commons.performance')" value="performance"></el-option>
<el-option :label="$t('commons.api')" value="api"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="$t('test_track.case.method')" :label-width="formLabelWidth" prop="method">
<el-select :disabled="readOnly" v-model="testCase.method" :placeholder="$t('test_track.case.input_method')">
<el-option :label="$t('test_track.case.auto')" value="auto"></el-option>
<el-option :label="$t('test_track.case.manual')" value="manual"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="testCase.method && testCase.method == 'auto'">
<el-col :span="9" :offset="1">
<el-form-item :label="$t('test_track.case.relate_test')" :label-width="formLabelWidth" prop="testId">
<el-select filterable :disabled="readOnly" v-model="testCase.testId"
:placeholder="$t('test_track.case.input_type')">
</el-select>
</el-form-item>
</el-col>
<el-col :span="9" :offset="1" v-if="testCase.testId=='other'">
<el-form-item :label="$t('test_track.case.test_name')" :label-width="formLabelWidth" prop="testId">
<el-input v-model="testCase.otherTestName" :placeholder="$t('test_track.case.input_test_case')"
:disabled="readOnly"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row style="margin-top: 15px;">
<el-col :offset="2">{{ $t('test_track.case.prerequisite') }}:</el-col>
</el-row>
<el-row type="flex" justify="center" style="margin-top: 10px;">
<el-col :span="20">
<el-form-item prop="prerequisite">
<el-input :disabled="readOnly" v-model="testCase.prerequisite"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4}"
:rows="2"
:placeholder="$t('test_track.case.input_prerequisite')"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row v-if="testCase.method && testCase.method != 'auto'" style="margin-bottom: 10px">
<el-col :offset="2">{{ $t('test_track.case.steps') }}:</el-col>
</el-row>
<el-row v-if="testCase.method && testCase.method != 'auto'" type="flex" justify="center">
<el-col :span="20">
<el-table
v-if="isStepTableAlive"
:data="JSON.parse(testCase.steps)"
class="tb-edit"
border
size="mini"
:default-sort="{prop: 'num', order: 'ascending'}"
highlight-current-row>
<el-table-column :label="$t('test_track.case.number')" prop="num" min-width="15%"></el-table-column>
<el-table-column :label="$t('test_track.case.step_desc')" prop="desc" min-width="35%">
<template v-slot:default="scope">
<el-input
class="table-edit-input"
size="mini"
:disabled="readOnly"
type="textarea"
:autosize="{ minRows: 1, maxRows: 6}"
:rows="2"
v-model="scope.row.desc"
:placeholder="$t('commons.input_content')"
clearable/>
</template>
</el-table-column>
<el-table-column :label="$t('test_track.case.expected_results')" prop="result" min-width="35%">
<template v-slot:default="scope">
<el-input
class="table-edit-input"
size="mini"
:disabled="readOnly"
type="textarea"
:autosize="{ minRows: 1, maxRows: 6}"
:rows="2"
v-model="scope.row.result"
:placeholder="$t('commons.input_content')"
clearable/>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row style="margin-top: 15px;margin-bottom: 10px">
<el-col :offset="2">{{ $t('commons.remark') }}:</el-col>
</el-row>
<el-row type="flex" justify="center">
<el-col :span="20">
<el-form-item prop="remark">
<el-input v-model="testCase.remark"
:autosize="{ minRows: 2, maxRows: 4}"
type="textarea"
:disabled="readOnly"
:rows="2"
:placeholder="$t('commons.input_content')"></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script>
export default {
name: "TestCaseDetail",
data() {
return {
result: {},
dialogFormVisible: false,
readOnly: true,
form: {
name: '',
module: '',
maintainer: '',
priority: '',
type: '',
method: '',
prerequisite: '',
testId: '',
otherTestName: '',
steps: [{
num: 1,
desc: '',
result: ''
}],
remark: '',
},
workspaceId: '',
formLabelWidth: "120px",
isStepTableAlive: true,
methodOptions: [
{value: 'auto', label: this.$t('test_track.case.auto')},
{value: 'manual', label: this.$t('test_track.case.manual')}
]
};
},
props: {
testCase: {
type: Object
}
},
}
</script>
<style scoped>
</style>
......@@ -56,9 +56,18 @@
show-overflow-tooltip
>
<template v-slot:default="scope">
<div @mouseover="showDetail(scope.row)">
<!--<div @mouseover="showDetail(scope.row)">
<p>{{ scope.row.name }}</p>
</div>
</div>-->
<el-popover
placement="right-end"
:title="$t('test_track.case.view_case')"
width="60%"
trigger="hover"
>
<test-case-detail :test-case="scope.row"/>
<p slot="reference">{{ scope.row.name }}</p>
</el-popover>
</template>
</el-table-column>
<el-table-column
......@@ -164,7 +173,7 @@
import {WORKSPACE_ID} from "../../../../../common/js/constants";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import StatusTableItem from "@/business/components/track/common/tableItems/planview/StatusTableItem";
import TestCaseDetail from "./TestCaseDetail";
export default {
name: "TestCaseList",
components: {
......@@ -182,7 +191,8 @@
MsTableHeader,
ShowMoreBtn,
BatchEdit,
StatusTableItem
StatusTableItem,
TestCaseDetail
},
data() {
return {
......
<template>
<el-menu :unique-opened="true" mode="horizontal" active-text-color="write"
class="project_menu">
<el-submenu index="1" popper-class="submenu">
<template v-slot:title>
<span class="menu-title">{{'[' + title + ']'}}</span>
<span> {{currentData == null ? '' : currentData.name}} </span>
</template>
<template v-slot:default>
<div style="height:400px;">
<el-scrollbar style="height:100%">
<label v-for="(item,index) in data" :key="index">
<el-menu-item @click="changeData(item)">
{{item.name}}
<i class="el-icon-check" v-if="currentData && item.id === currentData.id"></i>
</el-menu-item>
</label>
</el-scrollbar>
</div>
</template>
</el-submenu>
</el-menu>
<div>
<span class="menu-title">{{'[' + title + ']'}}</span>
<el-select filterable slot="prepend" v-model="value" @change="changeData" class="project_menu"
size="small">
<el-option v-for="(item,index) in data" :key="index" :label="item.name" :value="index"/>
</el-select>
</div>
</template>
<script>
export default {
name: "SelectMenu",
props: {
data: {
type: Array
},
currentData: {
type: Object
},
title: {
type: String
}
export default {
name: "SelectMenu",
props: {
data: {
type: Array
},
currentData: {
type: Object
},
methods: {
changeData(data) {
this.$emit("dataChange", data);
title: {
type: String
}
},
data() {
return {
value: ''
}
},
watch: {
currentData(data) {
if (data != undefined && data != null) {
this.value = data.name;
}
}
},
methods: {
changeData(index) {
this.$emit("dataChange", this.data[index]);
}
}
}
</script>
<style scoped>
.project_menu {
width: 214px;
}
.menu-title {
color: darkgrey;
margin-left: 10px;
margin-right: 10px;
}
</style>
......@@ -180,6 +180,7 @@
:disabled="isReadOnly"
v-model="scope.row.executeResult"
@change="stepResultChange()"
filterable
size="mini">
<el-option :label="$t('test_track.plan_view.pass')" value="Pass"
style="color: #7ebf50;"></el-option>
......@@ -227,6 +228,7 @@
{{ $t('test_track.issue.please_choose_current_owner') }}
<el-select v-model="testCase.tapdUsers"
multiple
filterable
style="width: 20%"
:placeholder="$t('test_track.issue.please_choose_current_owner')"
collapse-tags>
......
......@@ -61,7 +61,7 @@ export default {
window.console.error(error.response || error.message);
if (error.response && error.response.data) {
if (error.response.headers["authentication-status"] !== "invalid") {
Message.error({message: error.response.data.message, showClose: true});
Message.error({message: error.response.data.message || error.response.data, showClose: true});
}
} else {
Message.error({message: error.message, showClose: true});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册