Add import dropdown validation
This commit is contained in:
@@ -2,19 +2,36 @@ package com.ruoyi.info.collection.utils;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.handler.WriteHandler;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import com.ruoyi.common.annotation.DictDropdown;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
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;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.DataValidation;
|
||||
import org.apache.poi.ss.usermodel.DataValidationConstraint;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -77,8 +94,10 @@ public class EasyExcelUtil {
|
||||
* @return 数据列表
|
||||
*/
|
||||
public static <T> List<T> importExcel(String fileName, Class<T> clazz) {
|
||||
try {
|
||||
return EasyExcel.read(fileName).head(clazz).sheet().doReadSync();
|
||||
try (InputStream inputStream = java.nio.file.Files.newInputStream(java.nio.file.Path.of(fileName))) {
|
||||
return importExcel(inputStream, clazz);
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导入Excel失败", e);
|
||||
}
|
||||
@@ -94,7 +113,11 @@ public class EasyExcelUtil {
|
||||
*/
|
||||
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz) {
|
||||
try {
|
||||
return EasyExcel.read(inputStream).head(clazz).sheet().doReadSync();
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
validateDictDropdownTemplate(bytes, clazz, null);
|
||||
return EasyExcel.read(new ByteArrayInputStream(bytes)).head(clazz).sheet().doReadSync();
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导入Excel失败", e);
|
||||
}
|
||||
@@ -111,7 +134,11 @@ public class EasyExcelUtil {
|
||||
*/
|
||||
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz, String sheetName) {
|
||||
try {
|
||||
return EasyExcel.read(inputStream).head(clazz).sheet(sheetName).doReadSync();
|
||||
byte[] bytes = inputStream.readAllBytes();
|
||||
validateDictDropdownTemplate(bytes, clazz, sheetName);
|
||||
return EasyExcel.read(new ByteArrayInputStream(bytes)).head(clazz).sheet(sheetName).doReadSync();
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导入Excel失败", e);
|
||||
}
|
||||
@@ -128,7 +155,7 @@ public class EasyExcelUtil {
|
||||
public static <T> void importTemplateExcel(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
||||
try {
|
||||
setResponseHeader(response, sheetName + "模板");
|
||||
EasyExcel.write(response.getOutputStream(), clazz)
|
||||
templateWriter(response, clazz)
|
||||
.sheet(sheetName)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.doWrite(List.of());
|
||||
@@ -151,7 +178,7 @@ public class EasyExcelUtil {
|
||||
WriteHandler... handlers) {
|
||||
try {
|
||||
setResponseHeader(response, sheetName + "模板");
|
||||
var writerBuilder = EasyExcel.write(response.getOutputStream(), clazz)
|
||||
var writerBuilder = templateWriter(response, clazz)
|
||||
.sheet(sheetName)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
|
||||
// 注册所有自定义处理器
|
||||
@@ -190,7 +217,7 @@ public class EasyExcelUtil {
|
||||
public static <T> void importTemplateWithDictDropdown(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
||||
try {
|
||||
setResponseHeader(response, sheetName + "模板");
|
||||
EasyExcel.write(response.getOutputStream(), clazz)
|
||||
templateWriter(response, clazz)
|
||||
.sheet(sheetName)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
||||
@@ -217,7 +244,7 @@ public class EasyExcelUtil {
|
||||
String sheetName, String fileName) {
|
||||
try {
|
||||
setResponseHeader(response, fileName);
|
||||
EasyExcel.write(response.getOutputStream(), clazz)
|
||||
templateWriter(response, clazz)
|
||||
.sheet(sheetName)
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
||||
@@ -250,7 +277,7 @@ public class EasyExcelUtil {
|
||||
String fileName
|
||||
) {
|
||||
setResponseHeader(response, fileName);
|
||||
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
|
||||
try (ExcelWriter writer = templateWriter(response).build()) {
|
||||
writer.write(List.of(), buildTemplateSheet(0, firstClazz, firstSheetName));
|
||||
writer.write(List.of(), buildTemplateSheet(1, secondClazz, secondSheetName));
|
||||
} catch (IOException e) {
|
||||
@@ -322,4 +349,137 @@ public class EasyExcelUtil {
|
||||
throw new RuntimeException("导出带字典下拉框的Excel失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateDictDropdownTemplate(byte[] bytes, Class<?> clazz, String sheetName) {
|
||||
List<DropdownColumn> dropdownColumns = resolveDropdownColumns(clazz);
|
||||
if (dropdownColumns.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(bytes))) {
|
||||
Sheet sheet = sheetName == null ? workbook.getSheetAt(0) : workbook.getSheet(sheetName);
|
||||
if (sheet == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int lastDataRowIndex = findLastDataRowIndex(sheet);
|
||||
if (lastDataRowIndex < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> missingColumnTitles = new ArrayList<>();
|
||||
for (DropdownColumn column : dropdownColumns) {
|
||||
if (!isListValidationCovered(sheet, column.index(), lastDataRowIndex)) {
|
||||
missingColumnTitles.add(column.title());
|
||||
}
|
||||
}
|
||||
if (!missingColumnTitles.isEmpty()) {
|
||||
throw new ServiceException(sheet.getSheetName() + " Sheet 的 "
|
||||
+ String.join("、", missingColumnTitles)
|
||||
+ " 列缺少下拉框,请下载最新导入模板填写后重新导入");
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导入Excel失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<DropdownColumn> resolveDropdownColumns(Class<?> clazz) {
|
||||
List<DropdownColumn> columns = new ArrayList<>();
|
||||
Class<?> current = clazz;
|
||||
while (current != null && current != Object.class) {
|
||||
for (Field field : current.getDeclaredFields()) {
|
||||
if (field.getAnnotation(DictDropdown.class) == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
if (excelProperty == null || excelProperty.index() < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
columns.add(new DropdownColumn(excelProperty.index(), resolveColumnTitle(field, excelProperty)));
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
columns.sort(Comparator.comparingInt(DropdownColumn::index));
|
||||
return columns;
|
||||
}
|
||||
|
||||
private static String resolveColumnTitle(Field field, ExcelProperty excelProperty) {
|
||||
if (excelProperty.value().length > 0 && excelProperty.value()[0] != null
|
||||
&& !excelProperty.value()[0].isBlank()) {
|
||||
return excelProperty.value()[0].replace("*", "");
|
||||
}
|
||||
return field.getName();
|
||||
}
|
||||
|
||||
private static int findLastDataRowIndex(Sheet sheet) {
|
||||
int lastDataRowIndex = -1;
|
||||
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (hasData(row)) {
|
||||
lastDataRowIndex = rowIndex;
|
||||
}
|
||||
}
|
||||
return lastDataRowIndex;
|
||||
}
|
||||
|
||||
private static boolean hasData(Row row) {
|
||||
if (row == null || row.getLastCellNum() < 0) {
|
||||
return false;
|
||||
}
|
||||
for (int cellIndex = row.getFirstCellNum(); cellIndex < row.getLastCellNum(); cellIndex++) {
|
||||
if (cellIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
Cell cell = row.getCell(cellIndex);
|
||||
if (cell != null && cell.toString() != null && !cell.toString().isBlank()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isListValidationCovered(Sheet sheet, int columnIndex, int lastDataRowIndex) {
|
||||
boolean[] coveredRows = new boolean[lastDataRowIndex + 1];
|
||||
for (DataValidation validation : sheet.getDataValidations()) {
|
||||
DataValidationConstraint constraint = validation.getValidationConstraint();
|
||||
if (constraint == null || constraint.getValidationType() != DataValidationConstraint.ValidationType.LIST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (CellRangeAddress address : validation.getRegions().getCellRangeAddresses()) {
|
||||
if (address.getFirstColumn() > columnIndex || address.getLastColumn() < columnIndex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int firstRow = Math.max(1, address.getFirstRow());
|
||||
int lastRow = Math.min(lastDataRowIndex, address.getLastRow());
|
||||
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
|
||||
coveredRows[rowIndex] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int rowIndex = 1; rowIndex <= lastDataRowIndex; rowIndex++) {
|
||||
if (hasData(sheet.getRow(rowIndex)) && !coveredRows[rowIndex]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private record DropdownColumn(int index, String title) {}
|
||||
|
||||
private static <T> ExcelWriterBuilder templateWriter(HttpServletResponse response, Class<T> clazz)
|
||||
throws IOException {
|
||||
// 模板为空且体量小,使用内存工作簿避免 SXSSF 在无字体环境初始化 Fontconfig。
|
||||
return EasyExcel.write(response.getOutputStream(), clazz).inMemory(Boolean.TRUE);
|
||||
}
|
||||
|
||||
private static ExcelWriterBuilder templateWriter(HttpServletResponse response) throws IOException {
|
||||
return EasyExcel.write(response.getOutputStream()).inMemory(Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
AND e.status = #{query.status}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY e.create_time DESC
|
||||
ORDER BY e.create_time DESC, e.staff_id DESC
|
||||
</select>
|
||||
|
||||
<!-- 批量插入或更新员工信息(只更新非null字段) -->
|
||||
|
||||
@@ -21,4 +21,15 @@ class CcdiBaseStaffMapperTest {
|
||||
assertTrue(xml.contains("#{item.partyMember}"), xml);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapperXml_shouldUseStableOrderForBaseStaffPagination() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader()
|
||||
.getResourceAsStream("mapper/info/collection/CcdiBaseStaffMapper.xml")) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
|
||||
.replaceAll("\\s+", " ");
|
||||
|
||||
assertTrue(xml.contains("ORDER BY e.create_time DESC, e.staff_id DESC"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user