SpringCloud

SpringCloud通过feign批量导入

Posted by dm on January 18, 2021

问题背景:SpringCloud通过feign服务间调用上传Excel文件及Excel文件下载

服务关系:
服务提供方A(接收文件):解析Excel文件
服务消费方B(发送文件): 上传Excel文件及逻辑处理
B服务通过feign调用A服务

1.文件上传

1.1 添加依赖

B服务pom文件中添加依赖

<!-- Feign进行跨服务传递文件依赖 -->
 <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.4.1</version>
</dependency>

1.2 添加配置类(A/B服务)

用来编码转换

import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.cloud.netflix.feign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;

@Configuration
public class MultipartSupportConfig {

  @Autowired private ObjectFactory<HttpMessageConverters> messageConverters;

  @Bean
  @Primary
  @Scope("prototype")
  public Encoder multipartFormEncoder() {
    return new SpringMultipartEncoder(new SpringEncoder(messageConverters));
  }

  @Bean
  public feign.Logger.Level multipartLoggerLevel() {
    return feign.Logger.Level.FULL;
  }
}

1.3 client接口引用该配置(A/B服务)

feign调用,增加@FeignClient(name = "xcb-common", configuration = MultipartSupportConfig.class)注解

import com.xcb.common.domain.vo.ExcelVo;
import com.xcb.tmscargo.config.MultipartSupportConfig;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/** @Author dongm @Description: @Date 2020/10/16 */
@FeignClient(name = "xcb-common", configuration = MultipartSupportConfig.class)
public interface ExcelClient {

  /**
   * @author: dongm @Description: 导入货源
   * @date: 2021/1/6
   * @param: [file]
   * @return: List<ExcelVo>
   */
  @RequestMapping(
      value = "/api/admin/importExcel",
      method = RequestMethod.POST,
      consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
      produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
  List<ExcelVo> importExcel(@RequestPart(value = "file") MultipartFile file);
}

1.4 接口调用

**注意点 1. @PostMapping(value = “/batchAdd”,produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},consumes = MediaType.MULTIPART_FORM_DATA_VALUE) **

注意点 2. 使用 @RequestPart 非 @RequestParam()

注意点2、3 A/B服务相同

注意点 3. 使用CommonsMultipartFile转换后上传

DiskFileItem fileItem =
        (DiskFileItem)
            new DiskFileItemFactory()
                .createItem(
                    "file",
                    MediaType.MULTIPART_FORM_DATA_VALUE,
                    true,
                    files.getOriginalFilename());
    InputStream input = files.getInputStream();
    OutputStream os = fileItem.getOutputStream();
    IOUtils.copy(input, os);
    MultipartFile file = new CommonsMultipartFile(fileItem);

完整示例

