001package top.cenze.utils; 002 003import cn.hutool.core.collection.CollectionUtil; 004import cn.hutool.core.convert.Convert; 005import cn.hutool.core.date.DateUtil; 006import cn.hutool.core.io.FileUtil; 007import cn.hutool.core.util.ObjectUtil; 008import cn.hutool.core.util.StrUtil; 009import cn.hutool.core.util.XmlUtil; 010import com.alibaba.fastjson.JSON; 011import com.alibaba.fastjson.JSONArray; 012import com.alibaba.fastjson.JSONObject; 013import lombok.SneakyThrows; 014import lombok.extern.slf4j.Slf4j; 015import org.apache.commons.fileupload.FileItem; 016import org.apache.commons.fileupload.FileItemFactory; 017import org.apache.commons.fileupload.disk.DiskFileItemFactory; 018import org.apache.commons.jexl3.JexlBuilder; 019import org.apache.commons.lang.NullArgumentException; 020import org.apache.poi.hssf.usermodel.HSSFClientAnchor; 021import org.apache.poi.hssf.usermodel.HSSFDateUtil; 022import org.apache.poi.hssf.usermodel.HSSFRichTextString; 023import org.apache.poi.hssf.usermodel.HSSFWorkbook; 024import org.apache.poi.ss.usermodel.*; 025import org.apache.poi.xssf.usermodel.*; 026import org.jxls.common.Context; 027import org.jxls.expression.JexlExpressionEvaluator; 028import org.jxls.reader.ReaderBuilder; 029import org.jxls.reader.ReaderConfig; 030import org.jxls.reader.XLSReadStatus; 031import org.jxls.reader.XLSReader; 032import org.jxls.transform.Transformer; 033import org.jxls.transform.poi.PoiTransformer; 034import org.jxls.transform.poi.WritableCellValue; 035import org.jxls.transform.poi.WritableHyperlink; 036import org.jxls.util.JxlsHelper; 037import org.springframework.web.multipart.MultipartFile; 038import org.w3c.dom.Document; 039import org.w3c.dom.Element; 040import top.cenze.utils.component.ExcelDateConverter; 041import top.cenze.utils.enums.ExcelTypeEnum; 042import top.cenze.utils.file.CZFileUtil; 043import top.cenze.utils.file.MultipartFileUtil; 044import top.cenze.utils.file.XMLUtil; 045import top.cenze.utils.pojo.*; 046 047import javax.servlet.ServletOutputStream; 048import javax.servlet.http.HttpServletRequest; 049import javax.servlet.http.HttpServletResponse; 050import java.io.*; 051import java.text.DecimalFormat; 052import java.text.SimpleDateFormat; 053import java.util.*; 054import java.util.regex.Matcher; 055import java.util.regex.Pattern; 056import java.util.stream.Collectors; 057 058/** 059 * @desc: JxlsExcel工具类 060 * @author: chengze 061 * @createByDate: 2023/10/9 10:24 062 */ 063@Slf4j 064public class JxlsExcelUtil { 065 066 /** ========================================= delete ========================================= */ 067 068 /** 069 * 删除行(从指定起始行开始,删除排除行集合之外的所有行数据) 070 * @param file 071 * @param sheetIndex 072 * @param startRow 073 * @param lstExclusionRow 074 * @return 075 */ 076 @SneakyThrows 077 public static MultipartFile delWithExclusionRow(MultipartFile file, 078 Integer sheetIndex, 079 Integer startRow, 080 List<Integer> lstExclusionRow) { 081 if (ObjectUtil.isNull(file) || 082 ObjectUtil.isNull(sheetIndex) || 083 CollectionUtil.isEmpty(lstExclusionRow)) { 084 return null; 085 } 086 087 Workbook workbook = null; 088 if (file.getOriginalFilename().endsWith(".xls")) { 089 workbook = new HSSFWorkbook(file.getInputStream()); 090 } 091 if (file.getOriginalFilename().endsWith(".xlsx")) { 092 workbook = new XSSFWorkbook(file.getInputStream()); 093 } 094 095 // worksheet 096 Sheet sheet = workbook.getSheetAt(sheetIndex); 097 int lastRowNum = sheet.getLastRowNum(); 098 099 for (int i = startRow; i < lastRowNum; i++) { 100 Row row = sheet.getRow(i); 101 if (!lstExclusionRow.contains(i) && ObjectUtil.isNull(row)) { 102 sheet.removeRow(row); 103 } 104 } 105 106 FileItemFactory factory = new DiskFileItemFactory(16, null); 107 FileItem fileItem = factory.createItem("textField", "text/plain", true, file.getName()); 108 OutputStream os = fileItem.getOutputStream(); 109 workbook.write(os); 110 111 return MultipartFileUtil.getMultipartFile(fileItem); 112 } 113 114 /** 115 * 删除指定行 116 * @param file 117 * @param sheetIndex 118 * @param lstRow 119 * @return 120 */ 121 @SneakyThrows 122 public static MultipartFile delRow(MultipartFile file, 123 Integer sheetIndex, 124 List<Integer> lstRow) { 125 if (ObjectUtil.isNull(file) || 126 ObjectUtil.isNull(sheetIndex) || 127 CollectionUtil.isEmpty(lstRow)) { 128 return null; 129 } 130 131 Workbook workbook = null; 132 if (file.getOriginalFilename().endsWith(".xls")) { 133 workbook = new HSSFWorkbook(file.getInputStream()); 134 } 135 if (file.getOriginalFilename().endsWith(".xlsx")) { 136 workbook = new XSSFWorkbook(file.getInputStream()); 137 } 138 139 // worksheet 140 Sheet sheet = workbook.getSheetAt(sheetIndex); 141// // 创建样式 142// CellStyle cellStyle = workbook.createCellStyle(); 143// cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); 144// cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); 145 146 for (Integer rowIdx : lstRow) { 147 Row row = sheet.getRow(rowIdx); 148 if (ObjectUtil.isNull(row)) { 149 sheet.removeRow(row); 150 } 151// for (int i = 0; i < maxCol; i++) { 152// Cell cell = row.getCell(i); 153// cell.setCellStyle(cellStyle); 154// } 155 } 156 157 FileItemFactory factory = new DiskFileItemFactory(16, null); 158 FileItem fileItem = factory.createItem("textField", "text/plain", true, file.getName()); 159 OutputStream os = fileItem.getOutputStream(); 160 workbook.write(os); 161 162 return MultipartFileUtil.getMultipartFile(fileItem); 163 } 164 165 /** ========================================= import ========================================= */ 166 167 /** 168 * 导入Excel 169 * @param file 待导入数据文件(Excel) 170 * @param templateFilePath 导入模板文件路径 171 * @param items 输出对象集合 172 * @param skipErrs 是否跳过异常 173 * @return 174 */ 175 @SneakyThrows 176 public static XLSReadStatus importExcel(MultipartFile file, 177 String templateFilePath, 178 Object items, 179 Boolean skipErrs) { 180 // 载入导入模板流 181 InputStream isImportXmlTemplate = CZFileUtil.readFile(templateFilePath); 182 183 // 读取待导入数据文件(Excel) 184 InputStream isImportFile = file.getInputStream(); 185 186 return importExcel(isImportFile, isImportXmlTemplate, items, skipErrs); 187 } 188 189 /** 190 * 导入Excel 191 * @param file 待导入数据文件(Excel) 192 * @param templateFile 导入模板二进制文件(如从数据库中读取的文件) 193 * @param items 输出对象集合 194 * @param skipErrs 是否跳过异常 195 * @return 196 */ 197 @SneakyThrows 198 public static XLSReadStatus importExcel(MultipartFile file, 199 byte[] templateFile, 200 Object items, 201 Boolean skipErrs) { 202 // 载入导入模板流 203 InputStream isImportXmlTemplate = new ByteArrayInputStream(templateFile); 204 205 // 读取待导入数据文件(Excel) 206 InputStream isImportFile = file.getInputStream(); 207 208 return importExcel(isImportFile, isImportXmlTemplate, items, skipErrs); 209 } 210 211 /** 212 * 导入Excel 213 * @param importFilePath 待导入数据文件路径(Excel) 214 * @param templateFilePath 导入模板文件路径 215 * @param items 输出对象集合 216 * @param skipErrs 是否跳过异常 217 * @return 218 */ 219 @SneakyThrows 220 public static XLSReadStatus importExcel(String importFilePath, 221 String templateFilePath, 222 Object items, 223 Boolean skipErrs) { 224 // 载入导入模板流 225 InputStream isImportXmlTemplate = CZFileUtil.readFile(templateFilePath); 226 227 // 读取待导入数据文件(Excel) 228 InputStream isImportFile = CZFileUtil.readFile(importFilePath); 229 230 return importExcel(isImportFile, isImportXmlTemplate, items, skipErrs); 231 } 232 233 /** 234 * 导入Excel 235 * @param importFilePath 待导入数据文件路径(Excel) 236 * @param templateFile 导入模板二进制文件(如从数据库中读取的文件) 237 * @param items 输出对象集合 238 * @param skipErrs 是否跳过异常 239 * @return 240 */ 241 @SneakyThrows 242 public static XLSReadStatus importExcel(String importFilePath, 243 byte[] templateFile, 244 Object items, 245 Boolean skipErrs) { 246 // 载入导入模板流 247 InputStream isImportXmlTemplate = new ByteArrayInputStream(templateFile); 248 249 // 读取待导入数据文件(Excel) 250 InputStream isImportFile = CZFileUtil.readFile(importFilePath); 251 252 return importExcel(isImportFile, isImportXmlTemplate, items, skipErrs); 253 } 254 255 /** 256 * 导入Excel 257 * @param isImportFile 待导入数据文件流(Excel) 258 * @param isImportXmlTemplate 载入的导入模板流 259 * @param items 输出对象集合 260 * @param skipErrs 是否跳过异常 261 * @return 262 */ 263 @SneakyThrows 264 public static XLSReadStatus importExcel(InputStream isImportFile, 265 InputStream isImportXmlTemplate, 266 Object items, 267 Boolean skipErrs) { 268 // 默认不跳过异常 269 if (ObjectUtil.isNull(skipErrs)) { 270 skipErrs = false; 271 } 272 273 log.info("importExcel items: {}", JSON.toJSONString(items)); 274 if (ObjectUtil.isNull(isImportFile)) { 275 throw new NullArgumentException("待导入数据文件流不能为空"); 276 } 277 if (ObjectUtil.isNull(isImportXmlTemplate)) { 278 throw new NullArgumentException("导入模板流不能为空"); 279 } 280 if (ObjectUtil.isNull(items)) { 281 throw new NullArgumentException("输出对象集合不能为空"); 282 } 283 284 if (skipErrs) { 285 // 跳过异常 286 ReaderConfig.getInstance().setSkipErrors(true); 287 } 288 289 // 开启读取偏好设置 290 ReaderConfig.getInstance().setUseDefaultValuesForPrimitiveTypes(true); 291 292 // 创建读取器 293 XLSReader reader = ReaderBuilder.buildFromXML(isImportXmlTemplate); 294 reader.getConvertUtilsBeanProvider().getConvertUtilsBean().register(new ExcelDateConverter(), java.lang.String.class); 295// reader.getConvertUtilsBeanProvider().getConvertUtilsBean().register(new ZonedDateTimeConverter(), java.time.ZonedDateTime.class); 296// reader.getConvertUtilsBeanProvider().getConvertUtilsBean().register(new LocalDateConverter(), java.time.LocalDate.class); 297// reader.getConvertUtilsBeanProvider().getConvertUtilsBean().register(new InstantConverter(), java.time.Instant.class); 298// reader.getConvertUtilsBeanProvider().getConvertUtilsBean().register(new StringConverter(), java.lang.String.class); 299 300 // 创建映射对象集合 301 Map beans = new HashMap<>(); 302 beans.put("items", items); 303 304 return reader.read(isImportFile, beans); 305 } 306 307 /** 308 * 解释Excel读取映射异常信息 309 * @param xlsReadStatus 310 * @return 311 */ 312 public static List<ExcelReadException> getReadExceptions(XLSReadStatus xlsReadStatus) { 313 // 正则表达式(N个大写字母 + N个数字) 314 String regex = "[A-Z]+[0-9]+"; 315 Pattern pattern = Pattern.compile(regex); 316 317 // 映射失败信息处理 318 if (ObjectUtil.isNotNull(xlsReadStatus.getReadMessages())) { 319 List<ExcelReadException> lstReadException = new ArrayList<>(); 320 JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(xlsReadStatus.getReadMessages())); 321 for (int i = 0; i < jsonArray.size(); i++) { 322 JSONObject jsonObject = jsonArray.getJSONObject(i); 323 if (ObjectUtil.isNull(jsonObject)) { 324 continue; 325 } 326 327 ExcelReadException readException = new ExcelReadException(); 328 // 映射失败单元格提示 329 String errCell = jsonObject.getString("message"); 330 log.info("getReadExceptions errCell: {}", errCell); 331 if (StrUtil.isNotEmpty(errCell)) { 332 // 使用正则表达式提取单元格名称 333 Matcher matcher = pattern.matcher(errCell); 334 while (matcher.find()) { 335 readException.setExCell(matcher.group()); 336 } 337 } 338 JSONObject exception = jsonObject.getJSONObject("exception"); 339 if (ObjectUtil.isNotNull(exception)) { 340 // 映射失败原因 341 readException.setExMessage(exception.getString("message")); 342 } 343 344 lstReadException.add(readException); 345 } 346 347 return lstReadException; 348 } 349 350 return null; 351 } 352 353 /** ========================================= export ========================================= */ 354 355 /** 356 * 导出Excel(输出到数据流) 357 * @param templateFilePath Excel模板文件路径 358 * @param mapperObject 待导出对象数据(单一对象Object) 359 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 360 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 361 * @param response 362 * @param request 363 */ 364 @SneakyThrows 365 public static void exportExcel(String templateFilePath, 366 Object mapperObject, 367 Object mapperLoop, 368 Object mapperFunc, 369 HttpServletResponse response, 370 HttpServletRequest request) { 371 // 载入模板文件 372 File file = new File(templateFilePath); 373 FileInputStream fis = new FileInputStream(file); 374 // 强转成int类型大小的数组 375 byte[] bytes = new byte[(int) file.length()]; 376 // 将pdf内容放到数组当中 377 fis.read(bytes); 378 // 关闭文件流 379 fis.close(); 380 381 exportExcel(bytes, mapperObject, mapperLoop, mapperFunc, response, request); 382 383 } 384 385 /** 386 * 导出Excel(输出到数据流) 387 * @param templateFile Excel模板二进制文件(从数据库中读取的) 388 * @param mapperObject 待导出对象数据(单一对象Object) 389 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 390 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 391 * @param response 392 * @param request 393 */ 394 @SneakyThrows 395 public static void exportExcel(byte[] templateFile, 396 Object mapperObject, 397 Object mapperLoop, 398 Object mapperFunc, 399 HttpServletResponse response, 400 HttpServletRequest request) { 401 byte[] bytes = exportExcel(templateFile, mapperObject, mapperLoop, mapperFunc); 402 403 ServletOutputStream outputStream = response.getOutputStream(); 404 outputStream.write(bytes); 405 outputStream.flush(); 406 outputStream.close(); 407 } 408 409 /** 410 * 导出Excel(输出到本地) 411 * @param templateFilePath Excel模板文件路径: xx/xxx/xxx.xlsx 412 * @param mapperObject 待导出对象数据(单一对象Object) 413 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 414 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 415 */ 416 @SneakyThrows 417 public static byte[] exportExcel(String templateFilePath, 418 Object mapperObject, 419 Object mapperLoop, 420 Object mapperFunc) { 421 // 载入模板文件 422 File file = new File(templateFilePath); 423 FileInputStream fis = new FileInputStream(file); 424 // 强转成int类型大小的数组 425 byte[] bytes = new byte[(int) file.length()]; 426 // 将pdf内容放到数组当中 427 fis.read(bytes); 428 // 关闭文件流 429 fis.close(); 430 431 return exportExcel(bytes, mapperObject, mapperLoop, mapperFunc); 432 } 433 434 /** 435 * 导出Excel(输出到本地) 436 * @param templateFile Excel模板二进制文件(从数据库中读取的) 437 * @param mapperObject 待导出对象数据(单一对象Object) 438 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 439 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 440 */ 441 @SneakyThrows 442 public static byte[] exportExcel(byte[] templateFile, 443 Object mapperObject, 444 Object mapperLoop, 445 Object mapperFunc) { 446 String templateFileName = UUID.randomUUID().toString().replaceAll("-", "") + ExcelTypeEnum.XLSX.getExtname(); 447 File tmpFile = CZFileUtil.mkFileToResource(null, templateFileName); 448 CZFileUtil.writeFileToResource(tmpFile, templateFile); 449 450 451 String exportFileName = UUID.randomUUID().toString().replaceAll("-", "") + ExcelTypeEnum.XLSX.getExtname(); 452 File expFile = CZFileUtil.mkFileToResource(null, exportFileName); 453 454 return exportExcel(tmpFile, expFile, mapperObject, mapperLoop, mapperFunc); 455 } 456 457 /** 458 * 导出Excel 459 * @param templateFile Excel模板文件 460 * @param exportFile 输出文件 461 * @param mapperObject 待导出对象数据(单一对象Object) 462 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 463 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 464 */ 465 @SneakyThrows 466 public static byte[] exportExcel(File templateFile, 467 File exportFile, 468 Object mapperObject, 469 Object mapperLoop, 470 Object mapperFunc) { 471 MultipartFile file = exportExcelToMultiPartFile(templateFile, exportFile, mapperObject, mapperLoop, mapperFunc); 472 if (ObjectUtil.isNotNull(file)) { 473 return file.getBytes(); 474 } 475 476 return null; 477 } 478 479 /** 480 * 导出Excel 481 * @param templateFile Excel模板二进制文件(从数据库中读取的) 482 * @param mapperObject 待导出对象数据(单一对象Object) 483 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 484 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 485 */ 486 @SneakyThrows 487 public static MultipartFile exportExcelToMultiPartFile(byte[] templateFile, 488 Object mapperObject, 489 Object mapperLoop, 490 Object mapperFunc) { 491 String templateFileName = UUID.randomUUID().toString().replaceAll("-", "") + ExcelTypeEnum.XLSX.getExtname(); 492 File tmpFile = CZFileUtil.mkFileToResource(null, templateFileName); 493 CZFileUtil.writeFileToResource(tmpFile, templateFile); 494 495 496 String exportFileName = UUID.randomUUID().toString().replaceAll("-", "") + ExcelTypeEnum.XLSX.getExtname(); 497 File expFile = CZFileUtil.mkFileToResource(null, exportFileName); 498 499 return exportExcelToMultiPartFile(tmpFile, expFile, mapperObject, mapperLoop, mapperFunc); 500 } 501 502 /** 503 * 导出Excel 504 * @param templateFile Excel模板文件 505 * @param exportFile 输出文件 506 * @param mapperObject 待导出对象数据(单一对象Object) 507 * @param mapperLoop 待导出的循环对象数据(对象Object数组) 508 * @param mapperFunc 待载入的自定义对象(用于Excel中引用其对象中的方法)(单一对象Object) 509 */ 510 @SneakyThrows 511 public static MultipartFile exportExcelToMultiPartFile(File templateFile, 512 File exportFile, 513 Object mapperObject, 514 Object mapperLoop, 515 Object mapperFunc) { 516 if (!FileUtil.exist(templateFile) || !FileUtil.exist(exportFile)) { 517 throw new Exception("模板或导出文件不存在"); 518 } 519 520 FileInputStream fis = null; 521 FileOutputStream fos = null; 522 try { 523 // 载入模板文件 524 fis = new FileInputStream(templateFile); 525 // 创建输出文件流 526 fos = new FileOutputStream(exportFile); 527 528 JxlsHelper jxlsHelper = JxlsHelper.getInstance(); 529 Transformer trans = jxlsHelper.createTransformer(fis, fos); 530 531 // 设置自定义方法(用于Excel模板内引用) 532 if (ObjectUtil.isNotNull(mapperFunc)) { 533 Map<String , Object> mapFunc = new HashMap<>(); 534 mapFunc.put("funcs", mapperFunc); 535 // 启动新的jxls-api加载自定义方法 536 JexlExpressionEvaluator evaluator = (JexlExpressionEvaluator) trans.getTransformationConfig().getExpressionEvaluator(); 537 evaluator.setJexlEngine(new JexlBuilder().namespaces(mapFunc).create()); 538 } 539 540 // 创建导出数据 541 Context context = PoiTransformer.createInitialContext(); 542 // 设置导出对象数据 543 if (ObjectUtil.isNotNull(mapperObject)) { 544 context.putVar("object", mapperObject); 545 } 546 // 设置导出循环对象数据 547 if (ObjectUtil.isNotNull(mapperLoop)) { 548 context.putVar("items", mapperLoop); 549 } 550 551 //导出 552 jxlsHelper.setUseFastFormulaProcessor(false) // 必须要这个,否者表格函数统计会错乱 553 .processTemplate(context, trans); 554 555 return MultipartFileUtil.getMultipartFile(exportFile); 556 } catch (FileNotFoundException fe) { 557 log.error("export file not found err: {}", fe.getMessage()); 558 throw new Exception(fe.getMessage()); 559 } catch (IOException ie) { 560 log.error("export io err: {}", ie.getMessage()); 561 throw new Exception(ie.getMessage()); 562 } finally { 563 try { 564 if (ObjectUtil.isNotNull(fos)) { 565 fos.flush(); 566 fos.close(); 567 } 568 if (ObjectUtil.isNotNull(fis)) { 569 fis.close(); 570 } 571 572 FileUtil.del(templateFile); 573 FileUtil.del(exportFile); 574 } catch (IOException e) { 575 log.error("export io err: {}", e.getMessage()); 576 throw new Exception(e.getMessage()); 577 } 578 } 579 } 580 581 /** ========================================= create export template (xml) ========================================= */ 582 583 /** 584 * 给Cell添加批注 585 * 586 * @param cell 单元格 587 * @param value 批注内容 588 * @param excelType 扩展名 589 */ 590 private static void addComment(Cell cell, String value, ExcelTypeEnum excelType) { 591 if (ObjectUtil.isNull(excelType)) { 592 excelType = ExcelTypeEnum.XLSX; 593 } 594 595 Sheet sheet = cell.getSheet(); 596 cell.removeCellComment(); 597 if (ExcelTypeEnum.XLS.equals(excelType)) { 598 ClientAnchor anchor = new HSSFClientAnchor(); 599 // 关键修改 600 anchor.setDx1(0); 601 anchor.setDx2(0); 602 anchor.setDy1(0); 603 anchor.setDy2(0); 604 anchor.setCol1(cell.getColumnIndex()); 605 anchor.setRow1(cell.getRowIndex()); 606 anchor.setCol2(cell.getColumnIndex()); 607 anchor.setRow2(cell.getRowIndex()); 608 // 结束 609 Drawing drawing = sheet.createDrawingPatriarch(); 610 Comment comment = drawing.createCellComment(anchor); 611 // 输入批注信息 612 comment.setString(new HSSFRichTextString(value)); 613 // 将批注添加到单元格对象中 614 cell.setCellComment(comment); 615 } else if (ExcelTypeEnum.XLSX.equals(excelType)) { 616 ClientAnchor anchor = new XSSFClientAnchor(); 617 // 关键修改 618 anchor.setDx1(0); 619 anchor.setDx2(0); 620 anchor.setDy1(0); 621 anchor.setDy2(0); 622 anchor.setCol1(cell.getColumnIndex()); 623 anchor.setRow1(cell.getRowIndex()); 624 anchor.setCol2(cell.getColumnIndex()); 625 anchor.setRow2(cell.getRowIndex()); 626 // 结束 627 Drawing drawing = sheet.createDrawingPatriarch(); 628 Comment comment = drawing.createCellComment(anchor); 629 // 输入批注信息 630 comment.setString(new XSSFRichTextString(value)); 631 // 将批注添加到单元格对象中 632 cell.setCellComment(comment); 633 } 634 } 635 636 /** 637 * 创建导出模板 638 * @param mapping 639 * @return 640 */ 641 @SneakyThrows 642 public static MultipartFile createExportTemplate(ExcelMapping mapping) { 643 XSSFWorkbook workbook = new XSSFWorkbook(); 644 Sheet sheet = workbook.createSheet("Sheet1"); 645 if (ObjectUtil.isNull(sheet)) { 646 throw new NullPointerException("创建sheet失败"); 647 } 648 649 // 获取所有单元格样式 650 Map<Integer, Map<Integer, CellStyle>> mapAllStyle = new HashMap<>(); 651 Map<Integer, Short> mapAllHeight = new HashMap<>(); 652 Map<Integer, Integer> mapAllWidth = new HashMap<>(); 653 getSheetStyle(sheet, mapAllStyle, mapAllHeight, mapAllWidth); 654 655 // 创建所有行 656 Map<Integer, Row> allRow = createAllRow(sheet, mapping); 657 log.info("createExportTemplate allRow size: {}", allRow.size()); 658 if (ObjectUtil.isNull(allRow.get(0))) { 659 throw new NullPointerException("创建单元格失败"); 660 } 661 662 // A1单元格 663 Cell a1 = allRow.get(0).getCell(0); 664 CellStyle a1Style = null; 665 if (ObjectUtil.isNull(a1)) { 666 allRow.get(0).createCell(0, CellType.STRING); 667 a1 = allRow.get(0).getCell(0); 668 } 669 // 创建所有列及映射 670 Map<Integer, Map<Integer, Cell>> allCell = createAllCell(allRow, mapping); 671 672 // 设置A1批注 673 String a1Rich = ConvertUtil.numberToLetter(mapping.getMaxCol()) + (mapping.getMaxRow() + 1); 674 addComment(a1, "jx:area(lastCell=\"" + a1Rich + "\")", null); 675 if (ObjectUtil.isNotNull(a1) && StrUtil.isNotEmpty(a1.getStringCellValue())) { 676 a1.setCellValue(a1.getStringCellValue()); 677 } 678 679 // 设置工作表行高和列宽 680 // 读取Luckysheet插件编辑的表格 681 setSheetWidthHeight(sheet, mapping.getLstRowHeight(), mapping.getLstColWidth()); 682 // 读取excel软件编辑的表格 683// setSheetWidthHeight(sheet, mapAllHeight, mapAllWidth); 684 685 // 设置工作表样式 686 setSheetStyle(sheet, mapAllStyle); 687 688 // 创建临时模板文件 689 File tmpFile = CZFileUtil.createExcelTemplateFile(); 690 FileOutputStream fos = new FileOutputStream(tmpFile); 691 workbook.write(fos); 692 workbook.close(); 693 fos.flush(); 694 fos.close(); 695 696// // 文件转byte[] 697// byte[] bytes = CZFileUtil.file2Bytes(tmpFile); 698 // 文件转multipartFile 699 MultipartFile multipartFile = MultipartFileUtil.getMultipartFile(tmpFile); 700 // 删除临时模板文件 701 FileUtil.del(tmpFile); 702 703 return multipartFile; 704 } 705 706 /** 707 * 创建导出模板 708 * @param templateFile 模板excel文件(主要取其样式,在原样式上生成新的模板) 709 * @param mapping 映射数据 710 * @return 711 */ 712 @SneakyThrows 713 public static MultipartFile createExportTemplate(MultipartFile templateFile, ExcelMapping mapping) { 714 log.info("createExportTemplate mapping: {}", JSON.toJSONString(mapping)); 715 716 Workbook workbook = null; 717 if (templateFile.getOriginalFilename().endsWith(".xls")) { 718 workbook = new HSSFWorkbook(templateFile.getInputStream()); 719 } 720 if (templateFile.getOriginalFilename().endsWith(".xlsx")) { 721 workbook = new XSSFWorkbook(templateFile.getInputStream()); 722 } 723 724 // worksheet 725 Sheet sheet = workbook.getSheetAt(0); 726 if (ObjectUtil.isNull(sheet)) { 727 throw new NullPointerException("加载sheet失败"); 728 } 729 730 // 获取所有单元格样式 731 Map<Integer, Map<Integer, CellStyle>> mapAllStyle = new HashMap<>(); 732 Map<Integer, Short> mapAllHeight = new HashMap<>(); 733 Map<Integer, Integer> mapAllWidth = new HashMap<>(); 734 getSheetStyle(sheet, mapAllStyle, mapAllHeight, mapAllWidth); 735 736 // 创建所有行 737 Map<Integer, Row> allRow = createAllRow(sheet, mapping); 738 log.info("createExportTemplate allRow size: {}", allRow.size()); 739 if (ObjectUtil.isNull(allRow.get(0))) { 740 throw new NullPointerException("创建单元格失败"); 741 } 742 743 // A1单元格 744 Cell a1 = allRow.get(0).getCell(0); 745 CellStyle a1Style = null; 746 if (ObjectUtil.isNull(a1)) { 747 allRow.get(0).createCell(0, CellType.STRING); 748 a1 = allRow.get(0).getCell(0); 749 } 750 // 创建所有列及映射 751 Map<Integer, Map<Integer, Cell>> allCell = createAllCell(allRow, mapping); 752 753 // 设置A1批注 754 String a1Rich = ConvertUtil.numberToLetter(mapping.getMaxCol()) + (mapping.getMaxRow() + 1); 755 addComment(a1, "jx:area(lastCell=\"" + a1Rich + "\")", null); 756 if (ObjectUtil.isNotNull(a1) && StrUtil.isNotEmpty(a1.getStringCellValue())) { 757 a1.setCellValue(a1.getStringCellValue()); 758 } 759 760 // 设置工作表行高和列宽 761 // 读取Luckysheet插件编辑的表格 762 setSheetWidthHeight(sheet, mapping.getLstRowHeight(), mapping.getLstColWidth()); 763 // 读取excel软件编辑的表格 764// setSheetWidthHeight(sheet, mapAllHeight, mapAllWidth); 765 766 // 设置工作表样式 767 setSheetStyle(sheet, mapAllStyle); 768 769 // 创建临时模板文件 770 File tmpFile = CZFileUtil.createExcelTemplateFile(); 771 FileOutputStream fos = new FileOutputStream(tmpFile); 772 workbook.write(fos); 773 workbook.close(); 774 fos.flush(); 775 fos.close(); 776 777// // 文件转byte[] 778// byte[] bytes = CZFileUtil.file2Bytes(tmpFile); 779 // 文件转multipartFile 780 MultipartFile multipartFile = MultipartFileUtil.getMultipartFile(tmpFile); 781 // 删除临时模板文件 782 FileUtil.del(tmpFile); 783 784 return multipartFile; 785 } 786 787 @SneakyThrows 788 public static ExcelMapping getTemplateFileMaxRowAndCol(MultipartFile templateFile) { 789 Workbook workbook = null; 790 if (templateFile.getOriginalFilename().endsWith(".xls")) { 791 workbook = new HSSFWorkbook(templateFile.getInputStream()); 792 } 793 if (templateFile.getOriginalFilename().endsWith(".xlsx")) { 794 workbook = new XSSFWorkbook(templateFile.getInputStream()); 795 } 796 797 // worksheet 798 Sheet sheet = workbook.getSheetAt(0); 799 if (ObjectUtil.isNull(sheet)) { 800 throw new NullPointerException("加载sheet失败"); 801 } 802 803 int maxRow = sheet.getLastRowNum(); 804 int maxCol = 0; 805 for (int i = 0; i < maxRow + 1; i++) { 806 Row row = sheet.getRow(i); 807 if (ObjectUtil.isNotNull(row)) { 808 short lastCellNum = row.getLastCellNum(); 809 if (lastCellNum > maxCol) { 810 maxCol = lastCellNum; 811 } 812 } 813 } 814 815 ExcelMapping mapping = new ExcelMapping(); 816 mapping.setMaxRow(maxRow); 817 mapping.setMaxCol(maxCol); 818 819 return mapping; 820 } 821 822 /** 823 * 获取所有单元格样式 824 * @param sheet 825 * @return 826 */ 827 private static void getSheetStyle(Sheet sheet, 828 Map<Integer, Map<Integer, CellStyle>> mapAllStyle, 829 Map<Integer, Short> mapAllHeight, 830 Map<Integer, Integer> mapAllWidth) { 831 int maxRow = sheet.getLastRowNum(); 832 int maxCol = 0; 833 log.info("getSheetStyle maxRow: {}", maxRow); 834 if (maxRow < 0) { 835 return; 836 } 837 838 // 遍历行 839 for (int r = 0; r < maxRow + 1; r++) { 840 Row row = sheet.getRow(r); 841 // 行高 842 if (ObjectUtil.isNull(row)) { 843 row = sheet.createRow(r); 844 } 845 mapAllHeight.put(r, row.getHeight()); 846 847 // 获取当前行列数 848 int cols = row.getLastCellNum(); 849 log.info("getSheetStyle cols: {}", cols); 850 if (cols > maxCol) { 851 maxCol = cols; 852 } 853 854 // 遍历列 855 Map<Integer, CellStyle> mapStyle = new HashMap<>(); 856 for (int c = 0; c < cols + 1; c++) { 857 Cell cell = row.getCell(c); 858 if (ObjectUtil.isNull(cell)) { 859 continue; 860 } 861 862 CellStyle cellStyle = cell.getCellStyle(); 863 if (ObjectUtil.isNotNull(cellStyle)) { 864 mapStyle.put(c, cellStyle); 865 } 866 } 867 868 // 样式 869 if (CollectionUtil.isNotEmpty(mapStyle)) { 870 mapAllStyle.put(r, mapStyle); 871 } 872 } 873 874 log.info("getSheetStyle maxCol: {}", maxCol); 875 // 遍历列 876 for (int c = 0; c < maxCol + 1; c++) { 877 if (ObjectUtil.isNull(sheet.getRow(0).getCell(c))) { 878 sheet.getRow(0).createCell(c, CellType.STRING); 879 } 880 881 mapAllWidth.put(c, sheet.getColumnWidth(c)); 882 } 883 884 log.info("getSheetStyle allHeight: {}", JSON.toJSONString(mapAllHeight)); 885 log.info("getSheetStyle allWidth: {}", JSON.toJSONString(mapAllWidth)); 886 } 887 888 /** 889 * 设置工作表样式 890 * @param sheet 891 * @param mapAllStyle 892 */ 893 private static void setSheetStyle(Sheet sheet, Map<Integer, Map<Integer, CellStyle>> mapAllStyle) { 894 if (ObjectUtil.isNull(sheet) || CollectionUtil.isEmpty(mapAllStyle)) { 895 return; 896 } 897 898 for (Map.Entry<Integer, Map<Integer, CellStyle>> entry : mapAllStyle.entrySet()) { 899 Integer r = entry.getKey(); 900 Map<Integer, CellStyle> mapStyle = entry.getValue(); 901 if (CollectionUtil.isEmpty(mapStyle)) { 902 continue; 903 } 904 905 for (Map.Entry<Integer, CellStyle> e : mapStyle.entrySet()) { 906 Integer c = e.getKey(); 907 CellStyle style = e.getValue(); 908 if (ObjectUtil.isNull(style)) { 909 continue; 910 } 911 912 sheet.getRow(r).getCell(c).setCellStyle(style); 913 } 914 } 915 } 916 917 /** 918 * 设置工作表行高和列宽 919 * https://zhidao.baidu.com/question/721783003339700525.html 920 * @param sheet 921 */ 922 private static void setSheetWidthHeight(Sheet sheet, 923 Map<Integer, Short> mapAllHeight, 924 Map<Integer, Integer> mapAllWidth) { 925 if (ObjectUtil.isNull(sheet)) { 926 return; 927 } 928 929 // 设置行高 930 if (CollectionUtil.isNotEmpty(mapAllHeight)) { 931 for (Map.Entry<Integer, Short> entry : mapAllHeight.entrySet()) { 932 if (ObjectUtil.isNull(entry)) { 933 continue; 934 } 935 936 // 行高转换计算,可直接等于获取到的列宽值 937 // 实际行高 = 获取getWidth / 20 938// BigDecimal h = Convert.toBigDecimal(entry.getValue()).divide(new BigDecimal("20.00"), 3, RoundingMode.HALF_UP); 939// h.multiply(new BigDecimal("20.00")).shortValue(); 940 sheet.getRow(entry.getKey()).setHeight(entry.getValue()); 941 } 942 } 943 944 // 设置列宽 945 if (CollectionUtil.isNotEmpty(mapAllWidth)) { 946 for (Map.Entry<Integer, Integer> entry : mapAllWidth.entrySet()) { 947 if (ObjectUtil.isNull(entry)) { 948 continue; 949 } 950 951 // 列宽转换计算,可直接等于获取到的列宽值 952 // 实际列宽 = 获取getHeitht / 264 953// BigDecimal w = Convert.toBigDecimal(entry.getValue()).divide(new BigDecimal("264.00"), 3, RoundingMode.HALF_UP); 954// log.info("setSheetWidthHeight w: {}", w); 955// w.multiply(new BigDecimal("264.00")).intValue(); 956 sheet.setColumnWidth(entry.getKey(), entry.getValue()); 957 } 958 } 959 } 960 961 /** 962 * 设置工作表行高和列宽 963 * https://zhidao.baidu.com/question/721783003339700525.html 964 * @param sheet 965 * @param lstRowHeight 966 * @param lstColWidth 967 */ 968 private static void setSheetWidthHeight(Sheet sheet, List<Short> lstRowHeight, List<Short> lstColWidth) { 969 if (ObjectUtil.isNull(sheet)) { 970 return; 971 } 972 973 // 设置行高 974 if (CollectionUtil.isNotEmpty(lstRowHeight)) { 975 short pre = 0; 976 for (int r = 0; r < lstRowHeight.size(); r ++) { 977 Row row = sheet.getRow(r); 978 if (ObjectUtil.isNull(row)) { 979 row = sheet.createRow(r); 980 } 981 982 // 实际行高 = 所传高度 * 20 983 row.setHeight(Convert.toShort((lstRowHeight.get(r) - pre) * 20)); 984 pre = lstRowHeight.get(r); 985 } 986 } 987 988 // 设置列宽 989 if (CollectionUtil.isNotEmpty(lstColWidth)) { 990 int pre = 0; 991 for (int c = 0; c < lstColWidth.size(); c ++) { 992 Cell cell = sheet.getRow(0).getCell(c); 993 if (ObjectUtil.isNull(cell)) { 994 cell = sheet.getRow(0).createCell(c); 995 } 996 997 // 列宽转换计算 998 // 实际列宽 = 所传宽度 * 26.4 999 sheet.setColumnWidth(c, Convert.toInt((lstColWidth.get(c) - pre) * 26.4)); 1000 pre = lstColWidth.get(c); 1001 } 1002 } 1003 } 1004 1005 /** 1006 * 初始化创建所有行 1007 * @param sheet 1008 * @param mapping 1009 * @return 1010 */ 1011 private static Map<Integer, Row> createAllRow(Sheet sheet, ExcelMapping mapping) { 1012 if (ObjectUtil.isNull(sheet) || ObjectUtil.isNull(mapping)) { 1013 return null; 1014 } 1015 1016 Map<Integer, Row> mapAllRows = new HashMap<>(); 1017 // 涉及的所有行索引 1018 Set<Integer> rows = new HashSet<>(); 1019 1020 // 自定义涉及的行索引 1021 if (CollectionUtil.isNotEmpty(mapping.getLstCustom())) { 1022 Set<Integer> setRows = mapping.getLstCustom().stream().map(ExcelMappingCustom::getRow).collect(Collectors.toSet()); 1023 if (CollectionUtil.isNotEmpty(setRows)) { 1024 rows.addAll(setRows); 1025 } 1026 } 1027 1028 // 对象涉及的行索引 1029 if (CollectionUtil.isNotEmpty(mapping.getLstObject())) { 1030 Set<Integer> setRows = mapping.getLstObject().stream().map(ExcelMappingObject::getRow).collect(Collectors.toSet()); 1031 if (CollectionUtil.isNotEmpty(setRows)) { 1032 rows.addAll(setRows); 1033 } 1034 } 1035 1036 // 对象涉及的行索引 1037 if (ObjectUtil.isNotNull(mapping.getMappingLoop()) && CollectionUtil.isNotEmpty(mapping.getMappingLoop().getLstData())) { 1038 Set<Integer> setRows = mapping.getMappingLoop().getLstData().stream().map(ExcelMappingObject::getRow).collect(Collectors.toSet()); 1039 if (CollectionUtil.isNotEmpty(setRows)) { 1040 rows.addAll(setRows); 1041 } 1042 } 1043 1044 // 如果为空或不存在第一行,则添加 1045 if (CollectionUtil.isEmpty(rows) || !rows.contains(0)) { 1046 rows.add(0); 1047 } 1048 // 获取或创建各行 1049 for (Integer row : rows) { 1050 Row excelRow = sheet.getRow(row); 1051 if (ObjectUtil.isNull(excelRow)) { 1052 excelRow = sheet.createRow(row); 1053 } 1054 if (ObjectUtil.isNotNull(excelRow)) { 1055 mapAllRows.put(row, excelRow); 1056 } 1057 } 1058 1059 return mapAllRows; 1060 } 1061 1062 /** 1063 * 初始化创建所有列 1064 * @param mapAllRows 1065 * @param mapping 1066 * @return 1067 */ 1068 private static Map<Integer, Map<Integer, Cell>> createAllCell(Map<Integer, Row> mapAllRows, ExcelMapping mapping) { 1069 if (CollectionUtil.isEmpty(mapAllRows) || ObjectUtil.isNull(mapping)) { 1070 return null; 1071 } 1072 1073 Map<Integer, Map<Integer, Cell>> mapAllCells = new HashMap<>(); 1074 1075 // 自定义数据(如:标题) 1076 if (CollectionUtil.isNotEmpty(mapping.getLstCustom())) { 1077 for (ExcelMappingCustom custom : mapping.getLstCustom()) { 1078 Row row = mapAllRows.get(custom.getRow()); 1079 if (ObjectUtil.isNull(row)) { 1080 throw new NullPointerException("行单元格模板未找到"); 1081 } 1082 1083 Map<Integer, Cell> mapCells = mapAllCells.get(custom.getRow()); 1084 if (CollectionUtil.isEmpty(mapCells)) { 1085 mapCells = new HashMap<>(); 1086 } 1087 1088 // 单元格内容 1089 Cell cell = row.getCell(custom.getCol()); 1090 if (ObjectUtil.isNull(cell)) { 1091 cell = row.createCell(custom.getCol(), CellType.STRING); 1092 } 1093 1094 // 单元格样式 1095 CellStyle cellStyle = cell.getCellStyle(); 1096 cell.setCellValue(custom.getContent()); 1097 // 设置样式 1098 if (ObjectUtil.isNotNull(cellStyle)) { 1099 cell.setCellStyle(cellStyle); 1100 } 1101 1102 mapCells.put(custom.getCol(), cell); 1103 mapAllCells.put(custom.getRow(), mapCells); 1104 } 1105 } 1106 1107 // 对象数据 1108 if (CollectionUtil.isNotEmpty(mapping.getLstObject())) { 1109 for (ExcelMappingObject object : mapping.getLstObject()) { 1110 Row row = mapAllRows.get(object.getRow()); 1111 if (ObjectUtil.isNull(row)) { 1112 throw new NullPointerException("行单元格模板未找到"); 1113 } 1114 1115 Map<Integer, Cell> mapCells = mapAllCells.get(object.getRow()); 1116 if (CollectionUtil.isEmpty(mapCells)) { 1117 mapCells = new HashMap<>(); 1118 } 1119 1120 StringBuilder sb = new StringBuilder(); 1121 sb.append("${object.").append(object.getObjectItemName()).append("}"); 1122 1123 // 单元格内容 1124 Cell cell = row.getCell(object.getCol()); 1125 if (ObjectUtil.isNull(cell)) { 1126 cell = row.createCell(object.getCol(), CellType.STRING); 1127 } 1128 1129 // 单元格样式 1130 CellStyle cellStyle = cell.getCellStyle(); 1131 cell.setCellValue(sb.toString()); 1132 // 设置样式 1133 if (ObjectUtil.isNotNull(cellStyle)) { 1134 cell.setCellStyle(cellStyle); 1135 } 1136 1137 mapCells.put(object.getCol(), cell); 1138 mapAllCells.put(object.getRow(), mapCells); 1139 } 1140 } 1141 1142 // 循环数据 1143 if (ObjectUtil.isNotNull(mapping.getMappingLoop()) && CollectionUtil.isNotEmpty(mapping.getMappingLoop().getLstData())) { 1144 List<ExcelMappingObject> lstData = mapping.getMappingLoop().getLstData(); 1145 Integer minCol = lstData.stream().map(ExcelMappingObject::getCol).min((v1, v2) -> v1 - v2).get(); 1146 Integer maxCol = lstData.stream().map(ExcelMappingObject::getCol).max((v1, v2) -> v1 - v2).get(); 1147 if (ObjectUtil.isNull(minCol)) { 1148 minCol = 0; 1149 } 1150 1151 for (ExcelMappingObject object : lstData) { 1152// log.info("createAllCell allRows key: {}, object row: {}", JSON.toJSONString(mapAllRows.keySet()), object.getRow()); 1153 Row row = mapAllRows.get(object.getRow()); 1154 if (ObjectUtil.isNull(row)) { 1155 throw new NullPointerException("行单元格模板未找到"); 1156 } 1157 1158 Map<Integer, Cell> mapCells = mapAllCells.get(object.getRow()); 1159 if (CollectionUtil.isEmpty(mapCells)) { 1160 mapCells = new HashMap<>(); 1161 } 1162 1163 StringBuilder sb = new StringBuilder(); 1164 sb.append("${item.").append(object.getObjectItemName()).append("}"); 1165 1166 // 单元格内容 1167 Cell cell = row.getCell(object.getCol()); 1168 if (ObjectUtil.isNull(cell)) { 1169 cell = row.createCell(object.getCol(), CellType.STRING); 1170 } 1171 1172 // 单元格样式 1173 CellStyle cellStyle = cell.getCellStyle(); 1174 cell.setCellValue(sb.toString()); 1175 // 设置样式 1176 if (ObjectUtil.isNotNull(cellStyle)) { 1177 cell.setCellStyle(cellStyle); 1178 } 1179 1180 mapCells.put(object.getCol(), cell); 1181 mapAllCells.put(object.getRow(), mapCells); 1182 } 1183 1184 // jx:each(items="consignees" var="consignee" lastCell="E4") 1185// String loopRich = ConvertUtil.numberToLetter(maxCol) + (mapping.getMappingLoop().getMaxRow() + 1); 1186 // 循环体需占满最大列,否则循环体下面如果有统计数据是超过循环数据最大列的话,超出列的统计数据则会显示在设计时所以的行列位置,并不会显示最后行 1187 String loopRich = ConvertUtil.numberToLetter(mapping.getMaxCol()) + (mapping.getMappingLoop().getMaxRow() + 1); 1188 Cell firstLoopCell = mapAllCells.get(mapping.getMappingLoop().getMinRow()).get(minCol); 1189 if (ObjectUtil.isNull(firstLoopCell)) { 1190 Row row = mapAllRows.get(mapping.getMappingLoop().getMinRow()); 1191 if (ObjectUtil.isNull(row)) { 1192 throw new NullPointerException("行单元格模板未找到"); 1193 } 1194 firstLoopCell = row.createCell(minCol, CellType.STRING); 1195 } 1196 addComment(firstLoopCell, "jx:each(items=\"items\" var=\"item\" lastCell=\"" + loopRich + "\")", null); 1197 } 1198 1199 return mapAllCells; 1200 } 1201 1202 /** ========================================= create import template (xml) ========================================= */ 1203 1204 /** 1205 * 创建导入xml映射文件 1206 * Excel行或列索引从0开始(即行或列行数据,第一行或第一列,索引为0) 1207 * @param sheetName sheet名称 1208 * @param startRowIndex 真实数据起始行 1209 * @param lstMapper 数据映射(excel字段名和对应的对应属性名) 1210 * @param className 映射对象全类型 1211 * @param 1212 */ 1213// public static Document createImportXml(String sheetName, Integer startRowIndex, 1214// List<ExcelImportMapper> lstMapper, String className) { 1215// if (StrUtil.isEmpty(sheetName)) { 1216// throw new NullArgumentException("必须指定导入的Excel表Sheet名称"); 1217// } 1218// if (ObjectUtil.isNull(startRowIndex)) { 1219// throw new NullArgumentException("必须指定真实数据所在起始行"); 1220// } 1221// 1222// // 创建xml文档 1223// Document xml = XmlUtil.createXml(); 1224// 1225// // workbook 1226// Element workbook = xml.createElement("workbook"); 1227// 1228// // worksheet 1229// Element worksheet = xml.createElement("worksheet"); 1230// if (StrUtil.isNotEmpty(sheetName)) { 1231// worksheet.setAttribute("name", sheetName); 1232// } 1233// 1234// // 排除(跳过)真实数据起始行前面的行数(如果不跳过非真实数据行,则会出现脏数据,loop即会把非真实数据行读取进来) 1235// // exclusionSection 1236// Element sectionExclusion = xml.createElement("section"); 1237// sectionExclusion.setAttribute("startRow", "0"); 1238// if (startRowIndex > 0) { 1239// sectionExclusion.setAttribute("endRow", Convert.toStr(startRowIndex - 1)); 1240// } else { 1241// sectionExclusion.setAttribute("endRow", "0"); 1242// } 1243// 1244// // loop 1245// Element loop = xml.createElement("loop"); 1246// loop.setAttribute("startRow", Convert.toStr(startRowIndex)); 1247// loop.setAttribute("endRow", Convert.toStr(startRowIndex)); 1248// loop.setAttribute("items", "items"); 1249// loop.setAttribute("var", "item"); 1250// loop.setAttribute("varType", className); 1251// 1252// // sectionData 1253// Element sectionData = xml.createElement("section"); 1254// sectionData.setAttribute("startRow", Convert.toStr(startRowIndex)); 1255// sectionData.setAttribute("endRow", Convert.toStr(startRowIndex)); 1256// 1257// // mapping 1258// for (ExcelImportMapper mapper : lstMapper) { 1259// Element mapping = xml.createElement("mapping"); 1260// mapping.setAttribute("row", Convert.toStr(startRowIndex)); 1261// mapping.setAttribute("col", Convert.toStr(mapper.getColIndex())); 1262// mapping.setTextContent("item." + mapper.getMapperField()); 1263// 1264// sectionData.appendChild(mapping); 1265// } 1266// 1267// // loopbreakcondition 1268// Element loopbreakcondition = xml.createElement("loopbreakcondition"); 1269// 1270// // rowcheck 1271// Element rowcheck = xml.createElement("rowcheck"); 1272// rowcheck.setAttribute("offset", "0"); 1273// 1274// // cellcheck 1275// Element cellcheck = xml.createElement("cellcheck"); 1276// cellcheck.setAttribute("offset", "0"); 1277// 1278// rowcheck.appendChild(cellcheck); 1279// loopbreakcondition.appendChild(rowcheck); 1280// loop.appendChild(loopbreakcondition); 1281// loop.appendChild(sectionData); 1282// worksheet.appendChild(sectionExclusion); 1283// worksheet.appendChild(loop); 1284// workbook.appendChild(worksheet); 1285// xml.appendChild(workbook); 1286// 1287// return xml; 1288// } 1289 1290 /** 1291 * 创建导入模板xml文档 1292 * @param mapping 1293 * @return 1294 */ 1295 @SneakyThrows 1296 public static MultipartFile createImportXmlToMultipartFile(ExcelMapping mapping) { 1297 Document xml = createImportXml(mapping); 1298 if (ObjectUtil.isNull(xml)) { 1299 return null; 1300 } 1301 1302 File tmpFile = XMLUtil.toFile(xml); 1303 1304 MultipartFile multipartFile = MultipartFileUtil.getMultipartFile(tmpFile); 1305 1306 // 删除临时文件 1307 FileUtil.del(tmpFile); 1308 1309 return multipartFile; 1310 } 1311 1312 /** 1313 * 创建导入模板xml文档 1314 * @param mapping 1315 * @return 1316 */ 1317 public static Document createImportXml(ExcelMapping mapping) { 1318 if (ObjectUtil.isNull(mapping) || ObjectUtil.isNull(mapping.getMappingLoop())) { 1319 throw new NullArgumentException("映射内容不能为空"); 1320 } 1321 1322 // 创建xml文档 1323 Document xml = XmlUtil.createXml(); 1324 // workbook 1325 Element workbook = xml.createElement("workbook"); 1326 1327 // 设置工作Sheet索引(默认指定为第一个Sheet) 1328 if (ObjectUtil.isNull(mapping.getSheetIndex())) { 1329 mapping.setSheetIndex(0); 1330 } 1331 1332 // worksheet 1333 Element worksheet = xml.createElement("worksheet"); 1334 worksheet.setAttribute("idx", Convert.toStr(mapping.getSheetIndex())); 1335 1336 // 创建占位元素 1337 Element exclusionSection = createExclusionSection(xml, 0, mapping.getMappingLoop().getMinRow()); 1338 worksheet.appendChild(exclusionSection); 1339 1340 // 创建loop 1341 Element loop = createElementLoop(xml, mapping.getMappingLoop()); 1342 worksheet.appendChild(loop); 1343 1344 workbook.appendChild(worksheet); 1345 xml.appendChild(workbook); 1346 1347 return xml; 1348 } 1349 1350 /** 1351 * 创建Loop元素 1352 * @param xml 1353 * @param mappingLoop 1354 * @return 1355 */ 1356 private static Element createElementLoop(Document xml, ExcelMappingLoop mappingLoop) { 1357 // 找出loop的起始行和截止行索引 1358 Integer startRow = mappingLoop.getMinRow(); // lstLoop.stream().map(ExcelMappingObject::getRow).min((v1, v2) -> v1 - v2).get(); 1359 Integer endRow= mappingLoop.getMaxRow(); // lstLoop.stream().map(ExcelMappingObject::getCol).max((v1, v2) -> v1 - v2).get(); 1360 System.out.println("createElementLoop endRow: " + endRow); 1361 1362 String varType = mappingLoop.getVarType(); 1363 if (StrUtil.isEmpty(varType)) { 1364 throw new NullArgumentException("必须指定映射对象的全类名"); 1365 } 1366 1367 // loop 1368 Element loop = xml.createElement("loop"); 1369 loop.setAttribute("startRow", Convert.toStr(startRow)); 1370 loop.setAttribute("endRow", Convert.toStr(endRow)); 1371 loop.setAttribute("items", "items"); 1372 loop.setAttribute("var", "item"); 1373 loop.setAttribute("varType", varType); 1374 1375 // sectionData 1376 Element sectionData = xml.createElement("section"); 1377 sectionData.setAttribute("startRow", Convert.toStr(startRow)); 1378 sectionData.setAttribute("endRow", Convert.toStr(endRow)); 1379 1380 // mapping 1381 for (ExcelMappingObject item : mappingLoop.getLstData()) { 1382 Element mapping = xml.createElement("mapping"); 1383 mapping.setAttribute("row", Convert.toStr(item.getRow())); 1384 mapping.setAttribute("col", Convert.toStr(item.getCol())); 1385 mapping.setTextContent("item." + item.getObjectItemName()); 1386 1387 sectionData.appendChild(mapping); 1388 } 1389 1390 // loopbreakcondition 1391 Element loopbreakcondition = xml.createElement("loopbreakcondition"); 1392 1393 // rowcheck 1394 Element rowcheck = xml.createElement("rowcheck"); 1395 rowcheck.setAttribute("offset", "0"); 1396 1397 // cellcheck 1398 for (ExcelMappingObject item : mappingLoop.getLstData()) { 1399 Element cellcheck = xml.createElement("cellcheck"); 1400 cellcheck.setAttribute("offset", Convert.toStr(item.getCol())); 1401 1402 rowcheck.appendChild(cellcheck); 1403 } 1404 1405 loopbreakcondition.appendChild(rowcheck); 1406 loop.appendChild(loopbreakcondition); 1407 loop.appendChild(sectionData); 1408 1409 return loop; 1410 } 1411 1412 /** 1413 * 创建排除(占位)Section元素 1414 * 排除(跳过)真实数据起始行前面的行数(如果不跳过非真实数据行,则会出现脏数据,loop即会把非真实数据行读取进来) 1415 * @param xml 1416 * @param startRow 1417 * @param endRow 1418 * @return 1419 */ 1420 private static Element createExclusionSection(Document xml, Integer startRow, Integer endRow) { 1421 // exclusionSection 1422 Element sectionExclusion = xml.createElement("section"); 1423 sectionExclusion.setAttribute("startRow", Convert.toStr(startRow)); 1424 if (ObjectUtil.isNotNull(endRow) && endRow > 0) { 1425 sectionExclusion.setAttribute("endRow", Convert.toStr(endRow - 1)); 1426 } else { 1427 sectionExclusion.setAttribute("endRow", "0"); 1428 } 1429 1430 return sectionExclusion; 1431 } 1432 1433 1434 /** ========================================= read ========================================= */ 1435 1436 1437 /** 1438 * 读取excel文件指定行的字段信息 1439 * @param file excel文件 1440 * @param sheetIdx sheet索引(0开始) 1441 * @param row 字段名所在行数 1442 * @return 1443 */ 1444 @SneakyThrows 1445 public static List<ExcelFieldInfo> readExcelByRow(MultipartFile file, Integer sheetIdx, Integer row) { 1446 // 默认sheet0(第一个sheet) 1447 if (ObjectUtil.isNull(sheetIdx)) { 1448 sheetIdx = 0; 1449 } 1450 // 默认第一行 1451 if (ObjectUtil.isNull(row)) { 1452 row = 1; 1453 } 1454 1455 Workbook workbook = null; 1456 if (file.getOriginalFilename().endsWith(".xls")) { 1457 workbook = new HSSFWorkbook(file.getInputStream()); 1458 } 1459 if (file.getOriginalFilename().endsWith(".xlsx")) { 1460 workbook = new XSSFWorkbook(file.getInputStream()); 1461 } 1462 1463 // worksheet 1464 Sheet sheet = workbook.getSheetAt(sheetIdx); 1465 int lastRowNum = sheet.getLastRowNum(); 1466 if (lastRowNum < 0) { 1467 throw new Exception("Excel文件Sheet不能为空"); 1468 } 1469 1470 // 字段名所在行索引 1471 int rowIdx = row - 1; 1472 if (rowIdx > lastRowNum || rowIdx < 0) { 1473 throw new Exception("指定字段名行数不正确"); 1474 } 1475 1476 // 获取指定行数据 1477 Row r = sheet.getRow(rowIdx); 1478 if (ObjectUtil.isNull(r)) { 1479 throw new Exception("指定字段名行数据不能为空"); 1480 } 1481 1482 // 获取指定行列数(总索引总) 1483 short lastCellNum = r.getLastCellNum(); 1484 if (lastCellNum < 0) { 1485 throw new Exception("指定字段名行数据列不能为空"); 1486 } 1487 1488 List<ExcelFieldInfo> lstFields = new ArrayList<>(); 1489 for (int i = 0; i < lastCellNum; i++) { 1490 Cell cell = r.getCell(i); 1491 String cellVal = getCellValueByCell(cell); 1492 if (StrUtil.isEmpty(cellVal)) { 1493 continue; 1494 } 1495 1496 ExcelFieldInfo fieldInfo = new ExcelFieldInfo(); 1497 ConvertUtil.numberToLetter(i); 1498 fieldInfo.setCell(ConvertUtil.numberToLetter(i) + row); 1499 fieldInfo.setRowIdx(rowIdx); 1500 fieldInfo.setColIdx(i); 1501 fieldInfo.setFieldName(cellVal); 1502 1503 lstFields.add(fieldInfo); 1504 } 1505 1506 return lstFields; 1507 } 1508 1509 /** 1510 * 获取单元格各类型值,返回字符串类型 1511 * 1512 * @param cell cell 1513 * @return String 1514 */ 1515 public static String getCellValueByCell(Cell cell) { 1516 //判断是否为null或空串 1517 if (cell == null || cell.toString().trim().equals("")) { 1518 return ""; 1519 } 1520 1521 String cellValue = ""; 1522 CellType cellType = cell.getCellType(); 1523 switch (cellType) { 1524 case NUMERIC: // 数字 1525 short format = cell.getCellStyle().getDataFormat(); 1526 if (HSSFDateUtil.isCellDateFormatted(cell)) { // 日期 1527 SimpleDateFormat sdf; 1528 String dtFormat = "yyyy-MM-dd HH:mm:ss"; 1529 if (format == 20 || format == 32) { 1530 dtFormat = "HH:mm"; 1531 } else if (format == 14 || format == 31 || format == 57 || format == 58) { 1532 dtFormat = "yyyy-MM-dd"; 1533 } else if (format == 179) { 1534 dtFormat = "HH:mm:ss"; 1535 } else { 1536 dtFormat = "yyyy-MM-dd HH:mm:ss"; 1537 } 1538 cellValue = DateUtil.format(cell.getDateCellValue(), dtFormat); 1539 } else { 1540 cell.setCellType(CellType.STRING); 1541 cellValue = cell.getStringCellValue(); 1542 } 1543 break; 1544 case STRING: // 字符串 1545 cellValue = cell.getStringCellValue(); 1546 break; 1547 case BOOLEAN: // Boolean 1548 cellValue = cell.getBooleanCellValue() + ""; 1549 break; 1550 case FORMULA: // 公式 1551 cell.setCellType(CellType.STRING); 1552 cellValue = cell.getStringCellValue(); 1553 break; 1554 case BLANK: // 空值 1555 cellValue = ""; 1556 break; 1557 case ERROR: // 故障 1558 cellValue = "ERROR VALUE"; 1559 break; 1560 default: 1561 cellValue = "UNKNOWN VALUE"; 1562 break; 1563 } 1564 1565 return cellValue; 1566 } 1567 1568 1569 1570 1571 /** ========================================= test function ========================================= */ 1572 1573 /** 1574 * 字符串转日期(double字符串转Date) 1575 * @param val 1576 * @return 1577 */ 1578 public static String toDateDash(String val) { 1579 if (StrUtil.isNotEmpty(val)) { 1580 Double date = Convert.toDouble(val); 1581 System.out.println("toDate: " + date); 1582 1583 return DateUtil.format(org.apache.poi.ss.usermodel.DateUtil.getJavaDate(date), "yyyy-MM-dd HH:mm:ss"); 1584 } 1585 1586 return val; 1587 } 1588 1589 /** 1590 * 字符串转日期(double字符串转Date) 1591 * @param val 1592 * @return 1593 */ 1594 public static String toDate(String val) { 1595// System.out.println("toDate: " + val); 1596 if (StrUtil.isNotEmpty(val)) { 1597 if (StrUtil.contains(val, "-")) { 1598// System.out.println("toDate1: " + val); 1599 return val; 1600 } else if (StrUtil.contains(val, "/")) { 1601// System.out.println("toDate2: " + val); 1602 return val.replaceAll("/", "-"); 1603 } else if (StrUtil.contains(val, "\\\\")) { 1604// System.out.println("toDate3: " + val); 1605 return val.replaceAll("\\\\", "-"); 1606 } else { 1607 Double date = Convert.toDouble(val); 1608// System.out.println("toDate4: " + date); 1609 1610 return doubleToDate(date); 1611 } 1612 } 1613 1614 return val; 1615 } 1616 1617 /** 1618 * double to date 1619 * @param date 1620 * @return 1621 */ 1622 public static String doubleToDate(Double date) { 1623// System.out.println("当前时间:"+ new Date()); 1624 // Calendar.getInstance() 获取Calendar实例,并获取系统默认的TimeZone 1625 Calendar calendar = Calendar.getInstance(); 1626// System.out.println("Calendar的系统默认TimeZone ID:" + 1627// calendar.getTimeZone().getID()); 1628// 1629 // 指定时区,例如意大利的罗马时区:Asia/Shanghai 1630 // 时区:https://blog.csdn.net/caicai1377/article/details/115873822 1631 // 罗马时区属于东1区,也就是UTC+1或GMT+1 1632 TimeZone itTimeZone = TimeZone.getTimeZone("Asia/Shanghai"); 1633 1634 // Calendar指定罗马时区 1635 calendar.setTimeZone(itTimeZone); 1636// System.out.println("Calendar指定TimeZone ID:" + itTimeZone.getID()); 1637 1638 // 夏令时时间,比标准时间快1小时,即3600000毫秒, 1639 // 根据系统时间计算,如果不在夏令时生效范围内,则为0毫秒,反之为3600000毫秒 1640 int dstOffset = calendar.get(Calendar.DST_OFFSET); 1641 // 取得与GMT之间的时间偏移量,例如罗马属于东1区,则时间偏移量为3600000毫秒 1642 int zoneOffset = calendar.get(Calendar.ZONE_OFFSET); 1643 1644// System.out.println("夏令时时间:"+dstOffset); 1645// System.out.println("时间偏移量:"+zoneOffset); 1646 // 系统时区偏移 1900/1/1 到 1970/1/1 的 25569 天 1647// int localOffset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET) / (60 * 1000); 1648// int localOffset = zoneOffset + dstOffset; 1649 // 上海时区与GMT时间偏移8小时 1650 int localOffset = zoneOffset; 1651// System.out.println("doubleToDate localOffset: " + localOffset); 1652 Date tDate = new Date(); 1653 tDate.setTime((long) ((date - 25569) * 24 * 3600 * 1000 - localOffset)); 1654 1655 return DateUtil.format(tDate, "yyyy-MM-dd HH:mm:ss"); 1656 } 1657 1658 /** 1659 * 字符串转日期(double字符串转Date) 1660 * @param val 1661 * @return 1662 */ 1663 public static String toDateSlash(String val) { 1664 if (StrUtil.isNotEmpty(val)) { 1665 Double date = Convert.toDouble(val); 1666 System.out.println("toDate: " + date); 1667 1668 return DateUtil.format(org.apache.poi.ss.usermodel.DateUtil.getJavaDate(date), "yyyy/MM/dd HH:mm:ss"); 1669 } 1670 1671 return val; 1672 } 1673 1674 // 格式化时间(自定义方法,可通过jxls-api加载后,在Excel内引用) 1675 public Object formatDate(Date dt){ 1676 if(ObjectUtil.isNotNull(dt)){ 1677 String dateStr = DateUtil.format(dt, "yyyy-MM-dd HH:mm:ss"); 1678 return dateStr; 1679 } 1680 1681 return "--"; 1682 } 1683 1684 // 时延-处理执行时间 ms -> s 且保留两位(自定义方法,可通过jxls-api加载后,在Excel内引用) 1685 public Object timeChange(Long time){ 1686 if(time != null){ 1687 DecimalFormat df = new DecimalFormat("0.00"); 1688 String result = df.format((double)time / 1000); 1689 return result.equals("0.00") ? "--" : result + "s"; 1690 } 1691 return "--"; 1692 } 1693 1694 // 超链接方法(自定义方法,可通过jxls-api加载后,在Excel内引用) 1695 public WritableCellValue myLink(String address, String title) { 1696 return new WritableHyperlink(address, title); 1697 } 1698 1699 public String getTitle() { 1700 return "导出收货人"; 1701 } 1702}