开发环境
名称 | 版本 |
---|---|
操作系统 | Windows 10 X64 |
JDK | JDK1.8(jdk-8u151-windows-x64) |
IntelliJ IDEA | IntelliJ IDEA 2018.3 |
Maven | Maven 3.6.0 |
相关代码
详细可以参考:
IntelliJ IDEA 2018.3 创建 Maven 多模块(Module)项目+负载均衡
SpringBoot POI 导入,导出 Excel
浏览器
浏览器调用 Web 层的接口,传入一个 Excel 文件。
Web 层
Web 层解析 Excel 文件,得到一个 List。
/**
* 供应商识别号清单-上传替换
*
* @param file 上传文件
* @return 上传成功返回 true,否则返回 false
*/
@ApiImplicitParams({@ApiImplicitParam(paramType = "header", name = "Authorization", value = "凭证", required = true, dataType = "string"),})
@PostMapping("/supplierInfoImport")
@RequiresPermissions(value = {"payment"}, logical = Logical.OR)
public ResponseVO supplierInfoImport(@RequestParam(value = "filename") MultipartFile file) {
ResponseVO<Boolean> responseVO = new ResponseVO<>(ResponseCode.SYSTEM_EXCEPTION);
responseVO.setData(false);
if (file.isEmpty()) {
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMessage("文件为空!");
return responseVO;
}
//获取文件名
String fileName = file.getOriginalFilename();
if(!POIExcelUtils.validateExcel(fileName)){
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMessage("必须上传 Excel 文件!");
return responseVO;
}
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
Sheet sheet = POIExcelUtils.getSheet(fileName, inputStream);
List<SupplierInfo> listData = new ArrayList<>();
Iterator<Row> rowItr = sheet.rowIterator();//遍历全部非空行
int count = 1;
//for(Row row : sheet){ //忽略表头 if(row.getRowNum() == 0) continue; //会获取所有行,包括空行
//while(rowItr.hasNext()){ Row row=rowItr.next();//会获取所有行,包括空行
int rowCount = sheet.getPhysicalNumberOfRows();
for (int i = 1; i < rowCount; i++) {
Row row = sheet.getRow(i);
//如果得到的行是空的一行,可以根据row.getFirstCellNum()是否大于等于0 来判断
//该行为空那么:row.getFirstCellNum()=-1的,否则row.getFirstCellNum()=0
if (null == row) break;
if (row.getFirstCellNum() == -1) break;
Integer col = 0;//列序号
SupplierInfo data = new SupplierInfo();
//供应商编码 supplier_code
data.setSupplierCode(POIExcelUtils.getStringValue(row.getCell(col++)));
//供应商名称 supplier_name
data.setSupplierName(POIExcelUtils.getStringValue(row.getCell(col++)));
//供应商类型 supplier_type
data.setSupplierType(POIExcelUtils.getStringValue(row.getCell(col++)));
//扣缴类型 withhold_type
data.setWithholdType(POIExcelUtils.getStringValue(row.getCell(col++)));
if (!supplierInfoIsNull(data)) {
listData.add(data);
count++;
}
}
if (listData.size() > 0 && count == rowCount) {
responseVO = foreignPaymentConsumer.replaceSupplierInfo(listData);
} else {
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMessage("文件内容检验不通过,除备注字段外的所有字段都不能为空!");
}
} catch (IOException ex) {
responseVO.setMessage(ex.getMessage());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
return responseVO;
}
}
}
return responseVO;
}
Server
Web 层调用 Server 层接口,传入 List 数据,保存到数据库。
/**
* 替换供应商(删除之前的所有数据)
*
* @param param 供应商数据
* @return 替换成功返回 true,失败返回 false
*/
@PostMapping("/replaceSupplierInfo")
public ResponseVO replaceSupplierInfo(@RequestBody List<SupplierInfo> param) {
supplierInfoService.delAll();
supplierInfoService.adds(param);
ResponseVO<Boolean> responseVO = new ResponseVO<>(ResponseCode.OK);
responseVO.setData(true);
return responseVO;
}
Feign 接口
/**
* 替换供应商(删除之前的所有数据)
*
* @param param 供应商数据
* @return 替换成功返回 true,失败返回 false
*/
@PostMapping("/replaceSupplierInfo")
ResponseVO replaceSupplierInfo(@RequestBody List<SupplierInfo> param);
// ResponseVO replaceSupplierInfo(@RequestParam(value = "param") SupplierInfoObjectVO param);
问题描述
在 Web 层调用 Server 层接口的时候,Server 层报错:
2019-12-27 11:51:55.050 WARN [tax-foreign-service-producer,e3531fa5e887cd88,e3531fa5e887cd88,true] 4752 --- [io-18800-exec-9] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=--------------------------595537775916308170362405;charset=UTF-8' not supported]
尝试方法1-把文件传到 Server 去解析
修改 Feign 接口和 Server 接口的实现代码
/**
* 替换供应商(删除之前的所有数据)
*
* @param param 供应商数据
* @return 替换成功返回 true,失败返回 false
*/
@PostMapping("/replaceSupplierInfo")
ResponseVO replaceSupplierInfo(@RequestParam(value = "filename") MultipartFile file);
结果还是失败,文件从 Web 传不到 Server 去(框架的坑)
尝试方法2-Feign 接口和 Server 添加 Header
/**
* 替换供应商(删除之前的所有数据)
*
* @param param 供应商数据
* @return 替换成功返回 true,失败返回 false
*/
@Headers("Content-Type: application/json")
@PostMapping("/replaceSupplierInfo")
ResponseVO replaceSupplierInfo(@RequestBody List<SupplierInfo> param);
还是失败,报同样的错误
尝试方法3-Feign 接口和 Server 添加 consumes 标记
/**
* 替换供应商(删除之前的所有数据)
*
* @param param 供应商数据
* @return 替换成功返回 true,失败返回 false
*/
@RequestMapping(value = "/replaceSupplierInfo", method = RequestMethod.POST, consumes = "application/json")
ResponseVO replaceSupplierInfo(@RequestBody List<SupplierInfo> param);
还是报错!!
原因分析
描述是说 Server 解析不到类型 content-type:multipart/form-data
可是我 Feign 和 Server 接口的参数定义是 @RequestBody List<SupplierInfo> param
啊!很奇怪!
好像是 Web 层调用 Feign 接口的时候,HttpServletRequest
把浏览器的 ccontent-type:multipart/form-data
转发到 Server 去了。
那么如果我们在 Web 层调用 Feign 接口的时候,修改 content-type
为 content-type:application/json
就可以了。
问题解决
在框架的 Web 层发现了一个配置类——FeignInterceptor
,这个配置类有去使用 HttpServletRequest
重新透传调用者 request head
在这个类里面相关部分加入下面的代码:
if(!"content-type".equals(name)){
template.header(name,value);
}
template.header("content-type","application/json");
加入后的代码为(FeignInterceptor.java):
package com.foreign.payment.web.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
/**
* web通过FeignClient 调用service,防止request head头部信息丢失
* 重新透传调用者request head
* 将用户的登录id放在了请求头中传递给内部服务。但是当内部服务之间存在feign调用时,那么请求头信息会在feign请求的时候传递吗?
* 不会,请求的头信息和请求参数都不会进行传递。但是我们可以通过通过实现RequestInterceptor接口,完成对所有的Feign请求,传递请求头和请求参数
* 原文:https://blog.csdn.net/AaronSimon/article/details/82711036 *
*/
@Configuration
@Slf4j
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return;
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
//template.header(name, value);
if(!"content-type".equals(name)){
template.header(name,value);
}
}
}
template.header("content-type","application/json");
}
//Enumeration<String> bodyNames = request.getParameterNames();
//StringBuffer body = new StringBuffer();
//if (bodyNames != null) {
// while (bodyNames.hasMoreElements()) {
// String name = bodyNames.nextElement();
// String values = request.getParameter(name);
// body.append(name).append("=").append(values).append("&");
// }
//}
//
//
//if (body.length() != 0) {
// body.deleteCharAt(body.length() - 1);
// body.append(new String(template.body(), StandardCharsets.UTF_8));
// template.body(body.toString());
//}
}
}
加入这段代码后,问题解决。数据能够从 Web 传到 Server 了。
这个是这个框架的坑,只是暂时解决了这个问题。
这样存在一个问题,每个 Web 传到 Server 的请求都必须是 content-type:application/json
。
看以后还有什么更好的办法完全解决!
问题解决-第二种办法
注释掉 FeignInterceptor.java
,新增配置类 FeignConfiguration.java
package com.foreign.payment.web.config;
import feign.RequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
/**
* feign 之间传递header,传少量感兴趣的key,别去解析其他的value,否则会抛出意想不到的bug
*
* @author dragon
* @date 2020/01/17
*/
@Component
public class FeignConfiguration {
@Bean
public RequestInterceptor headerInterceptor() {
return requestTemplate -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
String token= request.getHeader("Authorization");
if (!StringUtils.isEmpty(token)) {
requestTemplate.header("Authorization", token);
}
};
}
}
这个配置类里面的 Authorization
是 Header
里面 Token 的名称,是需要在 Web 和 Server 之间传递的 Header
参数。