@ApiOperation(value = "批量添加货源", httpMethod = "POST")
@PostMapping(
    value = "/batchAdd",
    produces = {MediaType.APPLICATION_JSON_UTF8_VALUE},
    consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional(rollbackFor = Exception.class)
@AvoidRepeatableCommit
public ResponseEntity<XcbResponse> batchAdd(@RequestPart("file") MultipartFile files)
    throws IOException {
  try {
    // CommonsMultipartFile转换
    DiskFileItem fileItem =
        (DiskFileItem)
            new DiskFileItemFactory()
                .createItem(
                    "file",
                    MediaType.MULTIPART_FORM_DATA_VALUE,
                    true,
                    files.getOriginalFilename());
    InputStream input = files.getInputStream();
    OutputStream os = fileItem.getOutputStream();
    IOUtils.copy(input, os);
    MultipartFile file = new CommonsMultipartFile(fileItem);
    // 解析Excel
    List<ExcelVo> excelVos = excelClient.importExcel(file);

    // 获取解析数据
    for (int i = 0; i < excelVos.size(); i++) {
      // 获取对应参数
      Map<String, String> dataMap = excelVos.get(i).getDataMap();
      String account = dataMap.get("发货用户账号");
      String contactPerson = dataMap.get("联系人姓名");
      String contactPhone = dataMap.get("联系电话");
      String startDateTime = dataMap.get("装货开始日期");
      String vaildDays = dataMap.get("发货周期");
      String startDockCityName = dataMap.get("发货地-市");
      String startDockName = dataMap.get("装货码头");
      String endDockCityName = dataMap.get("卸货地-市");
      String endDockName = dataMap.get("卸货码头");
      String goodsName = dataMap.get("货物名称");
      String deliveryMount = dataMap.get("货物吨位");
      String isSecrecy = dataMap.get("是否为隐私货源");
      String freight = dataMap.get("运价-元/吨");
      String cargoReqMin = dataMap.get("船只最小吨位要求-吨");
      String cargoReqMax = dataMap.get("船只最大吨位要求-吨");
      String remark = dataMap.get("备注");
    }
  } catch (IOException e) {
    e.printStackTrace();
  }
  return new ResponseEntity<>(
      XcbResponse.createBySuccess(batchAddCargoOwnerSupplyVo.returnText()), HttpStatus.OK);
}

1.5 A服务解析导入Excel文件信息

@SneakyThrows
@Override
public List<ExcelVo> importExcelList(MultipartFile file) {
  // 判断文件格式
  Workbook workbook = getWorkbo(file);
  // 无效格式,抛出异常
  if (workbook == null) {
    throw new XcbServerException(CommonResultCode.FILE_FORMAT_ERROR);
  }
  List<ExcelVo> list = new ArrayList<ExcelVo>();
  try {
    // 表对象
    Sheet sheet = workbook.getSheetAt(0);
    // 总行数
    int rowLength = sheet.getLastRowNum();
    // 初始化map
    Map<Integer, String> map = new HashMap<>();
    // 正文内容从第3行开始,第一行为标题,第二行为示例
    for (int i = 0; i < rowLength + 1; i++) {
      Row row = sheet.getRow(i);
      // i = 0:获取第一行标题名称
      if (i == 0) {
        for (int j = 0; j < row.getLastCellNum(); j++) {
          // 获取到第j行的数据(单元格)
          Cell cell = row.getCell(j);
          cell.setCellType(CellType.STRING);
          map.put(j, cell.getStringCellValue());
        }
      }
      // i = 1:描述,可跳过
      else if (i == 1) {
        continue;
      }
      // 获取具体值
      else {
        // 初始化map、vo
        Map<String, String> maps = new HashMap<>();
        ExcelVo excelVo = new ExcelVo();
        for (int j = 0; j < row.getLastCellNum(); j++) {
          // 获取到第j行的数据(单元格)
          Cell cell = row.getCell(j);
          if (cell != null) {
            cell.setCellType(CellType.STRING);
            maps.put(map.get(j), cell.getStringCellValue());
          }
        }
        excelVo.setDataMap(maps);
        list.add(excelVo);
      }
    }
  } catch (Exception e) {
    log.error("parse excel file error :" + e);
  }
  return list;
}

1.5.1 获取Excel文件格式

/**
 * @author: dongm @Description: 获取文件格式信息
 * @date: 2020/10/17
 * @param: [file]
 * @return: Workbook
 */
private static Workbook getWorkbo(MultipartFile file) throws IOException {
  String fileName = file.getOriginalFilename();
  // 获取后缀格式名
  String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
  Workbook workbo = null;
  // 根据后缀格式,判断返回方式
  if (Objects.equals(ExcelType.XLS.getName(), fileSuffix)) {
    workbo = new HSSFWorkbook(file.getInputStream());
  } else if (Objects.equals(ExcelType.XLSX.getName(), fileSuffix)) {
    workbo = new XSSFWorkbook(file.getInputStream());
  }
  return workbo;
}

1.5.2 接收解析后返回值实体

@Data
public class ExcelVo {
  /** 返回参数数据 */
  @ApiModelProperty(notes = "返回参数数据")
  public Map<String, String> dataMap;
}

2. 文件下载Demo

@SneakyThrows
@Override
public void downLoadExcelTemplate(HttpServletRequest request, HttpServletResponse response) {
  String path = "excel/importSupply.xlsx";
  String fileName = path.substring(path.lastIndexOf("/") + 1);

  Resource resource = new ClassPathResource(path);
  InputStream inputStream = resource.getInputStream();
  // 转为POI的Workbook输出流,这样下载后不报错,直接输出流,打开Excel可能提示错误
  @SuppressWarnings("resource")
  XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
  String userAgent = request.getHeader("User-Agent");
  // 针对IE或者以IE为内核的浏览器
  if (userAgent.contains("MSIE") || userAgent.contains("Trident")) {
    fileName = URLUtil.encode(fileName, "UTF-8");
  } else {
    // 非IE浏览器的处理
    fileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1");
  }
  response.setHeader("Content-disposition", "attachment; filename=" + fileName);
  response.setContentType("application/vnd.ms-excel; charset=utf-8");
  response.setCharacterEncoding("UTF-8");
  // 将流中内容写出去
  try (OutputStream outputStream = response.getOutputStream()) {
    workbook.write(outputStream);
    outputStream.flush();
    outputStream.close();
  }
}