From 77f53cb991c4cc5a0db97f6d4b23aeb97bafbe65 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Fri, 13 Mar 2026 15:10:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84Excel=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=A0=BC=E5=BC=8F=E4=B8=8E=E8=B5=84=E4=BA=A7?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E4=B8=8B=E6=8B=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/excel/CcdiAssetInfoExcel.java | 4 + .../excel/CcdiStaffFmyRelationExcel.java | 2 + .../handler/TextFormatWriteHandler.java | 78 +++++++++++++++ .../info/collection/utils/EasyExcelUtil.java | 5 + .../utils/EasyExcelUtilTemplateTest.java | 98 +++++++++++++++++++ .../ruoyi/common/annotation/TextFormat.java | 17 ++++ 6 files changed, 204 insertions(+) create mode 100644 ccdi-info-collection/src/main/java/com/ruoyi/info/collection/handler/TextFormatWriteHandler.java create mode 100644 ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/TextFormat.java diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java index 32995c7..511be08 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java @@ -2,7 +2,9 @@ package com.ruoyi.info.collection.domain.excel; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.ruoyi.common.annotation.DictDropdown; import com.ruoyi.common.annotation.Required; +import com.ruoyi.common.annotation.TextFormat; import lombok.Data; import java.io.Serial; @@ -26,6 +28,7 @@ public class CcdiAssetInfoExcel implements Serializable { @ExcelProperty(value = "关系人证件号*", index = 0) @ColumnWidth(22) @Required + @TextFormat private String personId; /** 资产大类 */ @@ -75,6 +78,7 @@ public class CcdiAssetInfoExcel implements Serializable { /** 资产状态 */ @ExcelProperty(value = "资产状态*", index = 9) @ColumnWidth(14) + @DictDropdown(dictType = "ccdi_asset_status") @Required private String assetStatus; diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffFmyRelationExcel.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffFmyRelationExcel.java index ac147fa..50331f9 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffFmyRelationExcel.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiStaffFmyRelationExcel.java @@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.ruoyi.common.annotation.DictDropdown; import com.ruoyi.common.annotation.Required; +import com.ruoyi.common.annotation.TextFormat; import lombok.Data; import java.io.Serial; @@ -63,6 +64,7 @@ public class CcdiStaffFmyRelationExcel implements Serializable { @ExcelProperty(value = "关系人证件号码*", index = 6) @ColumnWidth(20) @Required + @TextFormat private String relationCertNo; /** 手机号码1 */ diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/handler/TextFormatWriteHandler.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/handler/TextFormatWriteHandler.java new file mode 100644 index 0000000..38f1a7a --- /dev/null +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/handler/TextFormatWriteHandler.java @@ -0,0 +1,78 @@ +package com.ruoyi.info.collection.handler; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import com.ruoyi.common.annotation.TextFormat; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Excel文本列写入处理器 + * + * @author ruoyi + */ +@Slf4j +public class TextFormatWriteHandler implements SheetWriteHandler { + + private final Class modelClass; + + public TextFormatWriteHandler(Class modelClass) { + this.modelClass = modelClass; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + Workbook workbook = writeWorkbookHolder.getWorkbook(); + Sheet sheet = writeSheetHolder.getSheet(); + CellStyle textStyle = createTextStyle(workbook); + + for (Field field : getAllFields(modelClass)) { + if (field.getAnnotation(TextFormat.class) == null) { + continue; + } + + Integer columnIndex = getColumnIndex(field); + if (columnIndex == null) { + log.warn("字段[{}]没有指定@ExcelProperty的index,跳过文本格式设置", field.getName()); + continue; + } + + sheet.setDefaultColumnStyle(columnIndex, textStyle); + log.info("成功将列[{}]设置为文本格式", columnIndex); + } + } + + private CellStyle createTextStyle(Workbook workbook) { + DataFormat dataFormat = workbook.createDataFormat(); + CellStyle style = workbook.createCellStyle(); + style.setDataFormat(dataFormat.getFormat("@")); + return style; + } + + private List getAllFields(Class clazz) { + List fields = new ArrayList<>(); + while (clazz != null && clazz != Object.class) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + clazz = clazz.getSuperclass(); + } + return fields; + } + + private Integer getColumnIndex(Field field) { + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + if (excelProperty != null && excelProperty.index() >= 0) { + return excelProperty.index(); + } + return null; + } +} diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java index f277d92..463f749 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/utils/EasyExcelUtil.java @@ -5,6 +5,7 @@ import com.alibaba.excel.write.handler.WriteHandler; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.ruoyi.info.collection.handler.DictDropdownWriteHandler; import com.ruoyi.info.collection.handler.RequiredFieldWriteHandler; +import com.ruoyi.info.collection.handler.TextFormatWriteHandler; import jakarta.servlet.http.HttpServletResponse; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -174,6 +175,7 @@ public class EasyExcelUtil { .sheet(sheetName) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .registerWriteHandler(new DictDropdownWriteHandler(clazz)) + .registerWriteHandler(new TextFormatWriteHandler(clazz)) .registerWriteHandler(new RequiredFieldWriteHandler(clazz)) .doWrite(List.of()); } catch (IOException e) { @@ -200,6 +202,7 @@ public class EasyExcelUtil { .sheet(sheetName) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .registerWriteHandler(new DictDropdownWriteHandler(clazz)) + .registerWriteHandler(new TextFormatWriteHandler(clazz)) .registerWriteHandler(new RequiredFieldWriteHandler(clazz)) .doWrite(List.of()); } catch (IOException e) { @@ -226,6 +229,7 @@ public class EasyExcelUtil { .sheet(sheetName) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .registerWriteHandler(new DictDropdownWriteHandler(clazz)) + .registerWriteHandler(new TextFormatWriteHandler(clazz)) .registerWriteHandler(new RequiredFieldWriteHandler(clazz)) .doWrite(list); } catch (IOException e) { @@ -253,6 +257,7 @@ public class EasyExcelUtil { .sheet(sheetName) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .registerWriteHandler(new DictDropdownWriteHandler(clazz)) + .registerWriteHandler(new TextFormatWriteHandler(clazz)) .registerWriteHandler(new RequiredFieldWriteHandler(clazz)) .doWrite(list); } catch (IOException e) { diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java new file mode 100644 index 0000000..70c2845 --- /dev/null +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/utils/EasyExcelUtilTemplateTest.java @@ -0,0 +1,98 @@ +package com.ruoyi.info.collection.utils; + +import com.ruoyi.common.core.domain.entity.SysDictData; +import com.ruoyi.common.utils.DictUtils; +import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel; +import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.ByteArrayInputStream; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; + +class EasyExcelUtilTemplateTest { + + @Test + void importTemplateWithDictDropdown_shouldAddAssetStatusDropdownToAssetTemplate() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + + try (MockedStatic mocked = mockStatic(DictUtils.class)) { + mocked.when(() -> DictUtils.getDictCache("ccdi_asset_status")) + .thenReturn(List.of( + buildDictData("正常"), + buildDictData("冻结"), + buildDictData("处置中"), + buildDictData("报废") + )); + + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiAssetInfoExcel.class, "亲属资产信息"); + } + + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) { + Sheet sheet = workbook.getSheetAt(0); + assertTrue(hasValidationOnColumn(sheet, 9), "资产状态列应包含下拉校验"); + } + } + + @Test + void importTemplateWithDictDropdown_shouldFormatCertificateColumnsAsText() throws Exception { + MockHttpServletResponse assetResponse = new MockHttpServletResponse(); + MockHttpServletResponse familyResponse = new MockHttpServletResponse(); + + try (MockedStatic mocked = mockStatic(DictUtils.class)) { + mocked.when(() -> DictUtils.getDictCache("ccdi_asset_status")) + .thenReturn(List.of(buildDictData("正常"))); + mocked.when(() -> DictUtils.getDictCache("ccdi_relation_type")) + .thenReturn(List.of(buildDictData("配偶"))); + mocked.when(() -> DictUtils.getDictCache("ccdi_indiv_gender")) + .thenReturn(List.of(buildDictData("男"))); + mocked.when(() -> DictUtils.getDictCache("ccdi_certificate_type")) + .thenReturn(List.of(buildDictData("居民身份证"))); + + EasyExcelUtil.importTemplateWithDictDropdown(assetResponse, CcdiAssetInfoExcel.class, "亲属资产信息"); + EasyExcelUtil.importTemplateWithDictDropdown(familyResponse, CcdiStaffFmyRelationExcel.class, "员工亲属关系信息"); + } + + try (Workbook assetWorkbook = WorkbookFactory.create(new ByteArrayInputStream(assetResponse.getContentAsByteArray())); + Workbook familyWorkbook = WorkbookFactory.create(new ByteArrayInputStream(familyResponse.getContentAsByteArray()))) { + assertTextColumn(assetWorkbook.getSheetAt(0), 0); + assertTextColumn(familyWorkbook.getSheetAt(0), 6); + } + } + + private void assertTextColumn(Sheet sheet, int columnIndex) { + CellStyle style = sheet.getColumnStyle(columnIndex); + assertNotNull(style, "文本列应设置默认样式"); + assertEquals("@", style.getDataFormatString(), "证件号列应使用文本格式"); + } + + private boolean hasValidationOnColumn(Sheet sheet, int columnIndex) { + for (DataValidation validation : sheet.getDataValidations()) { + for (CellRangeAddress address : validation.getRegions().getCellRangeAddresses()) { + if (address.getFirstColumn() <= columnIndex && address.getLastColumn() >= columnIndex) { + return true; + } + } + } + return false; + } + + private SysDictData buildDictData(String label) { + SysDictData dictData = new SysDictData(); + dictData.setDictLabel(label); + dictData.setDictValue(label); + return dictData; + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TextFormat.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TextFormat.java new file mode 100644 index 0000000..f40ab8c --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/TextFormat.java @@ -0,0 +1,17 @@ +package com.ruoyi.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel文本格式注解 + * 用于在生成Excel模板时将列设置为文本格式,避免证件号等内容被自动转成数值。 + * + * @author ruoyi + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TextFormat { +}