提交 68539540 authored 作者: chenshiying's avatar chenshiying

[新增] 部署脚本生成

上级 008e97d4
...@@ -10,6 +10,47 @@ ...@@ -10,6 +10,47 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>loit-build-deploy-env</artifactId> <artifactId>loit-build-deploy-env</artifactId>
<properties>
<!-- Maven Plugin Versions -->
<loit-core-boot.version>1.0.32</loit-core-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>com.loit</groupId>
<artifactId>loit-core-boot</artifactId>
<version>${loit-core-boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</exclusion>
<exclusion>
<groupId>com.loit</groupId>
<artifactId>loit-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
</dependencies>
</project> </project>
package com.loit.common.script;
import com.loit.common.script.dto.BackendFrontEnum;
import com.loit.common.script.dto.DeployInfoDataDTO;
import com.loit.common.utils.ListUtil;
import com.loit.common.utils.StringUtils;
import com.loit.common.utils.excel.ImportExcel;
import com.loit.common.utils.file.FileUtils;
import com.loit.common.utils.freemarker.FreeMarkerUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
public class GeneratorScript {
protected static String root_path = "F:\\9Git140\\loit-build-common\\loit-build-component\\loit-build-deploy-env\\src\\main\\resources\\bin";
protected static String root_path_full = root_path + "\\serverTest";
public static void main(String[] args) {
try {
//String filePathStr = Thread.currentThread().getContextClassLoader().getResource("deployInfo.xlsx").getPath();
String filePathStr = "F:\\9Git140\\loit-build-common\\loit-build-component\\loit-build-deploy-env\\src\\main\\resources\\deployInfo.xlsx";
File pdfFile = new File(filePathStr);
FileInputStream fileInputStream = new FileInputStream(pdfFile);
MultipartFile multipartFile = new MockMultipartFile(pdfFile.getName(), pdfFile.getName(),
ContentType.APPLICATION_OCTET_STREAM.toString(), fileInputStream);
ImportExcel ei = new ImportExcel(multipartFile, 1, 0);
List<DeployInfoDataDTO> grayVersionDataDTOList = ei.getDataList(DeployInfoDataDTO.class);
if (ListUtil.isEmpty(grayVersionDataDTOList)) {
return;
}
for (DeployInfoDataDTO grayVersionDataDTO : grayVersionDataDTOList) {
buildInitEnv(grayVersionDataDTO);
buildConfig(grayVersionDataDTO);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 生成InitEnv
*
* @throws IOException
*/
private static void buildInitEnv(DeployInfoDataDTO grayVersionDataDTO) throws IOException {
String fileDir = root_path_full + "\\" + grayVersionDataDTO.getFixedIp();
File folder = new File(fileDir);
if (!folder.exists()) {
folder.mkdirs();
}
String filePath = fileDir + "\\initEnv.sh";
File file = new File(filePath);
if (!file.exists()) {
FileUtils.write(filePath, "#!/bin/bash ");
}
String deployPath = grayVersionDataDTO.getDeployPath();
if (StringUtils.isNoneBlank(deployPath)) {
FileUtils.appendNewLine(filePath, "mkdir -p " + deployPath);
BackendFrontEnum backendFrontType = BackendFrontEnum.getEnumByCode(grayVersionDataDTO.getBackendFrontType());
if (BackendFrontEnum.BACKEND.equals(backendFrontType)) {
FileUtils.appendNewLine(filePath, "mkdir -p " + deployPath + "/logs");
}
}
}
/**
* 生成bootstrap.properties 及其 sh
*/
private static void buildConfig(DeployInfoDataDTO grayVersionDataDTO) throws IOException {
String serviceName = grayVersionDataDTO.getServiceName();
String fixedIp = grayVersionDataDTO.getFixedIp();
String port = grayVersionDataDTO.getPort();
String deployPath = grayVersionDataDTO.getDeployPath();
String deployJar = grayVersionDataDTO.getDeployJar();
if (StringUtils.isBlank(serviceName)) {
return;
}
BackendFrontEnum backendFrontType = BackendFrontEnum.getEnumByCode(grayVersionDataDTO.getBackendFrontType());
if (!BackendFrontEnum.BACKEND.equals(backendFrontType)) {
return;
}
String fileDir = root_path_full + "\\" + fixedIp + "\\" + serviceName + "-" + port;
File folder = new File(fileDir);
if (!folder.exists()) {
folder.mkdirs();
}
String bootstrapFileName = fileDir + "\\bootstrap.properties";
Map model = new HashMap();
model.put("port", port);
model.put("serviceName", serviceName);
model.put("deployPath", deployPath);
model.put("deployJar", deployJar);
String result = FreeMarkerUtils.process("bootstrap.ftl", model);
FileUtils.write(bootstrapFileName, result);
String replace = serviceName.replace("loit-", "deploy-");
String deployShFileName = fileDir + "\\" + replace + "-" + port + ".sh";
String deployShResult = FreeMarkerUtils.process("deploy-sh.ftl", model);
FileUtils.write(deployShFileName, deployShResult);
}
}
package com.loit.common.script.dto;
/**
* 前后端类型
*/
public enum BackendFrontEnum {
/**
* 后端
*/
BACKEND("backend", "后端"),
/**
* web 前端
*/
web("web", "前端"),
/**
* h5
*/
h5("h5", "h5");
private String code;
private String name;
private BackendFrontEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getName() {
return name;
}
public String getCode() {
return code;
}
/**
* 根据Code 获取枚举
*/
public static BackendFrontEnum getEnumByCode(String code) {
for (BackendFrontEnum bfEnum : BackendFrontEnum.values()) {
if (bfEnum.getCode().equals(code)) {
return bfEnum;
}
}
return null;
}
}
package com.loit.common.script.dto;
import com.loit.common.utils.excel.annotation.ExcelField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@ApiModel(value = "DeployInfoDataDTO", description = "部署信息DTO")
public class DeployInfoDataDTO implements Serializable {
private static final long serialVersionUID = -6587921299183759035L;
@ApiModelProperty(value = "固定IP")
@ExcelField(title = "固定IP", sort = 1)
private String fixedIp;
@ApiModelProperty(value = "浮动IP")
@ExcelField(title = "浮动IP", sort = 2)
private String floatingIp;
@ApiModelProperty(value = "部署路径")
@ExcelField(title = "deployPath", sort = 3)
private String deployPath;
@ApiModelProperty(value = "前后端类型")
@ExcelField(title = "前后端类型", sort = 4)
private String backendFrontType;
@ApiModelProperty(value = "模块服务名")
@ExcelField(title = "模块服务名", sort = 5)
private String serviceName;
@ApiModelProperty(value = "端口")
@ExcelField(title = "端口", sort = 6)
private String port;
@ApiModelProperty(value = "部署包名称")
@ExcelField(title = "部署包名称", sort = 6)
private String deployJar;
public String getFixedIp() {
return fixedIp;
}
public void setFixedIp(String fixedIp) {
this.fixedIp = fixedIp;
}
public String getFloatingIp() {
return floatingIp;
}
public void setFloatingIp(String floatingIp) {
this.floatingIp = floatingIp;
}
public String getDeployPath() {
return deployPath;
}
public void setDeployPath(String deployPath) {
this.deployPath = deployPath;
}
public String getBackendFrontType() {
return backendFrontType;
}
public void setBackendFrontType(String backendFrontType) {
this.backendFrontType = backendFrontType;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
public String getDeployJar() {
return deployJar;
}
public void setDeployJar(String deployJar) {
this.deployJar = deployJar;
}
}
/**
* Copyright &copy; 2015-2020 isaac All rights reserved.
*/
package com.loit.common.utils.excel;
import com.google.common.collect.Lists;
import com.loit.common.utils.Reflections;
import com.loit.common.utils.excel.annotation.ExcelField;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
/**
* 导入Excel文件(支持“XLS”和“XLSX”格式)
* @author loit
* @version 2013-03-10
*/
public class ImportExcel {
private static Logger log = LoggerFactory.getLogger(ImportExcel.class);
/**
* 工作薄对象
*/
private Workbook wb;
/**
* 工作表对象
*/
private Sheet sheet;
/**
* 标题行号
*/
private int headerNum;
/**
* 构造函数
* @param fileName 导入文件,读取第一个工作表
* @param headerNum 标题行号,数据行号=标题行号+1
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(String fileName, int headerNum)
throws InvalidFormatException, IOException {
this(new File(fileName), headerNum);
}
/**
* 构造函数
* @param file 导入文件对象,读取第一个工作表
* @param headerNum 标题行号,数据行号=标题行号+1
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(File file, int headerNum)
throws InvalidFormatException, IOException {
this(file, headerNum, 0);
}
/**
* 构造函数
* @param fileName 导入文件
* @param headerNum 标题行号,数据行号=标题行号+1
* @param sheetIndex 工作表编号
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(String fileName, int headerNum, int sheetIndex)
throws InvalidFormatException, IOException {
this(new File(fileName), headerNum, sheetIndex);
}
/**
* 构造函数
* @param file 导入文件对象
* @param headerNum 标题行号,数据行号=标题行号+1
* @param sheetIndex 工作表编号
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(File file, int headerNum, int sheetIndex)
throws InvalidFormatException, IOException {
this(file.getName(), new FileInputStream(file), headerNum, sheetIndex);
}
/**
* 构造函数
* @param multipartFile 导入文件对象
* @param headerNum 标题行号,数据行号=标题行号+1
* @param sheetIndex 工作表编号
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(MultipartFile multipartFile, int headerNum, int sheetIndex)
throws InvalidFormatException, IOException {
this(multipartFile.getOriginalFilename(), multipartFile.getInputStream(), headerNum, sheetIndex);
}
/**
* 构造函数
* @param fileName 导入文件对象
* @param headerNum 标题行号,数据行号=标题行号+1
* @param sheetIndex 工作表编号
* @throws InvalidFormatException
* @throws IOException
*/
public ImportExcel(String fileName, InputStream is, int headerNum, int sheetIndex)
throws InvalidFormatException, IOException {
if (StringUtils.isBlank(fileName)){
throw new RuntimeException("导入文档为空!");
}else if(fileName.toLowerCase().endsWith("xls")){
this.wb = new HSSFWorkbook(is);
}else if(fileName.toLowerCase().endsWith("xlsx")){
this.wb = new XSSFWorkbook(is);
}else{
throw new RuntimeException("文档格式不正确!");
}
if (this.wb.getNumberOfSheets()<sheetIndex){
throw new RuntimeException("文档中没有工作表!");
}
this.sheet = this.wb.getSheetAt(sheetIndex);
this.headerNum = headerNum;
log.debug("Initialize success.");
}
/**
* 获取行对象
* @param rownum
* @return
*/
public Row getRow(int rownum){
return this.sheet.getRow(rownum);
}
/**
* 获取数据行号
* @return
*/
public int getDataRowNum(){
return headerNum+1;
}
/**
* 获取最后一个数据行号
* @return
*/
public int getLastDataRowNum(){
return this.sheet.getLastRowNum()+headerNum;
}
/**
* 获取最后一个列号
* @return
*/
public int getLastCellNum(){
return this.getRow(headerNum).getLastCellNum();
}
/**
* 获取单元格值
* @param row 获取的行
* @param column 获取单元格列号
* @return 单元格值
*/
public Object getCellValue(Row row, int column) {
Object val = "";
try {
Cell cell = row.getCell(column);
if (cell != null) {
if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
// val = cell.getNumericCellValue();
// 当excel 中的数据为数值或日期是需要特殊处理
if (HSSFDateUtil.isCellDateFormatted(cell)) {
double d = cell.getNumericCellValue();
Date date = HSSFDateUtil.getJavaDate(d);
SimpleDateFormat dformat = new SimpleDateFormat(
"yyyy-MM-dd");
val = dformat.format(date);
} else {
cell.setCellType(Cell.CELL_TYPE_STRING);
val = cell.getStringCellValue();
}
} else if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
val = cell.getStringCellValue();
} else if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) {
val = cell.getCellFormula();
} else if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) {
val = cell.getBooleanCellValue();
} else if (cell.getCellType() == Cell.CELL_TYPE_ERROR) {
val = cell.getErrorCellValue();
}
}
} catch (Exception e) {
return val;
}
return val;
}
/**
* 获取导入数据列表
* @param cls 导入对象类型
* @param groups 导入分组
*/
public <E> List<E> getDataList(Class<E> cls, int... groups) throws InstantiationException, IllegalAccessException{
List<Object[]> annotationList = Lists.newArrayList();
// Get annotation field
Field[] fs = cls.getDeclaredFields();
for (Field f : fs){
ExcelField ef = f.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==2)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, f});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, f});
}
}
}
// Get annotation method
Method[] ms = cls.getDeclaredMethods();
for (Method m : ms){
ExcelField ef = m.getAnnotation(ExcelField.class);
if (ef != null && (ef.type()==0 || ef.type()==2)){
if (groups!=null && groups.length>0){
boolean inGroup = false;
for (int g : groups){
if (inGroup){
break;
}
for (int efg : ef.groups()){
if (g == efg){
inGroup = true;
annotationList.add(new Object[]{ef, m});
break;
}
}
}
}else{
annotationList.add(new Object[]{ef, m});
}
}
}
// Field sorting
Collections.sort(annotationList, new Comparator<Object[]>() {
public int compare(Object[] o1, Object[] o2) {
return new Integer(((ExcelField)o1[0]).sort()).compareTo(
new Integer(((ExcelField)o2[0]).sort()));
};
});
//log.debug("Import column count:"+annotationList.size());
// Get excel data
List<E> dataList = Lists.newArrayList();
for (int i = this.getDataRowNum(); i < this.getLastDataRowNum(); i++) {
E e = (E)cls.newInstance();
int column = 0;
Row row = this.getRow(i);
StringBuilder sb = new StringBuilder();
for (Object[] os : annotationList){
Object val = this.getCellValue(row, column++);
if (val != null){
ExcelField ef = (ExcelField)os[0];
// If is dict type, get dict value
if (StringUtils.isNotBlank(ef.dictType())){
/* val = DictDataUtils.getDictValue(val.toString(), ef.dictType(), ""); */
//log.debug("Dictionary type value: ["+i+","+colunm+"] " + val);
}
// Get param type and type cast
Class<?> valType = Class.class;
if (os[1] instanceof Field){
valType = ((Field)os[1]).getType();
}else if (os[1] instanceof Method){
Method method = ((Method)os[1]);
if ("get".equals(method.getName().substring(0, 3))){
valType = method.getReturnType();
}else if("set".equals(method.getName().substring(0, 3))){
valType = ((Method)os[1]).getParameterTypes()[0];
}
}
//log.debug("Import value type: ["+i+","+column+"] " + valType);
try {
//如果导入的java对象,需要在这里自己进行变换。
if (valType == String.class){
String s = String.valueOf(val.toString());
val = String.valueOf(val.toString());
}else if (valType == Integer.class){
val = Double.valueOf(val.toString()).intValue();
}else if (valType == Long.class){
val = Double.valueOf(val.toString()).longValue();
}else if (valType == Double.class){
val = Double.valueOf(val.toString());
}else if (valType == Float.class){
val = Float.valueOf(val.toString());
}else if (valType == Date.class){
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
val=sdf.parse(val.toString());
}else{
if (ef.fieldType() != Class.class){
val = ef.fieldType().getMethod("getValue", String.class).invoke(null, val.toString());
}else{
val = Class.forName(this.getClass().getName().replaceAll(this.getClass().getSimpleName(),
"fieldtype."+valType.getSimpleName()+"Type")).getMethod("getValue", String.class).invoke(null, val.toString());
}
}
} catch (Exception ex) {
log.info("Get cell value ["+i+","+column+"] error: " + ex.toString());
val = null;
}
// set entity value
if (os[1] instanceof Field){
Reflections.invokeSetter(e, ((Field)os[1]).getName(), val);
}else if (os[1] instanceof Method){
String mthodName = ((Method)os[1]).getName();
if ("get".equals(mthodName.substring(0, 3))){
mthodName = "set"+StringUtils.substringAfter(mthodName, "get");
}
Reflections.invokeMethod(e, mthodName, new Class[] {valType}, new Object[] {val});
}
}
sb.append(val+", ");
}
dataList.add(e);
log.debug("Read success: ["+i+"] "+sb.toString());
}
return dataList;
}
// /**
// * 导入测试
// */
// public static void main(String[] args) throws Throwable {
//
// ImportExcel ei = new ImportExcel("target/export.xlsx", 1);
//
// for (int i = ei.getDataRowNum(); i < ei.getLastDataRowNum(); i++) {
// Row row = ei.getRow(i);
// for (int j = 0; j < ei.getLastCellNum(); j++) {
// Object val = ei.getCellValue(row, j);
// System.out.print(val+", ");
// }
// System.out.print("\n");
// }
//
// }
}
package com.loit.common.utils.file;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
/**
* 文件追加工具类
*
* @author
*/
@Slf4j
public class FileUtils {
public static void write(String file, String conent) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
out.write(conent);
out.newLine();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
/**
* 使用 FileOutputStream
*/
public static void appendNewLine(String file, String conent) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
out.write(conent);
out.newLine();
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
public static void append(String file, String conent) {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
out.write(conent);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
package com.loit.common.utils.freemarker;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.StringWriter;
import java.util.Map;
@Slf4j
public class FreeMarkerUtils {
public static String process(String templateFileName, Map<String, ?> model) {
try {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setDirectoryForTemplateLoading(new File("F:\\9Git140\\loit-build-common\\loit-build-component\\loit-build-deploy-env\\src\\main\\resources\\template\\"));
cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_23));
Template temp = cfg.getTemplate(templateFileName);
StringWriter out = new StringWriter();
temp.process(model, out);
String result = out.toString();
return result;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return "";
}
}
spring.profiles.active=prod${port}
spring.application.name=${serviceName}
# Nacos \ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0135\ufffd\u05b7
spring.cloud.nacos.config.server-addr=10.0.120.221:8848
spring.cloud.nacos.config.namespace=05270cbf-5a81-4a23-a534-b59ba26f11d5
spring.cloud.nacos.config.file-extension=yaml
spring.main.allow-bean-definition-overriding=true
#!/bin/sh
echo "pramas:" $1
command=$(cat updowncommand.txt)
echo 'command:' $command
if [ -z "$1" ]; then
command='up'
echo 'command reset value:' $command
fi
echo '---------------kill.jar----------------'
KILL_PROCESS_NAME='${deployPath}/${deployJar}'
PROCESS_ID=`ps -ef | grep $KILL_PROCESS_NAME | grep -v 'grep' | awk '{print $2}'`
echo 'ProcessId: ' $PROCESS_ID
for id in $PROCESS_ID
do
echo 'KILL_ID: ' $id
kill -s 9 $id
done
echo '---------------killed.jar----------------'
if [ "$command" != "down" ]; then
echo '---------------start.jar----------------'
nohup /usr/local/java/jdk1.8/bin/java -javaagent:/usr/local/skywalking/agent/skywalking-agent.jar -Dskywalking.trace.ignore_path=/api/v1/rest/event/longpolling -Dskywalking.agent.service_name=${serviceName} -Dskywalking.collector.backend_service=10.0.120.212:11800,10.0.120.143:11800,10.0.120.44:11800 -Xms2g -Xmx2g -jar $KILL_PROCESS_NAME --spring.profiles.active=prod${port} >/dev/null 2>&1 &
echo '---------------started.jar----------------'
for i in {1..30};do
sleep 1
tail -n5 ${deployPath}/logs/${serviceName}.log
done
fi
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论