1、调整导入失败,导出excel功能
2、调整规范
This commit is contained in:
@@ -215,9 +215,10 @@ Entity 基类的选择**直接决定** Service 和 ServiceImpl 的继承方式
|
|||||||
|
|
||||||
- 所有 Excel 批量导入功能如果存在部分数据无法导入、校验失败或处理异常,后端必须将导入失败的数据导出为 Excel 文件返回给前端下载。
|
- 所有 Excel 批量导入功能如果存在部分数据无法导入、校验失败或处理异常,后端必须将导入失败的数据导出为 Excel 文件返回给前端下载。
|
||||||
- 导入失败 Excel 的字段顺序必须与原导入模板保持一致,并在最后一列追加“导入失败原因”;失败原因应包含明确行号和可读错误信息。
|
- 导入失败 Excel 的字段顺序必须与原导入模板保持一致,并在最后一列追加“导入失败原因”;失败原因应包含明确行号和可读错误信息。
|
||||||
|
- 导入失败 Excel 中的失败数据行必须使用红色文字展示,最后一列“导入失败原因”的错误信息也必须使用红色文字展示;表头可保持默认样式。
|
||||||
- 导入模板本身不得包含失败原因列;如复用导入模型,应使用 `@ExcelIgnore` 忽略内部错误字段,或单独定义 `XxxImportFailureExcel` 模型。
|
- 导入模板本身不得包含失败原因列;如复用导入模型,应使用 `@ExcelIgnore` 忽略内部错误字段,或单独定义 `XxxImportFailureExcel` 模型。
|
||||||
- 导入逻辑应逐行处理:可成功导入的数据正常保存,失败行收集到失败明细;除非业务明确要求全量事务回滚,不得因部分失败回滚已成功行。
|
- 导入逻辑应逐行处理:可成功导入的数据正常保存,失败行收集到失败明细;除非业务明确要求全量事务回滚,不得因部分失败回滚已成功行。
|
||||||
- Controller 在失败明细非空时应直接通过 `ExcelUtil.export` 写入 `HttpServletResponse`,文件名统一包含业务名称、`导入失败明细` 和时间戳;全部成功时返回标准 `R.success`。
|
- Controller 在失败明细非空时应直接通过导入失败明细通用导出工具写入 `HttpServletResponse`,文件名统一包含业务名称、`导入失败明细` 和时间戳;全部成功时返回标准 `R.success`。
|
||||||
- Service 层应返回失败明细列表或等效结构,禁止只抛出拼接后的错误字符串导致前端无法下载失败数据。
|
- Service 层应返回失败明细列表或等效结构,禁止只抛出拼接后的错误字符串导致前端无法下载失败数据。
|
||||||
|
|
||||||
### 6.12 日志
|
### 6.12 日志
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ package org.springblade.common.excel;
|
|||||||
import cn.idev.excel.FastExcel;
|
import cn.idev.excel.FastExcel;
|
||||||
import cn.idev.excel.annotation.ExcelIgnore;
|
import cn.idev.excel.annotation.ExcelIgnore;
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import cn.idev.excel.write.metadata.style.WriteCellStyle;
|
||||||
|
import cn.idev.excel.write.metadata.style.WriteFont;
|
||||||
|
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@@ -48,6 +52,7 @@ public class ImportFailureExcelUtil {
|
|||||||
|
|
||||||
private static final String FAILURE_REASON = "导入失败原因";
|
private static final String FAILURE_REASON = "导入失败原因";
|
||||||
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
|
private static final String ERROR_MESSAGE_FIELD = "errorMessage";
|
||||||
|
private static final String FAILURE_REASON_FIELD = "failureReason";
|
||||||
|
|
||||||
private ImportFailureExcelUtil() {
|
private ImportFailureExcelUtil() {
|
||||||
}
|
}
|
||||||
@@ -60,9 +65,8 @@ public class ImportFailureExcelUtil {
|
|||||||
* @param sheetName 工作表名
|
* @param sheetName 工作表名
|
||||||
* @param data 失败数据
|
* @param data 失败数据
|
||||||
* @param excelClass 原导入 Excel 类型
|
* @param excelClass 原导入 Excel 类型
|
||||||
* @param <T> 泛型
|
|
||||||
*/
|
*/
|
||||||
public static <T> void export(HttpServletResponse response, String fileName, String sheetName, List<T> data, Class<T> excelClass) {
|
public static void export(HttpServletResponse response, String fileName, String sheetName, List<?> data, Class<?> excelClass) {
|
||||||
response.setContentType("application/vnd.ms-excel");
|
response.setContentType("application/vnd.ms-excel");
|
||||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||||
@@ -71,7 +75,11 @@ public class ImportFailureExcelUtil {
|
|||||||
List<List<String>> head = buildHead(excelFields);
|
List<List<String>> head = buildHead(excelFields);
|
||||||
List<List<Object>> rows = buildRows(data, excelFields);
|
List<List<Object>> rows = buildRows(data, excelFields);
|
||||||
try {
|
try {
|
||||||
FastExcel.write(response.getOutputStream()).head(head).sheet(sheetName).doWrite(rows);
|
FastExcel.write(response.getOutputStream())
|
||||||
|
.registerWriteHandler(failureRowStyle())
|
||||||
|
.head(head)
|
||||||
|
.sheet(sheetName)
|
||||||
|
.doWrite(rows);
|
||||||
} catch (IOException exception) {
|
} catch (IOException exception) {
|
||||||
throw new IllegalStateException("导出导入失败明细失败", exception);
|
throw new IllegalStateException("导出导入失败明细失败", exception);
|
||||||
}
|
}
|
||||||
@@ -82,6 +90,7 @@ public class ImportFailureExcelUtil {
|
|||||||
.filter(field -> field.getAnnotation(ExcelProperty.class) != null)
|
.filter(field -> field.getAnnotation(ExcelProperty.class) != null)
|
||||||
.filter(field -> field.getAnnotation(ExcelIgnore.class) == null)
|
.filter(field -> field.getAnnotation(ExcelIgnore.class) == null)
|
||||||
.filter(field -> !Objects.equals(field.getName(), ERROR_MESSAGE_FIELD))
|
.filter(field -> !Objects.equals(field.getName(), ERROR_MESSAGE_FIELD))
|
||||||
|
.filter(field -> !Objects.equals(field.getName(), FAILURE_REASON_FIELD))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +105,9 @@ public class ImportFailureExcelUtil {
|
|||||||
return head;
|
return head;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> List<List<Object>> buildRows(List<T> data, List<Field> excelFields) {
|
private static List<List<Object>> buildRows(List<?> data, List<Field> excelFields) {
|
||||||
List<List<Object>> rows = new ArrayList<>();
|
List<List<Object>> rows = new ArrayList<>();
|
||||||
for (T item : data) {
|
for (Object item : data) {
|
||||||
List<Object> row = new ArrayList<>();
|
List<Object> row = new ArrayList<>();
|
||||||
for (Field field : excelFields) {
|
for (Field field : excelFields) {
|
||||||
row.add(fieldValue(item, field));
|
row.add(fieldValue(item, field));
|
||||||
@@ -111,16 +120,17 @@ public class ImportFailureExcelUtil {
|
|||||||
|
|
||||||
private static Object fieldValue(Object item, Field field) {
|
private static Object fieldValue(Object item, Field field) {
|
||||||
try {
|
try {
|
||||||
field.setAccessible(true);
|
Field targetField = targetField(item.getClass(), field.getName());
|
||||||
return field.get(item);
|
targetField.setAccessible(true);
|
||||||
} catch (IllegalAccessException exception) {
|
return targetField.get(item);
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException exception) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object errorMessage(Object item) {
|
private static Object errorMessage(Object item) {
|
||||||
try {
|
try {
|
||||||
Field field = item.getClass().getDeclaredField(ERROR_MESSAGE_FIELD);
|
Field field = targetField(item.getClass(), ERROR_MESSAGE_FIELD, FAILURE_REASON_FIELD);
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
Object value = field.get(item);
|
Object value = field.get(item);
|
||||||
return value == null ? "" : String.valueOf(value);
|
return value == null ? "" : String.valueOf(value);
|
||||||
@@ -129,4 +139,28 @@ public class ImportFailureExcelUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Field targetField(Class<?> itemClass, String... fieldNames) throws NoSuchFieldException {
|
||||||
|
Class<?> currentClass = itemClass;
|
||||||
|
while (currentClass != null) {
|
||||||
|
for (String fieldName : fieldNames) {
|
||||||
|
try {
|
||||||
|
return currentClass.getDeclaredField(fieldName);
|
||||||
|
} catch (NoSuchFieldException ignored) {
|
||||||
|
// 继续查找其他字段名或父类字段。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentClass = currentClass.getSuperclass();
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(String.join(",", fieldNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HorizontalCellStyleStrategy failureRowStyle() {
|
||||||
|
WriteFont contentFont = new WriteFont();
|
||||||
|
contentFont.setColor(IndexedColors.RED.getIndex());
|
||||||
|
|
||||||
|
WriteCellStyle contentStyle = new WriteCellStyle();
|
||||||
|
contentStyle.setWriteFont(contentFont);
|
||||||
|
return new HorizontalCellStyleStrategy(null, contentStyle);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import org.springblade.core.tenant.annotation.NonDS;
|
|||||||
import org.springblade.core.tool.api.R;
|
import org.springblade.core.tool.api.R;
|
||||||
import org.springblade.core.tool.utils.DateUtil;
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
import org.springblade.core.tool.utils.Func;
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||||
import org.springblade.system.excel.CargoTypeExcel;
|
import org.springblade.system.excel.CargoTypeExcel;
|
||||||
import org.springblade.system.excel.CargoTypeExportExcel;
|
import org.springblade.system.excel.CargoTypeExportExcel;
|
||||||
import org.springblade.system.excel.CargoTypeImportFailureExcel;
|
import org.springblade.system.excel.CargoTypeImportFailureExcel;
|
||||||
@@ -158,7 +159,7 @@ public class CargoTypeController extends BladeController {
|
|||||||
List<CargoTypeExcel> data = ExcelUtil.read(file, CargoTypeExcel.class);
|
List<CargoTypeExcel> data = ExcelUtil.read(file, CargoTypeExcel.class);
|
||||||
List<CargoTypeImportFailureExcel> failureList = cargoTypeService.importCargoType(data);
|
List<CargoTypeImportFailureExcel> failureList = cargoTypeService.importCargoType(data);
|
||||||
if (Func.isNotEmpty(failureList)) {
|
if (Func.isNotEmpty(failureList)) {
|
||||||
ExcelUtil.export(response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CargoTypeImportFailureExcel.class);
|
ImportFailureExcelUtil.export(response, "货物类型导入失败明细" + DateUtil.time(), "导入失败明细", failureList, CargoTypeExcel.class);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return R.success("导入数据成功");
|
return R.success("导入数据成功");
|
||||||
|
|||||||
Reference in New Issue
Block a user