ExcelUtils.java 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. package com.shkpr.service.alambizplugin.commtools;
  2. import com.global.base.log.LogLevelFlag;
  3. import com.global.base.log.LogPrintMgr;
  4. import com.shkpr.service.alambizplugin.annotation.ExcelMapping;
  5. import com.shkpr.service.alambizplugin.constants.ExcelEnum;
  6. import com.shkpr.service.alambizplugin.constants.LogFlagBusiType;
  7. import org.apache.commons.collections4.map.CaseInsensitiveMap;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  10. import org.apache.poi.ss.usermodel.*;
  11. import org.apache.poi.xssf.streaming.SXSSFWorkbook;
  12. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.io.OutputStream;
  16. import java.lang.reflect.Field;
  17. import java.lang.reflect.InvocationTargetException;
  18. import java.math.BigDecimal;
  19. import java.nio.file.Files;
  20. import java.nio.file.Paths;
  21. import java.util.ArrayList;
  22. import java.util.Arrays;
  23. import java.util.Collection;
  24. import java.util.HashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Objects;
  28. import java.util.function.Function;
  29. import java.util.stream.Collectors;
  30. import java.util.stream.IntStream;
  31. /**
  32. * excel工具类
  33. *
  34. * @author 欧阳劲驰
  35. * @serial 1.0.0
  36. */
  37. public class ExcelUtils {
  38. /**
  39. * log
  40. */
  41. private static final String mStrClassName = "ExcelUtils";
  42. private static final String mBizType = LogFlagBusiType.BUSI_GIS_SURVEY.toStrValue();
  43. /**
  44. * 默认页名
  45. */
  46. private static final String DEFAULT_SHEET_NAME = "页1";
  47. /**
  48. * 解析excel文件
  49. *
  50. * @param path 文件地址
  51. * @param headerRowNum 表头行
  52. * @param dataRowNum 数据行
  53. * @return 数据(key : 页名, v : 数据)
  54. */
  55. public static Map<String, List<Map<String, String>>> parseExcelFile(String path, Integer headerRowNum, Integer dataRowNum) throws InterruptedException {
  56. //excel枚举
  57. ExcelEnum excelEnum = null;
  58. //文件后缀
  59. String ext = path.substring(path.lastIndexOf("."));
  60. //根据后缀名指定枚举
  61. if (".xls".equalsIgnoreCase(ext)) excelEnum = ExcelEnum.XLS;
  62. else if (".xlsx".equalsIgnoreCase(ext)) excelEnum = ExcelEnum.XLS;
  63. if (excelEnum == null) return null;
  64. //解析文件
  65. try {
  66. return parseExcelFile(Files.newInputStream(Paths.get(path)), excelEnum, headerRowNum, dataRowNum);
  67. } catch (IOException e) {
  68. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  69. , String.format("excel文件解释失败 msg:%s", e.getMessage())
  70. );
  71. return null;
  72. }
  73. }
  74. /**
  75. * 解析excel文件
  76. *
  77. * @param inputStream 输入流
  78. * @param excelEnum excel类型
  79. * @param headerRowNum 表头行
  80. * @param dataRowNum 数据行
  81. * @return 数据(key : 页名, v : 数据)
  82. */
  83. public static Map<String, List<Map<String, String>>> parseExcelFile(InputStream inputStream, ExcelEnum excelEnum
  84. , Integer headerRowNum, Integer dataRowNum) throws InterruptedException {
  85. if (inputStream == null || excelEnum == null || excelEnum == ExcelEnum.CSV) return null;
  86. //读取输入流
  87. try (Workbook workbook = excelEnum == ExcelEnum.XLSX ? new XSSFWorkbook(inputStream) : new HSSFWorkbook(inputStream)) {
  88. if (workbook.getNumberOfSheets() <= 0) return null;
  89. //结果
  90. Map<String, List<Map<String, String>>> results = new HashMap<>(workbook.getNumberOfSheets());
  91. //遍历页,并解析
  92. for (Sheet sheet : workbook) results.put(sheet.getSheetName(), parseSheet(sheet, headerRowNum, dataRowNum));
  93. return results;
  94. } catch (IOException e) {
  95. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  96. , String.format("excel文件解释失败 msg:%s", e.getMessage())
  97. );
  98. return null;
  99. }
  100. }
  101. /**
  102. * 解析页
  103. *
  104. * @param sheet 页
  105. * @param headerRowNum 表头行
  106. * @param dataRowNum 数据行
  107. * @return 数据
  108. */
  109. private static List<Map<String, String>> parseSheet(Sheet sheet, Integer headerRowNum, Integer dataRowNum) throws InterruptedException {
  110. //数据集合
  111. List<Map<String, String>> dataList = new ArrayList<>();
  112. if (sheet == null) return dataList;
  113. //行数
  114. int rowsNum = sheet.getPhysicalNumberOfRows();
  115. if (rowsNum <= 1) return dataList;
  116. //页头行
  117. Row headerRow = sheet.getRow(Math.max(headerRowNum - 1, 0));
  118. //字段映射
  119. Map<Integer, String> fieldMap = new HashMap<>();
  120. for (Cell cell : headerRow) {
  121. //检查线程中断,并响应
  122. if (Thread.interrupted()) throw new InterruptedException();
  123. //获取值,如不为空,则存入索引关系
  124. String cellValue = getCellValue(cell);
  125. if (!StringUtils.isEmpty(cellValue)) {
  126. fieldMap.put(cell.getColumnIndex(), cellValue);
  127. }
  128. }
  129. //遍历行
  130. for (Row row : sheet) {
  131. //检查线程中断,并响应
  132. if (Thread.interrupted()) throw new InterruptedException();
  133. //跳过非数据行
  134. if (row == null || row.getRowNum() < (dataRowNum - 1)) continue;
  135. //跳过空行
  136. if (IntStream.range(row.getFirstCellNum(), row.getLastCellNum())
  137. .mapToObj(row::getCell)
  138. .allMatch(cell -> cell == null || cell.getCellTypeEnum() == CellType.BLANK)) continue;
  139. //数据
  140. Map<String, String> data = new CaseInsensitiveMap<>();
  141. //遍历字段映射
  142. for (Map.Entry<Integer, String> entry : fieldMap.entrySet()) {
  143. //获取单元格
  144. Cell cell = row.getCell(entry.getKey());
  145. //设置值
  146. data.put(entry.getValue(), getCellValue(cell));
  147. }
  148. //添加数据
  149. dataList.add(data);
  150. }
  151. return dataList;
  152. }
  153. /**
  154. * excel文件转数据集合
  155. *
  156. * @param path 文件地址
  157. * @param clazz 数据类型
  158. * @param headerRowNum 表头行
  159. * @param dataRowNum 数据行
  160. * @param <E> 数据泛形
  161. * @return 数据集合
  162. */
  163. public static <E> Map<String, List<E>> parseExcelFile(String path, Class<E> clazz, Integer headerRowNum, Integer dataRowNum) {
  164. //excel枚举
  165. ExcelEnum excelEnum = null;
  166. //文件后缀
  167. String ext = path.substring(path.lastIndexOf("."));
  168. //根据后缀名指定枚举
  169. if (".xls".equalsIgnoreCase(ext)) excelEnum = ExcelEnum.XLS;
  170. else if (".xlsx".equalsIgnoreCase(ext)) excelEnum = ExcelEnum.XLS;
  171. if (excelEnum == null) return null;
  172. //解析文件
  173. try {
  174. return parseExcelFile(Files.newInputStream(Paths.get(path)), clazz, excelEnum, headerRowNum, dataRowNum);
  175. } catch (IOException e) {
  176. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  177. , String.format("excel文件解释失败 msg:%s", e.getMessage())
  178. );
  179. return null;
  180. }
  181. }
  182. /**
  183. * excel文件转数据集合
  184. *
  185. * @param inputStream input流
  186. * @param clazz 数据类型
  187. * @param excelEnum excel枚举
  188. * @param headerRowNum 表头行
  189. * @param dataRowNum 数据行
  190. * @param <E> 数据泛形
  191. * @return 数据集合
  192. */
  193. public static <E> Map<String, List<E>> parseExcelFile(InputStream inputStream, Class<E> clazz, ExcelEnum excelEnum
  194. , Integer headerRowNum, Integer dataRowNum) {
  195. if (inputStream == null || excelEnum == null || excelEnum == ExcelEnum.CSV) return null;
  196. //读取表
  197. try (Workbook workbook = excelEnum == ExcelEnum.XLSX ? new XSSFWorkbook(inputStream) : new HSSFWorkbook(inputStream)) {
  198. if (workbook.getNumberOfSheets() <= 0) return null;
  199. //结果
  200. Map<String, List<E>> results = new HashMap<>(workbook.getNumberOfSheets());
  201. //遍历页,并解析
  202. for (Sheet sheet : workbook)
  203. results.put(sheet.getSheetName(), parseSheet(sheet, clazz, headerRowNum, dataRowNum));
  204. return results;
  205. } catch (IOException e) {
  206. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  207. , String.format("excel文件解释失败 msg:%s", e.getMessage())
  208. );
  209. return null;
  210. }
  211. }
  212. /**
  213. * 解析页
  214. *
  215. * @param sheet 页
  216. * @param clazz 类型
  217. * @param headerRowNum 表头行
  218. * @param dataRowNum 数据行
  219. * @param <E> 数据泛形
  220. * @return 数据
  221. */
  222. private static <E> List<E> parseSheet(Sheet sheet, Class<E> clazz, Integer headerRowNum, Integer dataRowNum) {
  223. try {
  224. //数据集合
  225. List<E> dataList = new ArrayList<>();
  226. if (sheet == null) return dataList;
  227. //行数
  228. int rowsNum = sheet.getPhysicalNumberOfRows();
  229. if (rowsNum <= 1) return dataList;
  230. //页头行
  231. Row headerRow = sheet.getRow(Math.max(headerRowNum - 1, 0));
  232. //字段映射
  233. Map<Integer, Field> fieldMap = Arrays.stream(clazz.getDeclaredFields())
  234. //过滤需要导出的字段
  235. .filter(f -> f.isAnnotationPresent(ExcelMapping.class))
  236. //设置字段公开
  237. .peek(f -> f.setAccessible(true))
  238. .collect(Collectors.toMap(
  239. f -> {
  240. //获取excel映射值
  241. String excelMappingValue = f.getAnnotation(ExcelMapping.class).value();
  242. //获取对应的索引
  243. return IntStream.range(0, headerRow.getLastCellNum())
  244. //过滤相同的值
  245. .filter(index -> headerRow.getCell(index).getStringCellValue().equals(excelMappingValue))
  246. .findFirst().orElse(-1);
  247. }, Function.identity(),
  248. (it1, it2) -> it2,
  249. CaseInsensitiveMap::new
  250. ));
  251. //遍历行
  252. for (Row row : sheet) {
  253. //跳过非数据
  254. if (row == null || row.getRowNum() < (dataRowNum - 1)) continue;
  255. //跳过空行
  256. if (IntStream.range(row.getFirstCellNum(), row.getLastCellNum())
  257. .mapToObj(row::getCell)
  258. .allMatch(cell -> cell == null || cell.getCellTypeEnum() == CellType.BLANK)) continue;
  259. //实列化数据
  260. E data = clazz.getDeclaredConstructor().newInstance();
  261. //遍历字段映射
  262. for (Map.Entry<Integer, Field> fieldEntry : fieldMap.entrySet()) {
  263. //跳过未找到的字段
  264. if (fieldEntry.getKey() == -1) continue;
  265. //获取单元格
  266. Cell cell = row.getCell(fieldEntry.getKey());
  267. //设置值
  268. setObjValue(data, fieldEntry.getValue(), getCellValue(cell));
  269. }
  270. //添加数据
  271. dataList.add(data);
  272. }
  273. return dataList;
  274. } catch (NoSuchMethodException | InvocationTargetException | InstantiationException |
  275. IllegalAccessException e) {
  276. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  277. , String.format("excel页解释失败 msg:%s", e.getMessage())
  278. );
  279. return null;
  280. }
  281. }
  282. /**
  283. * 写入文件
  284. *
  285. * @param headers 表头信息
  286. * @param datas 数据
  287. * @param outputStream 输出流
  288. * @param excelEnum excel枚举
  289. * @param headerRowNum 表头行
  290. * @param dataRowNum 数据行
  291. */
  292. public static void writeFile(Map<String, Map<String, String>> headers, Map<String, List<Map<String, Object>>> datas
  293. , OutputStream outputStream, ExcelEnum excelEnum, Integer headerRowNum, Integer dataRowNum) {
  294. //创建表
  295. try (Workbook workbook = excelEnum == ExcelEnum.XLSX ? new SXSSFWorkbook() : new HSSFWorkbook()) {
  296. //表头样式
  297. CellStyle headerStyle = workbook.createCellStyle();
  298. headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
  299. headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  300. Font headerFont = workbook.createFont();
  301. headerFont.setBold(true);
  302. headerStyle.setFont(headerFont);
  303. //遍历页
  304. for (Map.Entry<String, Map<String, String>> headersEntry : headers.entrySet()) {
  305. //创建页
  306. Sheet sheet = workbook.createSheet(headersEntry.getKey());
  307. if (!datas.containsKey(headersEntry.getKey())) continue;
  308. //写入页
  309. writeSheet(headersEntry.getValue(), datas.get(headersEntry.getKey()), sheet, headerStyle, headerRowNum, dataRowNum);
  310. }
  311. //写入文件
  312. workbook.write(outputStream);
  313. //清理临时文件
  314. if (workbook instanceof SXSSFWorkbook) ((SXSSFWorkbook) workbook).dispose();
  315. } catch (IOException e) {
  316. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  317. , String.format("excel文件写入失败 msg:%s", e.getMessage())
  318. );
  319. }
  320. }
  321. /**
  322. * 写入文件
  323. *
  324. * @param header 表头信息
  325. * @param data 数据
  326. * @param outputStream 输出流
  327. * @param excelEnum excel枚举
  328. * @param headerRowNum 表头行
  329. * @param dataRowNum 数据行
  330. */
  331. public static void writeFile(Map<String, String> header, List<Map<String, Object>> data
  332. , OutputStream outputStream, ExcelEnum excelEnum, Integer headerRowNum, Integer dataRowNum) {
  333. //创建表
  334. try (Workbook workbook = excelEnum == ExcelEnum.XLSX ? new XSSFWorkbook() : new HSSFWorkbook()) {
  335. //表头样式
  336. CellStyle headerStyle = workbook.createCellStyle();
  337. headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
  338. headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
  339. Font headerFont = workbook.createFont();
  340. headerFont.setBold(true);
  341. headerStyle.setFont(headerFont);
  342. //创建页
  343. Sheet sheet = workbook.createSheet(DEFAULT_SHEET_NAME);
  344. //写入页
  345. writeSheet(header, data, sheet, headerStyle, headerRowNum, dataRowNum);
  346. //写入文件
  347. workbook.write(outputStream);
  348. } catch (IOException e) {
  349. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  350. , String.format("excel文件写入失败 msg:%s", e.getMessage())
  351. );
  352. }
  353. }
  354. /**
  355. * 写入页
  356. *
  357. * @param header 表头
  358. * @param data 数据
  359. * @param sheet 页
  360. * @param headerStyle 头样式
  361. * @param headerRowNum 表头行
  362. * @param dataRowNum 数据行
  363. */
  364. public static void writeSheet(Map<String, String> header, List<Map<String, Object>> data
  365. , Sheet sheet, CellStyle headerStyle, Integer headerRowNum, Integer dataRowNum) {
  366. //表头键
  367. List<String> headerKeys = new ArrayList<>();
  368. //表头行
  369. Row headRow = sheet.createRow(Math.max(headerRowNum - 1, 0));
  370. //遍历表头
  371. for (Map.Entry<String, String> headerEntry : header.entrySet()) {
  372. //缓存键
  373. headerKeys.add(headerEntry.getKey());
  374. //设置值和样式
  375. Cell cell = headRow.createCell(headerKeys.size() - 1);
  376. setCellValue(cell, headerEntry.getValue());
  377. cell.setCellStyle(headerStyle);
  378. }
  379. //遍历数据
  380. for (int i = 0; i < data.size(); i++) {
  381. Map<String, Object> item = data.get(i);
  382. Row row = sheet.createRow(i + dataRowNum - 1);
  383. //遍历表头键
  384. for (int j = 0, headerKeysSize = headerKeys.size(); j < headerKeysSize; j++) {
  385. //根据表头键,设置值
  386. String headerKey = headerKeys.get(j);
  387. Cell cell = row.createCell(j);
  388. setCellValue(cell, item.get(headerKey));
  389. }
  390. }
  391. }
  392. /**
  393. * 获取单元格值
  394. *
  395. * @param cell 单元格
  396. * @return 值
  397. */
  398. private static String getCellValue(Cell cell) {
  399. if (cell == null) return null;
  400. //值
  401. String value = null;
  402. try {
  403. switch (cell.getCellTypeEnum()) {
  404. case NUMERIC: {
  405. if (DateUtil.isCellDateFormatted(cell)) {
  406. value = TimeTool.convertDateObj2DateStr(cell.getDateCellValue(), cell.getCellStyle().getDataFormatString());
  407. } else if (String.valueOf(cell.getNumericCellValue()).contains(".")) {
  408. value = BigDecimal.valueOf(cell.getNumericCellValue()).stripTrailingZeros().toPlainString();
  409. } else
  410. value = String.valueOf(cell.getNumericCellValue());
  411. }
  412. break;
  413. case STRING:
  414. value = cell.getRichStringCellValue().toString();
  415. break;
  416. case BOOLEAN:
  417. value = String.valueOf(cell.getBooleanCellValue());
  418. break;
  419. case FORMULA: {
  420. //根据结果类型设置值
  421. CellType cellType = cell.getCachedFormulaResultTypeEnum();
  422. if (cellType == CellType.NUMERIC) value = String.valueOf(cell.getNumericCellValue());
  423. else if (cellType == CellType.STRING) value = cell.getRichStringCellValue().toString();
  424. else value = cell.getCellFormula();
  425. }
  426. break;
  427. default:
  428. break;
  429. }
  430. } catch (Exception e) {
  431. LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, mBizType, mStrClassName
  432. , String.format("excel单元格解释失败 error:%s", e)
  433. );
  434. }
  435. return value == null ? "" : value.trim();
  436. }
  437. /**
  438. * 设置单元格值
  439. *
  440. * @param cell 单元格
  441. * @param value 值
  442. */
  443. private static void setCellValue(Cell cell, Object value) {
  444. if (value == null) cell.setCellValue("");
  445. else if (value instanceof String) cell.setCellValue((String) value);
  446. else if (value instanceof Integer) cell.setCellValue((Integer) value);
  447. else if (value instanceof Double) cell.setCellValue((Double) value);
  448. else if (value instanceof Boolean) cell.setCellValue((Boolean) value);
  449. else if (value instanceof Long) cell.setCellValue((Long) value);
  450. else if (value instanceof Float) cell.setCellValue((Float) value);
  451. else if (value instanceof Short) cell.setCellValue((Short) value);
  452. else if (value instanceof Byte) cell.setCellValue((Byte) value);
  453. else if (value instanceof Character) cell.setCellValue(value.toString());
  454. else if (value instanceof Collection<?>)
  455. cell.setCellValue(((Collection<?>) value).stream().map(Objects::toString).collect(Collectors.joining(",")));
  456. else cell.setCellValue(value.toString());
  457. }
  458. /**
  459. * 设置数据值
  460. *
  461. * @param data 数据
  462. * @param field 字段
  463. * @param value 值
  464. * @param <E> 数据泛形
  465. * @throws IllegalAccessException 非法访问异常
  466. */
  467. private static <E> void setObjValue(E data, Field field, Object value) throws IllegalAccessException {
  468. //值判断空
  469. if (value != null) {
  470. //判断数据类型是否相同,如相同直接设置值,如不同,则转换值
  471. if (field.getType().isAssignableFrom(value.getClass())) {
  472. //设置值
  473. field.set(data, value);
  474. } else {
  475. //如值为double类型
  476. if (value instanceof Double) {
  477. //对double类型兼容的类型处理
  478. if (field.getType() == short.class || field.getType() == Short.class)
  479. field.set(data, ((Double) value).shortValue());
  480. else if (field.getType() == int.class || field.getType() == Integer.class)
  481. field.set(data, ((Double) value).intValue());
  482. else if (field.getType() == long.class || field.getType() == Long.class)
  483. field.set(data, ((Double) value).longValue());
  484. else if (field.getType() == float.class || field.getType() == Float.class)
  485. field.set(data, ((Double) value).floatValue());
  486. }
  487. }
  488. }
  489. }
  490. }