技术问题排查与解决汇总

1.SpringBoot MultipartFile上传文件异常

背景

客源数据Excel批量导入接口,为了防止文件过大处理耗时过长决定采用异步处理方式,即提交文件直接返回成功,然后异步去处理客源数据。异步处理好处确实快了很多,但是会偶现文件找不到异常…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2021-01-22 10:05:36 [fe51fa42-f77e-440d-a4c4-6db0e81fc06b] [ERROR] [CustomerImportBaseInfoServiceImpl][call][-1][query-pool-35][traceId:422f6f07-6eff-4650-98a9-eca3d00c0736] 客户数据导入easyExcel读取文本失败... syncId:422f6f07-6eff-4650-98a9-eca3d00c0736, ucid:1000001001436304, fileName:D 客户模版.xlsx
java.io.FileNotFoundException: /data0/www/cache/i.nh-agency-customer-api.ke.com/tomcat.6265879184974759873.11864/work/Tomcat/localhost/ROOT/upload_42ea569a_8555_42cd_ac25_45b03f29d456_00000453.tmp (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at org.apache.tomcat.util.http.fileupload.disk.DiskFileItem.getInputStream(DiskFileItem.java:194)
at org.apache.catalina.core.ApplicationPart.getInputStream(ApplicationPart.java:100)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile.getInputStream(StandardMultipartHttpServletRequest.java:251)
at com.ke.newhouse.agency.customer.service.impl.CustomerImportBaseInfoServiceImpl.lambda$readCustomerImportFile$0(CustomerImportBaseInfoServiceImpl.java:120)
at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1626)
at com.ke.newhouse.agency.customer.common.utils.RunnableWrapper.run(RunnableWrapper.java:33)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

排查

从堆栈日志中可以追踪到是代码里引用的file.getInputStream()处查找文件失败,所以可以想到应该是文件上传上来SpringBoot会自动在tomcat的临时目录下创建临时文件接受上传上来的文件,但是在获取文件字节流时却未找到文件,猜想是该目录/文件被自动删除了。
通过查询资料和跟踪MultipartFile源文件了解到springboot会在同步条件下接收并处理完成文件后自动将临时文件clean掉,突然想到我这边业务里时同步接收文件,异步处理文件,所以接收完文件直接返回“提交成功了”,异步再处理文件中的数据。这就导致springboot以为你已经处理完了就删掉了临时文件,异步处理时却找不到了出现数据不同步问题发生异常。

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
public String readCustomerImportFile(MultipartFile file, Long ucid) {
if (file == null) {
return ApiCodeEnum.PARAM_ERROR.getMsg();
}
String fileName = null;
try {
fileName = URLDecoder.decode(file.getOriginalFilename(), UTF_8);
} catch (UnsupportedEncodingException e) {
LOGGER.error("客户数据导入 转码文件名失败... ucid:{}", ucid, e);
}
if (StringUtils.isEmpty(fileName)) {
return ApiCodeEnum.PARAM_ERROR.getMsg();
}
if (!fileName.endsWith(CustomerSyncDataConst.XLS) && !fileName.endsWith(CustomerSyncDataConst.XLSX)) {
return ApiCodeEnum.FILE_FORMAT_ERROR.getMsg();
}
String syncId = TraceUtils.getTraceId();
try {
customerDataSyncMapper.insertSelective(new CustomerDataSyncDO()
.setFileName(fileName)
.setBizType(CustomerSyncDataConst.SYNC_BIZ_TYPE1)
.setSyncId(syncId)
.setBrokerUcid(ucid)
.setSyncType(CustomerSyncDataConst.SYNC_TYPE_UPLOAD)
.setStatus(CustomerSyncDataConst.SYNC_STATUS_UNFINISH)
.setSyncTime(DateUtil.localDateTime2Date(LocalDateTime.now())));
LOGGER.info("客户数据导入 初始化导入状态信息... syncId:{}, file:{}, brokerId:{}", syncId, fileName, ucid);
} catch (Exception e) {
LOGGER.error("客户数据导入 初始化导入状态信息失败... syncId:{}, file:{}, brokerId:{}", syncId, fileName, ucid, e);
return ApiCodeEnum.FAILED.getMsg();
}
PermissionInfo permissionTypeInfo = permissionService.queryDataPermissionBy(ucid);
final String asyncFileName = fileName;
CompletableFuture.runAsync(() -> {
try {
Integer companyType = permissionTypeInfo.getCompanyType();
LOGGER.info("客户数据导入 开始异步解析文本内容... syncId:{}, file:{}, brokerId:{}, companyType:{}", syncId, asyncFileName, ucid, companyType);
EasyExcel.read(file.getInputStream(), CustomerImportDataVo.class, new CustomerImportBaseInfoListener(syncId, companyType))
.sheet().doRead();
} catch (IOException e) {
LOGGER.error("客户数据导入easyExcel读取文本失败... syncId:{}, ucid:{}, fileName:{}", syncId, ucid, asyncFileName, e);
}
}, executorService);

return ApiCodeEnum.SUBMIT_SUCCESS.getMsg();
}

解决

知道了是数据不同步导致的问题,解决起来就容易很多。既然这个接口无法改成同步逻辑,那就直接创建一个对象把MultipartFile的字节流复制一份到内存中供给异步操作使用即可。但是要注意限制下上传文件的大小,防止上传过大文件放在内存导致OOM

2.Spring中集成了logback,但是启动日志没有输出

  • 查看pom中的logback依赖的jar是否配置完整
  • 如果logback依赖都正常配置了,再排查是否因为其他依赖内置logback的版本冲突,如果有则exclusion移除

3. 解决中文乱码问题汇总

解决tomcat中文乱码

在tomcat的conf文件夹下找到server.xml,增加支持中文配置,解决在GET或者POST过程中文乱码

1
2
3
4
5
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8"/>

重启tomcat解决问题

解决idea中tomcat启动输出日志在console中文乱码

在idea中的tomcat配置选项中增加 VM options:-Dfile.encoding=UTF-8即可;
如果仍有问题,可以在idea安装目录下的idea.exe.vmoptions配置文件增加-Dfile.encoding=UTF-8 即可;

post过程中报文的中文乱码问题

html或者jsp页面有中文乱码问题