封装的easyexcel,基于注解实现excel的导入导出,以场景来说,就是你有一个现成的分页接口或者一个list接口,只需要添加几个简单的注解,就可以实现excel的导出,也是为了方便有模板生成代码的情况下直接生成导出功能。

这是封装的依赖库源码:https://github.com/chenqi92/allbs-excel

这是这个依赖库的使用示例:https://github.com/chenqi92/allbs-excel-test

依赖库运行后在浏览器中打开:http://localhost:8080/ 即可测试各种示例,参照示例进行使用可以不用看后续的使用说明。

昨晚看到评论,发现确实把这个功能点给漏了。
评论

所以额外加了这一节,用来说明如何使用带进度的导入。

还是需要导入maven最新版本:

1
2
3
4
5
<dependency>  
<groupId>cn.allbs</groupId>
<artifactId>allbs-excel</artifactId>
<version>3.1.0</version>
</dependency>

普通带进度的导入

导入的示例代码:
当前示例设置了两种模式,一种是检测到错误不让导入,一种是有错误跳过。通过设置参数skipErrors实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@PostMapping("/with-progress")  
@ImportProgress(listener = SseImportProgressListener.class, interval = 100)
public ResponseEntity<?> importWithProgress(
@ImportExcel List<UserDTO> users,
@RequestAttribute(name = "excelErrors", required = false) List<ErrorMessage> excelErrors,
@RequestParam(value = "skipErrors", defaultValue = "false") boolean skipErrors
) {
Map<String, Object> result = new HashMap<>();

// 计算统计信息
int totalRows = users.size() + (excelErrors != null ? excelErrors.size() : 0);
int successCount = users.size();
int errorCount = excelErrors != null ? excelErrors.size() : 0;

if (!CollectionUtils.isEmpty(excelErrors)) {
if (skipErrors) {
// 跳过错误模式:只导入正确的数据
result.put("success", true);
result.put("message", String.format("导入完成(跳过了 %d 行错误数据)", errorCount));
result.put("successCount", successCount);
result.put("errorCount", errorCount);
result.put("totalRows", totalRows);
result.put("skipRate", String.format("%.2f%%", (errorCount * 100.0 / totalRows)));

// 返回错误摘要
List<String> errorSummary = excelErrors.stream()
.limit(10) // 只返回前10个错误
.flatMap(em -> em.getErrorMessages().stream()
.map(msg -> "行号 " + em.getLineNum() + ":" + msg))
.collect(Collectors.toList());
if (errorCount > 10) {
errorSummary.add("... 还有 " + (errorCount - 10) + " 行错误数据被跳过");
} result.put("errorSummary", errorSummary);
} else {
// 严格模式:有错误则拒绝导入
List<String> errors = excelErrors.stream()
.limit(20) // 只返回前20个错误
.flatMap(em -> em.getErrorMessages().stream()
.map(msg -> "行号 " + em.getLineNum() + ":" + msg))
.collect(Collectors.toList());
if (errorCount > 20) {
errors.add("... 还有 " + (errorCount - 20) + " 行错误");
}
result.put("success", false);
result.put("message", String.format("发现 %d 行数据有错误,请修正后重新导入", errorCount));
result.put("errors", errors);
result.put("validCount", successCount);
result.put("errorCount", errorCount);
return ResponseEntity.badRequest().body(result);
} } else {
result.put("success", true);
result.put("message", "导入成功");
result.put("successCount", successCount);
result.put("errorCount", 0);
result.put("totalRows", totalRows);
}
// 不返回所有数据,避免响应太大
result.put("previewData", users.size() > 10 ? users.subList(0, 10) : users);
result.put("hasMore", users.size() > 10);

return ResponseEntity.ok(result);
}

我将使用有一行故意不满足校验的模板:
模板
导入后如下:
提示
带进度的如下:
导入进度

异步导入

可以用几十万条数据进行测试,不然效果不明显。
效果
接口实时返回内容,然后通过另外一个接口获取这个异步任务的动态。代码分为导入和获取状态的接口。异步处理的我就不贴了,可以看示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**  
* 1. 提交异步导入任务
*/
@PostMapping("/submit")
public ResponseEntity<?> submitTask(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "skipErrors", defaultValue = "false") boolean skipErrors
) {
try {
String taskId = asyncImportService.submitTask(file, UserDTO.class, skipErrors);

Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("taskId", taskId);
response.put("message", "任务已提交,请通过taskId查询进度");

return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "提交任务失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}}

/**
* 2. 获取任务状态
*/
@GetMapping("/task/{taskId}")
public ResponseEntity<?> getTaskStatus(@PathVariable String taskId) {
ImportTask task = asyncImportService.getTask(taskId);

if (task == null) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "任务不存在");
return ResponseEntity.badRequest().body(response);
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("taskId", task.getTaskId());
response.put("fileName", task.getFileName());
response.put("status", task.getStatus().name());
response.put("progress", task.getProgress());
response.put("totalRows", task.getTotalRows());
response.put("processedRows", task.getProcessedRows());
response.put("successCount", task.getSuccessCount());
response.put("errorCount", task.getErrorCount());
response.put("errorMessage", task.getErrorMessage());
response.put("createdAt", task.getCreatedAt());
response.put("startedAt", task.getStartedAt());
response.put("completedAt", task.getCompletedAt());

// 如果有错误,返回部分错误详情
if (task.getErrorList() != null && !task.getErrorList().isEmpty()) {
response.put("errors", task.getErrorList().stream()
.limit(20)
.map(error -> {
Map<String, Object> errorMap = new HashMap<>();
errorMap.put("rowIndex", error.getRowIndex());
errorMap.put("data", error.getData());
errorMap.put("fieldErrors", error.getFieldErrors());
return errorMap;
}) .collect(Collectors.toList()));
response.put("hasMoreErrors", task.getErrorList().size() > 20);
}
// 如果完成,返回部分成功数据
if (task.getSuccessData() != null && !task.getSuccessData().isEmpty()) {
response.put("previewData", task.getSuccessData());
response.put("hasMoreData", task.getSuccessCount() > 20);
}
return ResponseEntity.ok(response);
}

数据预览和确认导入

类似海康系统里面的那个导入,先将待导入的数据列出来,然后判断是否导入。代码太多了,就不贴了,可以直接看示例。
数据预览和确认导入
有错误的情况
跳过错误