有些人可能插肩而过就再也见不到了
做了一个需求,我已经从数据库里跑出来了需要的数据放到excel里了,现在又要在区县的基础上细分到镇与街道,不想手动分了,写了个java程序来跑,毕竟不止是一个区县呢...之前写的Excel工具类也正好派上用场.import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.List; /** * 根据户籍地址分镇,街道 * @author 晴风 */ public class ExcelFenSheet { //读文件路径 public static final String wbName = "C:\\Users\\Clearwind\\Desktop\\xxxxx\\桐城市\\xxxxxxx.xlsx"; public static final String dataSheetName = "zjhmcx_temp3"; //写文件路径 public static final String outputPath = "C:\\Users\\Clearwind\\Desktop\\xxxxx\\桐城市"; //所有镇,街道 public static final String[] strings = { "鲟鱼", "孔城", "范岗", "青草", "金神", "唐湾", "双港", "嬉子湖", "新渡", "大关", "吕亭", "黄甲", "文昌", "龙眠", "龙腾" }; public static void main(String[] args) throws Exception{ //初始化输出文件 String outputFile = outputPath + "\\" + initOutputFile(); Workbook wb = ExcelUtil.getWorkBook(wbName); Workbook wbNew = ExcelUtil.getWorkBook(outputFile); //取sheet Sheet st = wb.getSheet(dataSheetName); //sheet内数据行数 int rowNumbs = st.getPhysicalNumberOfRows(); //遍历sheet for (int i = 1; i < rowNumbs; i++) { //初始化hjdz String hjdz = ""; //取第i行数据 List<String> line = ExcelUtil.getOneRowToList(st, i); System.out.println("正在处理第" + i + "行..."); //简单处理hjdz可能为空的问题 if (line.size() < 3){ hjdz = ""; } else { hjdz = line.get(2); } //分值写入 boolean isContain = false; for (String str : strings){ //找到匹配的值 if (hjdz.contains(str)) { isContain = true; //写数据到对应的sheet writeSheet(wbNew,line,str); } } //均不匹配,写到other sheet if (!isContain) { //System.out.println("什么也没找到..."); writeSheet(wbNew,line,"other"); } } //保存更改 try { wbNew.write(new FileOutputStream(outputFile)); } catch (Exception e) { e.printStackTrace(); } } public static void writeSheet(Workbook wb,List<String> data,String sheetName) { Sheet sheet = wb.getSheet(sheetName); //取得最后一行索引 int lastRowNum = sheet.getLastRowNum(); //加表头 if (lastRowNum == 0) { ArrayList<String> head = new ArrayList<>(); head.add("zjhm"); head.add("xm"); head.add("hjdz"); ExcelUtil.writeALine(sheet,head,0); } //写入一行 ExcelUtil.writeALine(sheet,data,++lastRowNum); System.out.println("writeSheetName:" + sheetName + ", rowIndex:" + lastRowNum); } public static String initOutputFile() throws Exception { //时间戳文件名 String fileName = "out_"+System.currentTimeMillis()+".xlsx"; File fileDir = new File(outputPath); if (fileDir.exists()) { //创建Workbook对象 Workbook wb = new XSSFWorkbook(); //遍历sheetName并创建 for (String sheetName : strings) { wb.createSheet(sheetName); } //用于保存未匹配到的记录 wb.createSheet("other"); //保存文件 wb.write(new FileOutputStream(outputPath + "\\" + fileName)); } else { throw new RuntimeException("输出文件夹不存在..."); } return fileName; } }
工作上有时要整理挺多excel的,好多工作是体力活,所以写了个java的excel工具类来辅助工作.更新...pom:<dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> </dependencies>ExcelUtil:package clearwind; import java.io.*; import java.util.*; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author 晴风 * * Workbook: excel表格对象,可包含多个Sheet * Sheet: 工作表对象,包含多个Row * Row: 行对象,包含多个Cell * Cell: 单元格对象,最小的对象,有多种单元格数据类型 */ public class ExcelUtil { /***索引 * */ private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class); public static final String XLS = ".xls"; public static final String XLSX = ".xlsx"; public static final String DEFAULT_FONT_NAME = "宋体"; public static final String CRLF = "\r\n"; /** * 预设的默认的样式 * 宋体 11号 有边框 * * @param workbook Workbook对象 * @param isTitle 是否是标题 * @return CellStyle */ public static CellStyle getDefaultCellStyle(Workbook workbook, boolean isTitle) { //创建样式 Font defaultFont = workbook.createFont(); CellStyle defaultCellStyle = workbook.createCellStyle(); //设置字体样式 defaultFont.setFontName(DEFAULT_FONT_NAME); // defaultFont.setFontHeight((short) 11); //这么写并非11号字体 //标题加粗 if (isTitle) { defaultFont.setBold(true); } //四边框 defaultCellStyle.setBorderBottom(BorderStyle.MEDIUM); defaultCellStyle.setBorderLeft(BorderStyle.MEDIUM); defaultCellStyle.setBorderRight(BorderStyle.MEDIUM); defaultCellStyle.setBorderTop(BorderStyle.MEDIUM); //应用字体 defaultCellStyle.setFont(defaultFont); return defaultCellStyle; } /** * 预设的默认的样式 * 宋体 11号 有边框 * * @param workbook Workbook对象 * @return CellStyle */ public static CellStyle getDefaultCellStyle(Workbook workbook) { return getDefaultCellStyle(workbook, false); } /** * 读取excel文件创建Workbook对象，过期，使用getWorkbook()代替 * * @param filePath 文件路径 * @return Workbook */ @Deprecated public static Workbook readExcel(String filePath) { Workbook wb = null; if (filePath == null) { return null; } //提取文件后缀名 String extString = filePath.substring(filePath.lastIndexOf(".")); InputStream is; try { //创建输入流 is = new FileInputStream(filePath); if (XLS.equals(extString)) { wb = new HSSFWorkbook(is); } else if (XLSX.equals(extString)) { wb = new XSSFWorkbook(is); } //关闭输入流 is.close(); } catch (IOException e) { e.printStackTrace(); } return wb; } /** * 创建一个xlsx文件 * * @return Workbook */ public static Workbook createWorkbook() { return new XSSFWorkbook(); } /** * 使用WorkbookFactory读取excel文件创建Workbook对象 * * @param filePath 文件路径 * @return Workbook */ public static Workbook getWorkbook(String filePath) { Workbook wb = null; InputStream is = null; if ((filePath.endsWith(XLSX) || filePath.endsWith(XLS)) && !filePath.startsWith("~")) { try { is = new FileInputStream(filePath); wb = WorkbookFactory.create(is); //测试:打印文件名 LOGGER.info("[getWorkbook] " + filePath); } catch (IOException | InvalidFormatException e) { e.printStackTrace(); } finally { //关闭输入流 try { assert is != null; is.close(); } catch (IOException e) { e.printStackTrace(); } } } else { LOGGER.info("[getWorkbook] "+ filePath +": 非xls或者xlsx文件!"); } return wb; } /** * 从文件对象加载Workbook对象 * * @param excelFile excel文件 * @return Workbook */ public static Workbook getWorkbook(File excelFile) throws IOException, InvalidFormatException { if (!excelFile.exists()){ return null; } String fileName = excelFile.getName(); //判断是否是xls或者xlsx文件 if (fileName.endsWith(XLSX) || fileName.endsWith(XLS)) { LOGGER.info("[getWorkbook] " + excelFile.getPath()); return WorkbookFactory.create(excelFile); } else { LOGGER.info("[getWorkbook] 非xls或者xlsx文件!"); return null; } } public static Sheet getSheet(String excelFilePath,String name) { return getWorkbook(excelFilePath).getSheet(name); } public static Sheet getSheetAt(String excelFilePath, int index) { return getWorkbook(excelFilePath).getSheetAt(index); } /** * 拿到所有Sheet * * @return List<Sheet> */ public static List<Sheet> getAllSheet(Workbook workbook) { return getSheetsWithRegex(workbook, "[\\s\\S]*"); } /** * 根据一个String[]数组拿到多个Sheet,数组内是Sheet名 * * @param workbook 表格对象 * @param sheetNames Sheet名称数组 * @return List<Sheet> */ public static List<Sheet> getSheetsWithArray(Workbook workbook, String[] sheetNames) { List<Sheet> sheets = new ArrayList<>(); if (sheetNames.length < 1) { return sheets; } //遍历传入的sheetNames数组 for (String sheetName : sheetNames) { //尝试根据名称拿到sheet Sheet sheet = workbook.getSheet(sheetName); if (sheet != null) { LOGGER.info("[getSheetsWithArray] 找到Sheet:" + sheetName); sheets.add(sheet); } else { LOGGER.warn("[getSheetsWithArray] 未找到Sheet:" + sheetName); } } return sheets; } /** * 拿到sheet名符合正则的sheet * 正则语法参考:https://www.runoob.com/regexp/regexp-syntax.html * * @param pattern 正则表达式 * @return List<Sheet> */ public static List<Sheet> getSheetsWithRegex(Workbook workbook, String pattern) { List<Sheet> sheets = new ArrayList<>(); //numberOfSheets int numberOfSheets = workbook.getNumberOfSheets(); for (int i = 0; i < numberOfSheets; i++) { Sheet sheet = workbook.getSheetAt(i); String sheetName = sheet.getSheetName(); //正则匹配的sheet添加到list if (sheetName.matches(pattern)) { sheets.add(sheet); } } LOGGER.info("[getSheetWithRegex] numberOfSheets:" + sheets.size()); return sheets; } /** * 取一个单元格的字符串值,空返回空字符串 * * @param row cell所在行 * @param columnIndex 列索引 * @return Cell value */ public static String getCellStringValue(Row row, int columnIndex) { Cell cell; if (row != null) { try { //取第h行，第l列的cell cell = row.getCell(columnIndex); //取数据 cell.setCellType(CellType.STRING); return cell.getStringCellValue(); } catch (Exception e) { e.printStackTrace(); } } return ""; } /** * 取一行拼接为String字符串 * * @param row Row对象 * @param delimiter 分隔符 * @return String */ public static String getOneRowToString(Row row, String delimiter) throws Exception { StringBuilder line = new StringBuilder(); //取得迭代器 if (row != null) { Iterator<Cell> cellIterator = row.cellIterator(); Cell cell; while (cellIterator.hasNext()) { cell = cellIterator.next(); //全部设置为字符串类型 cell.setCellType(CellType.STRING); //以分隔符分隔每一个值 //line += cell.getStringCellValue() + "\t"; line.append(cell.getStringCellValue()).append(delimiter); } //空行 if (line.length() < 1) { return ""; } //去除最后的分隔符 return line.substring(0, line.length() - 1); } else { LOGGER.info("[getOneRowToString] Row空对象"); return ""; } } /** * 取一行拼接为String字符串，以制表符分隔输出字符串 * * @param row Row对象 * @return String */ public static String getOneRowToString(Row row) throws Exception { return getOneRowToString(row, "\t"); } /** * 取一行 * * @param sheet Sheet对象 * @param rowIndex 行号 * @return Row */ public static Row getRow(Sheet sheet, int rowIndex) throws RuntimeException { if (sheet == null) { throw new RuntimeException("[getRow] sheet空对象!"); } if (rowIndex < 0) { throw new RuntimeException("[getRow] 行索引不能小0!"); } if (rowIndex > getLastRowNumIndex(sheet)) { throw new RuntimeException("[getRow] 行索引不能大于实际表最大索引!"); } return sheet.getRow(rowIndex); } /** * 取范围内行 * * @param sRowIndex 开始索引 * @param eRowIndex 结束索引 * @return List<Row> */ public static List<Row> getRow(Sheet sheet, int sRowIndex, int eRowIndex) throws RuntimeException { if (sheet == null) { throw new RuntimeException("[getRow] sheet空对象!"); } if (sRowIndex < 0 || eRowIndex < sRowIndex) { throw new RuntimeException("[getRow] 索引错误!"); } int lastRowNum = getLastRowNumIndex(sheet); if (sRowIndex > lastRowNum || eRowIndex > lastRowNum) { throw new RuntimeException("[getRow] 行索引不能大于实际表最大索引!"); } //拿row ArrayList<Row> rows = new ArrayList<>(); for (int i = sRowIndex; i < eRowIndex + 1; i++) { rows.add(sheet.getRow(i)); } return rows; } /** * Row转为List<String> * * @param row Row对象 * @param setRowLength 设置Row长度(非索引),当为-1时不设置 * @return List<String> */ public static List<String> rowToList(Row row,short setRowLength) { short setLastCellNumIndex = (short) (setRowLength-1); List<String> dataList = new ArrayList<>(); //标记是否有数据(有些excel会有很多空行) boolean haveData = false; short lastCellNum; //取得列内真实有数据的单元格个数 lastCellNum = getLastCellNumIndex(row); //固定Row长度 if (setRowLength!=-1 && setLastCellNumIndex!=lastCellNum) { lastCellNum=setLastCellNumIndex; } //自定义方法getLastCellNumIndex会返回索引,所以改为<= Cell cell; for (int i = 0; i <= lastCellNum; i++) { cell = row.getCell(i); if (cell == null) { cell = row.createCell(i); } //类型置为String,不去判断是什么类型的了 cell.setCellType(CellType.STRING); //取数据添加到list String stringCellValue = cell.getStringCellValue(); if (!"".equals(stringCellValue)) { haveData = true; } dataList.add(stringCellValue); } //确实至少有一列有数据才返回 if (!haveData) { Sheet sheet = row.getSheet(); int rowNum = row.getRowNum(); Row row1 = sheet.getRow(0); System.out.println(rowToList(row1)); LOGGER.warn("[rowToList] "+sheet.getSheetName()+":"+rowNum+",无数据!返回null"); return null; } return dataList; } /** * Row转为List<String>,默认不去除右空格 * @param row Row对象 * @return List<String> */ public static List<String> rowToList(Row row) { //不固定列长度 return rowToList(row, (short) -1); } /** * Row转为List<String> * * @param rowList 多行 * @return List<List < String>> */ public static List<List<String>> rowToList(List<Row> rowList, short setRowLength) { List<List<String>> dataList = null; if (rowList.size() == 0) { return dataList; } try { dataList = new ArrayList<>(); for (Row row : rowList) { List<String> rowData = rowToList(row,setRowLength); if (rowData != null) { dataList.add(rowData); } } } catch (Exception e) { e.printStackTrace(); } return dataList; } public static List<List<String>> rowToList(List<Row> rowList) { return rowToList(rowList, (short) -1); } /** * 控制台打印excel */ public static void printSheet(Sheet sheet) throws Exception { try { //总行数 int lastRowNum = getLastRowNumIndex(sheet); //打印 for (int i = 0; i <= lastRowNum; i++) { LOGGER.info(getOneRowToString(getRow(sheet, i))); } } catch (NullPointerException e) { LOGGER.warn("[printSheet] Sheet对象为NULL."); } } /** * 写一行 * * @param row 行对象 * @param rowIndex 写excel的第几行 * @param cellStyle 单元格样式 * @return Row */ public static Row writeALine(Sheet sheet, Row row, int rowIndex, CellStyle cellStyle) { return writeALine(sheet, rowToList(row), rowIndex, cellStyle); } public static Row writeALine(Sheet sheet, Row row, int rowIndex) { return writeALine(sheet, row, rowIndex, sheet.getWorkbook().createCellStyle()); } /** * 写一行 * * @param data 数据 * @param rowIndex 写excel的第几行 * @param cellStyle 单元格样式 * @return Row */ public static Row writeALine(Sheet sheet, List<String> data, int rowIndex, CellStyle cellStyle) { Row row = null; try { //尝试取得需要写入的行 row = sheet.getRow(rowIndex); if (row == null) { //还不存在,新建行 row = sheet.createRow(rowIndex); } //取得行的单元格,并写数据 Cell cell; if (data == null) { LOGGER.warn("无数据,返回空行"); return row; } for (int i = 0; i < data.size(); i++) { //从yIndex列开始写 cell = row.createCell(i); //全部设置数据类型为文本 cell.setCellType(CellType.STRING); cell.setCellValue(data.get(i)); //设置样式 cell.setCellStyle(cellStyle); } } catch (Exception e) { e.printStackTrace(); } return row; } /** * 写一行,无样式 * * @param data 数据 * @param rowIndex 写excel的第几行 * @return Row */ public static Row writeALine(Sheet sheet, List<String> data, int rowIndex) { return writeALine(sheet, data, rowIndex, sheet.getWorkbook().createCellStyle()); } /** * 写一列 * * @param sheet Sheet对象 * @param data 数据 * @param startRowIndex 从excel第几行开始写 * @param colIndex 写excel的第几列 */ public static void writeAColumn(Sheet sheet, List<String> data, int startRowIndex, int colIndex) { try { if (sheet == null) { throw new RuntimeException("Sheet is null!"); } if (data.size() == 0) { throw new RuntimeException("Data is empty!"); } if (startRowIndex < 0 || colIndex < 0) { throw new RuntimeException("StartRowIndex and colIndex needs to be greater than 1!"); } //开始写 for (int i = 0; i < data.size(); i++) { LOGGER.info("写入第" + i + "行..."); Row row = sheet.getRow(i); if (row == null) { sheet.createRow(startRowIndex + i).createCell(colIndex).setCellValue(data.get(i)); } else { row.createCell(colIndex).setCellValue(data.get(i)); } } } catch (Exception e) { e.printStackTrace(); } } /** * 写键值对类型的数据 * * @param sheet sheet * @param data 数据，map类型 * @param startRowIndex 从excel第几行开始写 * @param colIndex1 key写在excel第几列 * @param colIndex2 value写在excel第几列 */ public static void writeMapTypeData(Sheet sheet, Map<String, String> data, int startRowIndex, int colIndex1, int colIndex2) { try { Row row; if (sheet == null) { throw new RuntimeException("Sheet is null!"); } if (data.size() == 0) { throw new RuntimeException("Data is empty!"); } if (startRowIndex < 0 || colIndex1 < 0 || colIndex2 < 0) { throw new RuntimeException("StartRowIndex and colIndex needs to be greater than 1!"); } if (colIndex1 == colIndex2) { throw new RuntimeException("ColIndex1 is equal to colIndex2!"); } //将Map转为list List<Map.Entry<String, String>> dataList = new ArrayList<>(data.entrySet()); for (int i = 0; i < data.size(); i++) { row = sheet.createRow(startRowIndex + i); row.createCell(colIndex1).setCellValue(dataList.get(i).getKey()); row.createCell(colIndex2).setCellValue(dataList.get(i).getValue()); } } catch (Exception e) { e.printStackTrace(); } } @SuppressWarnings({"rawtypes", "unchecked"}) public static void printCollect(Object objs) { if (objs instanceof Map) { Map maps = (Map) objs; Set<Map.Entry> set = maps.entrySet(); for (Map.Entry entry : set) { LOGGER.info(entry.getKey() + " -> " + entry.getValue()); } } else if (objs instanceof Collection) { for (Object obj : (Collection) objs) { LOGGER.info(obj.toString()); } } else if (objs.getClass().isArray()) { // List<Object> objects = Arrays.asList(objs); // printCollect(objects); LOGGER.warn("未实现数组打印...,有空研究..."); } else { LOGGER.info(objs + "不是 Collection 或者 Map 类型!!"); } } /** * 拿到表最后一行索引 * @param sheet Sheet对象 * @return lastRowNum */ public static int getLastRowNumIndex(Sheet sheet){ int lastRowNum = sheet.getLastRowNum(); //修正 //sheet.getLastRowNum(); 取得的是索引,已验证 for (int i = lastRowNum; i >= 0; i--) { Row row; try { row = sheet.getRow(i); } catch (NullPointerException npe) { //若row为空 //跳过,进行下一次循环 continue; } List<String> strings = rowToList(row); //从最后一行开始校验,遇到非空行则跳出 if (strings == null) { lastRowNum--; } else { break; } } return lastRowNum; } /** * 拿到行的最后一列索引 * @param row Row对象 * @return lastCellNum */ public static short getLastCellNumIndex(Row row) throws IllegalStateException{ short lastCellNum = 0; lastCellNum = row.getLastCellNum(); //修正 for (int i = lastCellNum - 1; i >= 0; i--) { //从最后一列开始校验,遇到非空列则跳出 Cell cell = row.getCell(i); if (cell == null) { lastCellNum--; } else { String stringCellValue; // stringCellValue = cell.getStringCellValue(); try { stringCellValue = cell.getStringCellValue(); } catch (IllegalStateException ie) { Sheet sheet = row.getSheet(); System.out.println("错误:"+sheet.getSheetName()+","+row.getRowNum()); System.out.println(i); // throw ie; // stringCellValue=""; stringCellValue=cell.getNumericCellValue()+""; } if ("".equals(stringCellValue.trim())) { lastCellNum--; } else { break; } } } //row.getLastCellNum()拿到的是列数而非索引,所以做-1处理 return (short)(lastCellNum-1); } /** * 取得一个sheet内的所有行，存到List内,中间空行会被屏蔽 * * @param sheet Sheet对象 * @return List<List < String>> */ public static List<List<String>> getSheetAllRowsToList(Sheet sheet) throws Exception { List<List<String>> rowsList = new ArrayList<>(); //sheet总行数 int lastRowNum = getLastRowNumIndex(sheet); //打印详情:打印SheetName LOGGER.info("[getSheetAllRowsToList] SheetName:" + sheet.getSheetName() + ",lastRowNum:" + lastRowNum); if (lastRowNum > 0) { Row row; //遍历sheet for (int i = 0; i <= lastRowNum; i++) { //拿到一行 row = ExcelUtil.getRow(sheet, i); //中间空行判断 String rowString = ExcelUtil.getOneRowToString(row, ""); if (!"".equals(rowString)) { //拿到一行的list rowsList.add(ExcelUtil.rowToList(row)); } } } return rowsList; } /** * 不能与write()同名,否则java编译时类型擦除导致两个方法的声明一样,编译不通过 */ public static Sheet writeRow(Sheet sheet, List<Row> data, int startIndex, CellStyle cellStyle) throws Exception { return write(sheet, rowToList(data), startIndex, cellStyle); } public static Sheet writeRow(Sheet sheet, List<Row> data, int startIndex) throws Exception { return writeRow(sheet, data, startIndex, sheet.getWorkbook().createCellStyle()); } /** * 多行写入到sheet内 * * @param data 写入的数据 * @param startIndex 从第几行开始写入 */ public static Sheet write(Sheet sheet, List<List<String>> data, int startIndex, CellStyle cellStyle) throws Exception { //遍历数据写入 for (List<String> oneRow : data) { writeALine(sheet, oneRow, startIndex++, cellStyle); } return sheet; } public static Sheet write(Sheet sheet, List<List<String>> data, int startIndex) throws Exception { return write(sheet, data, startIndex, sheet.getWorkbook().createCellStyle()); } /** * 通过sheetName取sheet,不存在则创建 * * @param wb Workbook对象 * @param sheetName sheet名 * @return Sheet */ public static Sheet getOrCreateSheet(Workbook wb, String sheetName) { Sheet sheet = null; try { sheet = wb.getSheet(sheetName); if (sheet == null) { sheet = wb.createSheet(sheetName); } } catch (Exception e) { e.printStackTrace(); } return sheet; } /** * 遍历文件夹 * * @param folderPath 文件夹路径 * @return List<File> */ public static List<File> fileList(String folderPath) { //文件夹 File folder = new File(folderPath); File[] files = folder.listFiles(); //存文件对象 ArrayList<File> fileList = new ArrayList<>(); if (files != null) { //递归遍历文件夹内文件 for (File f : files) { if (f.isDirectory()) { //递归 fileList.addAll(fileList(f.getAbsolutePath())); } if (f.isFile()) { fileList.add(f); } } } LOGGER.info("[fileList] 共" + fileList.size() + "个文件."); return fileList; } /** * 合并多个`表头一致`的sheet,返回新的Workbook对象 * * @return Workbook */ public static Workbook sheetsMerge(List<Sheet> sheets, int titleIndex) { return sheetsMerge(sheets, titleIndex, createWorkbook(), "merge"); } /** * 合并多个`表头一致`的sheet,写入到指定wb * * @param sheets 合并文件列表 * @param titleIndex 表头最大索引 * @param workbook 写入到的wb * @param mergeSheetName 写入数据的新sheet名 * @return Workbook */ public static Workbook sheetsMerge(List<Sheet> sheets, int titleIndex, Workbook workbook, String mergeSheetName) { try { if (sheets == null || sheets.size() < 1) { return null; } //创建sheet Sheet mergeSheet = workbook.createSheet(mergeSheetName); //拿第一个sheet的表头,写到mergeSheet List<Row> titleRows = getRow(sheets.get(0), 0, titleIndex); for (int i = 0; i < titleRows.size(); i++) { writeALine(mergeSheet, rowToList(titleRows.get(i)), i, getDefaultCellStyle(workbook, true)); } //遍历sheets,拿数据写到mergeSheet for (int i = 0; i < sheets.size(); i++) { String logMSG = "[sheetsMerge] 合并第" + (i + 1) + "个sheet"; LOGGER.info(logMSG); Sheet sheet = sheets.get(i); int lastRowNum = getLastRowNumIndex(sheet); List<Row> dataRows; //有数据才执行 if (lastRowNum > titleIndex) { //拿数据 dataRows = getRow(sheet, titleIndex + 1, lastRowNum); //写数据 for (Row dataRow : dataRows) { List<String> strings = rowToList(dataRow); //保险,非空才写,rowToList全空列会返回null if (strings != null) { writeALine(mergeSheet, strings, getLastRowNumIndex(mergeSheet) + 1, getDefaultCellStyle(workbook)); } } } else { logMSG = "[sheetsMerge] 第" + (i + 1) + "个sheet无数据"+"("+sheet.getSheetName()+")"; LOGGER.info(logMSG); } } } catch (Exception e) { e.printStackTrace(); } return workbook; } /** * 依据某一列,拆分一个sheet到多个sheet,返回新的Workbook对象 * * @return Workbook */ public static Workbook breakdownIntoSheets(Sheet sheet, int titleIndex, int basisIndex) throws Exception { return breakdownIntoSheets(sheet, titleIndex, basisIndex, createWorkbook()); } /** * 依据某一列,拆分一个sheet到多个sheet,返回新的Workbook对象 * * @param sourceSheet 需要拆分的sheet * @param titleIndex 表头最大索引 * @param basisIndex 依据列索引,依据列请勿带特殊字符 * @param workbook 写入到的 workbook * @return Workbook */ public static Workbook breakdownIntoSheets(Sheet sourceSheet, int titleIndex, int basisIndex, Workbook workbook) throws Exception { LOGGER.info("[breakdownIntoSheets] 拆分:"+sourceSheet.getSheetName()+"\t依据列:"+basisIndex); //取得最后一行索引 int lastRowNumIndex = getLastRowNumIndex(sourceSheet); //取表头 List<Row> titleRow = getRow(sourceSheet, 0, titleIndex); //拿到数据行 List<Row> dataRowList = getRow(sourceSheet, titleIndex + 1, lastRowNumIndex); //数据行转list List<List<String>> rowToLists = rowToList(dataRowList); //封装的各个sheet数据 Map<String, List<List<String>>> sheetsMap = new LinkedHashMap<String, List<List<String>>>(); //遍历数据行,依照依据列提取各分sheet行到Map for (List<String> rowList : rowToLists) { //依据列的值 String basisValue = rowList.get(basisIndex); //sheetsMap内查找,不存在则创建 List<List<String>> lists = sheetsMap.computeIfAbsent(basisValue, k -> new ArrayList<List<String>>()); //把当前行加到map里 lists.add(rowList); } //上面for结束表示已遍历完数据,下面开始按照分好的数据建sheet,并把数据写进去 Set<Map.Entry<String, List<List<String>>>> entries = sheetsMap.entrySet(); for (Map.Entry<String, List<List<String>>> entry : entries) { String basisValue = entry.getKey(); //使用basisValue建sheet Sheet basisValueSheet = workbook.createSheet(basisValue); LOGGER.info("[breakdownIntoSheets] 新Sheet:" + basisValue + CRLF); //写数据到这个sheet里,表头 writeRow(basisValueSheet,titleRow,0); //写数据到这个sheet里,数据 write(basisValueSheet,entry.getValue(),titleIndex+1); } LOGGER.info("[breakdownIntoSheets] 拆分出Sheet数:" + sheetsMap.size() + CRLF); return workbook; } /** * 列转行 * * ID F1 F2 * 1 a A,B * -->> * ID F1 F2 * 1 a A * 1 a B * * @param sheet 存数据的sheet * @param titleIndex 表头最大索引 * @param splitIndex 拆分列 * @param delimiter 分隔符,注意:分隔符是正则中的特殊字符时,需要带转义 * @param newWorkbook 合并结果存放的wb * @return 合并结果存放的wb */ public static Workbook columnsToRows(Sheet sheet, int titleIndex, int splitIndex, String delimiter, Workbook newWorkbook, String newSheetName) throws Exception { //拿到sheet最大行数 int lastRowNum = getLastRowNumIndex(sheet); //拿到表头 List<Row> titleRows = getRow(sheet, 0, titleIndex); //拿到表头的最后一行 Row titleMaxRow = titleRows.get(titleIndex); //根据表头最后一行得到每行长度 short lastCellNumIndex = getLastCellNumIndex(titleMaxRow); //表头转List List<List<String>> titleRowsList = ExcelUtil.rowToList(titleRows); //表头添加一列 titleRowsList.get(titleIndex).add("原始位置"); //拿到数据 List<Row> dataRows = getRow(sheet, titleIndex + 1, lastRowNum); //创建新sheet存放合并后的数据 Sheet newSheet = newWorkbook.createSheet(newSheetName); //新数据Collect 表<行<单元格>> ArrayList<List<String>> lists = new ArrayList<>(); //记录数据行索引,方便找到报错行 int dataIndex = titleIndex+1; //遍历数据 row每一行 for (Row row : dataRows) { //Row转List,固定宽度 List<String> strings = rowToList(row, (short) (lastCellNumIndex+1)); assert strings != null; String splitStringValue; try { splitStringValue=strings.get(splitIndex); } catch (IndexOutOfBoundsException iobe) { LOGGER.warn("Sheet:" + sheet.getSheetName() + ",行:" + dataIndex+1 + ",拆分列索引越界"); LOGGER.warn(strings.toString()); //这种情况一般为拆分列无数据,且后面的列也没有数据,这里给处理了 splitStringValue=""; // throw iobe; } //去前后空格 splitStringValue = splitStringValue.trim(); //尝试拆分 String[] splits = splitStringValue.split(delimiter); //拆分列直接使用拆分出来的值替换原来的值 for (String split : splits) { //复制一个新的list,并替换列的值 ArrayList<String> strings_new = new ArrayList<>(strings); //添加一列:原始位置 strings_new.add((dataIndex+1)+""); boolean b = Collections.replaceAll(strings_new, splitStringValue, split); lists.add(strings_new); } //更新数据行索引 dataIndex++; } // printCollect(lists); //存储到newSheet内 //写表头 write(newSheet, titleRowsList, 0,getDefaultCellStyle(newSheet.getWorkbook(),true)); //写数据 write(newSheet, lists, getLastRowNumIndex(newSheet) + 1,getDefaultCellStyle(newSheet.getWorkbook())); return newWorkbook; } public static Workbook columnsToRows(Sheet sheet, int titleIndex, int splitIndex, String delimiter) throws Exception { //默认 Workbook newWorkbook = sheet.getWorkbook(); String newSheetName="columnsToRows_" + String.valueOf(System.currentTimeMillis()).substring(8); return columnsToRows(sheet, titleIndex, splitIndex, delimiter, newWorkbook, newSheetName); } /** * 写到xlsx文件 */ public static void workbookWrite(Workbook workbook, String pathName) { try { workbook.write(new FileOutputStream(pathName)); } catch (Exception e) { e.printStackTrace(); } } /** * 合并多个excel文件的某个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件 * * @param pathName 输入和输出文件文件夹的地址 * @param sheetIndex 需要合并的sheetIndex */ public static void workbookMerge(String pathName, int sheetIndex, int titleIndex) throws IOException, InvalidFormatException { workbookMerge(pathName, pathName,createWorkbook(), sheetIndex, titleIndex); } /** * 合并多个excel文件的某个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件 * * @param inPath 输入文件文件夹的地址 * @param outPath 输出文件文件夹的地址 * @param newWorkbook 输出到Workbook * @param sheetIndex 需要合并的sheetIndex * @return Workbook */ public static Workbook workbookMerge(String inPath, String outPath, Workbook newWorkbook, int sheetIndex, int titleIndex) throws IOException, InvalidFormatException { //取得需要合并的文件列表 List<File> files = fileList(inPath); //拿到所有需要合并的sheet ArrayList<Sheet> sheets = new ArrayList<>(); for (File file : files) { String fileName = file.getName(); if (fileName.endsWith(XLSX) || fileName.endsWith(XLS)) { Workbook workbook = getWorkbook(file); assert workbook != null; Sheet sheetAt = workbook.getSheetAt(sheetIndex); sheets.add(sheetAt); } } if (sheets.size() != 0) { //合并sheet newWorkbook = sheetsMerge(sheets, titleIndex,newWorkbook,sheets.get(0).getSheetName()); //写出 workbookWrite(newWorkbook, outPath + "\\合并" + XLSX); } else { LOGGER.info("[workbookMerge] 空Sheets,检查文件夹路径是否有误!"); } return newWorkbook; } /** * 合并多个excel文件的多个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件 * * @param inPath 输入文件文件夹的地址 * @param outPath 输出文件文件夹的地址 * @param sheet_title 需要合并的sheetIndex->titleIndex * @return Workbook */ public static Workbook workbookMerge(String inPath, String outPath, Map<Integer,Integer> sheet_title) throws IOException, InvalidFormatException { Workbook newWorkbook = createWorkbook(); //解析map Set<Map.Entry<Integer, Integer>> entries = sheet_title.entrySet(); //遍历map for (Map.Entry<Integer, Integer> entry : entries) { Integer sheetIndex = entry.getKey(); Integer titleIndex = entry.getValue(); // 调用合并 newWorkbook = workbookMerge(inPath, outPath, newWorkbook, sheetIndex, titleIndex); } return newWorkbook; } /** * 根据一个String[]数组删除多个Sheet,数组内是Sheet名 * * @param workbook 表格对象 * @param sheetNames Sheet名称数组 * @return boolean(全部找到并删除则返回true) */ public static boolean delSheetWithArray(Workbook workbook, String[] sheetNames) { LOGGER.info("[delSheetWithArray] " + workbook); boolean flag = true; if (sheetNames.length < 1) { return flag; } //遍历sheetNames for (String sheetName : sheetNames) { //尝试通过名称拿到Sheet索引 int sheetIndex = workbook.getSheetIndex(sheetName); if (sheetIndex != -1) { workbook.removeSheetAt(sheetIndex); LOGGER.info("[delSheetWithArray] 删除Sheet:" + sheetName); } else { //只要一个未找到则返回false flag = false; LOGGER.info("[delSheetWithArray] 未找到Sheet:" + sheetName); } } return flag; } /** * 创建sheet,并设置表头 * * @param workbook 工作簿 * @param sheetName Sheet名 * @param title 表头,可多行 * @return Sheet */ public static Sheet createSheet(Workbook workbook, String sheetName, List<List<String>> title) { Sheet sheet = null; try { //创建新的sheet sheet = workbook.createSheet(sheetName); //拿到默认表头样式 CellStyle defaultCellStyle = getDefaultCellStyle(workbook, true); //写表头 for (int i = 0; i < title.size(); i++) { writeALine(sheet, sheet.getRow(i), i, defaultCellStyle); } } catch (IllegalArgumentException e) { String logMsg = "[createSheet] Sheet'" + sheetName + "'已存在,跳过"; LOGGER.warn(logMsg); } return sheet; } /** * 创建sheet,并设置表头 * * @param workbook 工作簿 * @param sheetName Sheet名 * @param titleRow 表头,一行 * @return Sheet */ public static Sheet createSheet(Workbook workbook, String sheetName, Row titleRow) { ArrayList<List<String>> title = new ArrayList<>(); title.add(rowToList(titleRow)); return createSheet(workbook, sheetName, title); } /** * 同时设置Sheet多列列宽 * * @param sheet Sheet对象 * @param widths 变长参数,每一个参数设置一列的列宽 */ public static void setColumnWidth(Sheet sheet, int... widths) { //根据变长参数的个数和顺序从第一列开始设置每一列的列宽 for (int i = 0; i < widths.length; i++) { //宽计算公式,较接近实际宽度 int width = 256 * widths[i] + 154; sheet.setColumnWidth(i, width); } } public static void setColumnWidthWithMultipleSheets(List<Sheet> sheets, int... widths) { //遍历每一个sheet for (Sheet sheet : sheets) { setColumnWidth(sheet, widths); } } /** * 设置Sheet为默认样式 * (sheet.getLastRowNum拿到的是之后一行索引,row.getLastCellNum()拿到的是单元格个数.) * * @param sheet Sheet对象 * @param titleIndex 表头最后索引 */ public static void setAsDefaultCellStyle(Sheet sheet, int titleIndex) { LOGGER.info("[setAsDefaultCellStyle] sheetName:" + sheet.getSheetName()); //取得默认样式 CellStyle defaultTitleCellStyle = getDefaultCellStyle(sheet.getWorkbook(), true); CellStyle defaultCellStyle = getDefaultCellStyle(sheet.getWorkbook()); //依据表头最后一列取得列数 Row row = getRow(sheet, titleIndex); short lastCellNum = getLastCellNumIndex(row); //设置表头样式 List<Cell> titleCells = getCellInArea(sheet, titleIndex, lastCellNum); for (Cell cell : titleCells) { cell.setCellStyle(defaultTitleCellStyle); } //拿到表最大行索引 int lastRowNum = getLastRowNumIndex(sheet); //拿到数据cell,并设置样式 List<Cell> dataCells = getCellInArea(sheet, titleIndex + 1, 0, lastRowNum, lastCellNum); for (Cell cell : dataCells) { cell.setCellStyle(defaultCellStyle); } } /** * 设置Sheet为默认样式 * * @param sheets 多个Sheet对象 * @param titleIndex 表头最后索引 */ public static void setAsDefaultCellStyle(List<Sheet> sheets, int titleIndex) { for (Sheet sheet : sheets) { setAsDefaultCellStyle(sheet, titleIndex); } } /** * 拿到指定区域内的cell * * @param sheet Sheet对象 * @param indexTLX 左上X索引 * @param indexTLY 左上Y索引 * @param indexBRX 右下X索引 * @param indexBRY 右下Y索引 * @return List<Cell> */ public static List<Cell> getCellInArea(Sheet sheet, int indexTLX, int indexTLY, int indexBRX, int indexBRY) { LOGGER.info("[getCellInArea] 区域[" + indexTLX + "," + indexTLY + ":" + indexBRX + "," + indexBRY + "]"); ArrayList<Cell> cells = new ArrayList<>(); //遍历指定区域 for (int i = indexTLX; i <= indexBRX; i++) { //尝试获取行 Row row = sheet.getRow(i); //不存在则创建 if (row == null) { row = sheet.createRow(i); } for (int j = indexTLY; j <= indexBRY; j++) { //尝试获取列 assert row != null; Cell cell = row.getCell(j); //不存在则创建 if (cell == null) { cell = row.createCell(j); } //添加到list cells.add(cell); } } LOGGER.info("[getCellInArea] cell个数:" + cells.size()); return cells; } /** * 拿到指定区域内的cell,从左上角开始计算 * * @param sheet Sheet对象 * @param indexBRX 右下X索引 * @param indexBRY 右下Y索引 * @return List<Cell> */ public static List<Cell> getCellInArea(Sheet sheet, int indexBRX, int indexBRY) { return getCellInArea(sheet, 0, 0, indexBRX, indexBRY); } /** * 列转行 * ID F1 F2 * 1 a A * 1 a B * -->> * ID F1 F2 * 1 a A,B * * @param sheet * @param titleIndex * @param basisIndex * @param mergeIndex * @param separator * @return * @throws Exception */ public static Sheet rowsToColumn(Sheet sheet,int titleIndex,int basisIndex,int mergeIndex,String separator) throws Exception { //存储依据列和合并列 HashMap<String, List<String>> basis_merge = new HashMap<>(); //合并之后数据存放的sheet Sheet newSheet = sheet.getWorkbook().createSheet(sheet.getSheetName() + "_列转行"); CellStyle defaultCellStyle = getDefaultCellStyle(newSheet.getWorkbook()); HashMap<String, List<String>> newSheetData = new HashMap<>(); //写表头到newSheet writeRow(newSheet,getRow(sheet,0,titleIndex),0,defaultCellStyle); //遍历来源sheet List<List<String>> sheetAllRowsToList = getSheetAllRowsToList(sheet); for (int i=titleIndex+1;i<sheetAllRowsToList.size();i++){ //拿到一行 List<String> rowList = sheetAllRowsToList.get(i); //取出依据列和和并列 String key = rowList.get(basisIndex); String value = rowList.get(mergeIndex); //尝试在map里找依据列 List<String> values = basis_merge.get(key); // stringListHashMap.computeIfAbsent(s, //// k-> { //// ArrayList<String> strings1 = new ArrayList<>(); //// strings1.add(st); //// return strings1; //// } //// ); //没找到则创建 if (values==null){ values=new ArrayList<String>(); //只放一行到新sheet数据的map内 newSheetData.put(key, rowList); } //把值暂时写到list里 values.add(value); //加到map里,key是`依据列`,值是`合并列` basis_merge.put(key, values); } //遍历合并好的结果,替换写到新sheet数据的map内 Set<Map.Entry<String, List<String>>> entries = basis_merge.entrySet(); for (Map.Entry<String, List<String>> entry:entries) { List<String> rowList = newSheetData.get(entry.getKey()); //替换掉原始值 // rowList.remove(mergeIndex); rowList.set(mergeIndex, StringUtils.join(entry.getValue().toArray(), separator)); //写数据到sheet writeALine(newSheet,rowList,getLastRowNumIndex(newSheet)+1,defaultCellStyle); } return newSheet; } /** * 测试 */ public static void main(String[] args) { System.out.println("123"); } }
原因是这样的：今天想给服务器设置一个快照，关机、拍摄快照、拍摄完成、开机，ssh无法连接。。。使用救援链接登录发现网卡挂了，没起来journalctl -ex原因是什么没搞懂啊。解决方法：禁用重命名修改/etc/default/grub 把GRUB_CMDLINE_LINUX_DEFAULT="quiet" 修改为GRUB_CMDLINE_LINUX_DEFAULT="net.ifnames=0" update-grub reboot解决！
修改时区：timedatectl list-timezones # 查看所有时区 timedatectl set-timezone Asia/Shanghai # 设置时为亚洲/上海 date -R 修改语言：locale -a # 查看本机安装的语言 dpkg-reconfigure locales # 上下键滑动，空格键选择语言包，回车确认 设置语言，选择上 [*] zh_CN.UTF-8 UTF-8，确认，选择zh_CN.UTF-8为默认区域设置，确认 重启系统即可 安装输入fcitx法：apt install fcitx -y
一、准备1）Debian9 最小化安装的机子一台2）服务商可以设置快照的话尽量设置一个快照二、安装桌面使用ssh连接工具登录拥有管理员权限的账户1）安装基础版gnomeapt -y install x-window-system-core gnome-core 2）允许root登录修改/etc/gdm3/daemon.conf文件，在[security]下增加一行AllowRoot = true 修改/etc/pam.d/gdm-password文件，注释掉auth required pam_succeed_if.so user != root quiet_success 3）重启sync&reboot 三、安装远程连接1）安装xrdpapt -y install xrdp 2)设置启动权限dpkg-reconfigure xserver-xorg-legacy 选择 anybody 四、远程连接使用win自带远程连接工具mstsc输入ip或域名直接连接即可win10家庭版不自带mstsc，可以通过安装补丁解决
安装ServerStatus时，因为服务器上安装了nginx，所以想手动配置http服务，但是选择手动配置HTTP之后ServerStatus服务启动不起来。状况：已配置虚拟主机，根目录为：/usr/local/ServerStatus/webServerStatus服务启动失败访问域名，nginx提示403解决过程：我去/usr/local/ServerStatus/web目录查看文件，发现里面还套有一层文件夹，即正确的网站根路径为：/usr/local/ServerStatus/web/web所以修改虚拟主机的根目录为：/usr/local/ServerStatus/web/web重载nginx，发现能访问监控网站了，但是ServerStatus的服务还没解决。去GitHub查看ServerStatus-Hotaru的源项目BotoX/ServerStatus，发现有一些启动配置，其中有WEB_BIN的配置查看ServerStatus的管理脚本，找到启动命令为：/etc/init.d/status-server start编辑status-server，修改 WEB_BIN="/usr/local/ServerStatus/web" 为 WEB_BIN="/usr/local/ServerStatus/web/web"再次尝试启动，成功！添加节点信息，并且安装服务端，好了~所以，别人封装好的脚本未必是最好的，毕竟环境不完全一样，这就体现docker的好处了哈哈或者我想是不是把/usr/local/ServerStatus/web/web内的文件移动到/usr/local/ServerStatus/web，也就是去除多套的一层文件夹，而不修改虚拟主机的根目录和启动文件，也能启动ServerStatus的服务呢。这个没有尝试
国内的小鸡下github的东西是真的慢,有时候还下不了...,遂学习了下在linux下使用代理前提:本机配置xray客户端,且设置socks系统代理wget仅支持http代理，利用xray提供的socks代理（酸酸、酸酸乳、vtoray也一样），安装tsocks工具，可以让其他程序使用socks代理。对于apt系的linux，直接安装即可。apt install tsocks修改/etc/tsocks.conf配置文件:server = 127.0.0.1 # 服务地址，本机直接使用环回地址。 server_type = 5 server_port = 1089 # socks监听的端口使用时,直接套上tsocks即可使用代理。例如 wget、git：tsocks wget ... tsocks git...试一下tsocks wget https://github.com/vaxilu/x-ui/releases/download/0.3.2/x-ui-linux-amd64.tar.gz
国内的机子，外网访问真是太慢了，GitHub部署个项目，下载个文件，几kb的跑，忍不了！所以研究了下trojan的客户端。一、准备1）国内任意linux vps2）trojan连接信息二、部署trojan client可以选择编译安装或者下载编译好的二进制包安装，这里就二进制包安装。1）下载打开 https://github.com/trojan-gfw/trojan/releases ，下载编译好的trojan-x.xx.x-linux-amd64.tar.xz的最新xz包使用wget下载（或者本机下载好再上传到vps，毕竟就是要解决下载慢的问题）wget --no-check-certificate https://github.com/trojan-gfw/trojan/releases/download/v1.16.0/trojan-1.16.0-linux-amd64.tar.xz 2）解包这个包不是tar.gz的包，不能使用tar zxvf解压xz -d trojan-1.16.0-linux-amd64.tar.xz tar xvf trojan-1.16.0-linux-amd64.tar # 得到trojan文件夹 ls -l 3）安装mv trojan /opt/sources/trojan-1.16.0 # 移动，方便管理 ln -s /opt/sources/trojan-1.16.0/ /opt/services/trojan # 创建软连接 cd /opt/services/trojan ls -l cp config.json config.json_bak # 备份配置文件 vim config.json # 编辑配置文件 参考配置文件，具体查看官网，https://trojan-gfw.github.io/trojan/config，需要填写trojan的连接信息。{ "run_type": "client", # 运行模式 "local_addr": "127.0.0.1", # 监听地址 "local_port": 1080, # 监听端口 "remote_addr": "xxxx.xxxx", # trojan服务域名 "remote_port": 443, # trojan服务端口 "password": [ # 密码，可以写多个，逗号分隔，第一个生效 "xxxxxxxx" ], "log_level": 1, "ssl": { "verify": true, "verify_hostname": true, "cert": "", "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA", "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384", "sni": "", "alpn": [ "h2", "http/1.1" ], "reuse_session": true, "session_ticket": false, "curves": "" }, "tcp": { "no_delay": true, "keep_alive": true, "reuse_port": false, "fast_open": false, "fast_open_qlen": 20 } }好了，保存，前台运行测试./trojan -l ./trojan.log -c ./config.json # -l 日志文件路径 # -c 配置文件路径 ssh打开另一个连接窗口查看是否监听127.0.0.1:1080ss -tlnp | grep 1080安装完成！三、后台运行与自启动回到第一个窗口，退出前台ctrl+c 1）写一个新的systemd配置文件vim trojan.service 写入以下配置[Unit] Description=trojan After=network.target network-online.target nss-lookup.target [Service] Type=simple StandardError=journal ExecStart=/opt/services/trojan/trojan -l /opt/services/trojan/trojan.log -c /opt/services/trojan/config.json ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=3s [Install] WantedBy=multi-user.target 2）启动systemctl start trojan 3）开机自启动systemctl enable trojan trojan客户端安装好了！