1. 恼人的Multipart form data

1. 概述

      我目前在公司负责开放平台项目,使用spring-cloud-gateway作为开放平台网关,我们网关处理两类数据,都是通过post提交,一类普通数据接口,是通过application/json提交到网关的, 一类是文件上传接口,通过multipart/form-data的方式提交到网关,今天分享下使用multipart/form-data遇到的问题及解决方式。

2. 问题

      因为我们是开放平台,开发者接入开放平台,调用开放平台接口的时候会顺带提交开发者信息,方便开放平台后续业务处理,在文件上传接口中,开发者信息也是一并通过multipart/form-data的方式提交的,此时,我需要从multipart/form-data中获取解析出开发者信息,问题出现在获取开发者信息这里。
      通过api的方式获取MultipartData,是类似这样的代码:

return exchange.getMultipartData().flatMap(data ->{
   Map<String, Part> map = data.toSingleValueMap();
   Part msgIdPart = map.get("msgId");
   String msgId = parseFormFieldPartValue(msgIdPart);
   record.setMsgId(msgId);

   Part orgIdPart = map.get("orgId");
   String orgId = parseFormFieldPartValue(orgIdPart);
   exchange.getAttributes().put(OpenApiConstants.ORGID, orgId);

   Part appIdPart = map.get("appId");
   String appId = parseFormFieldPartValue(appIdPart);
   exchange.getAttributes().put(OpenApiConstants.APPID, appId);

      获取MultiparData非常完美,但问题是WebFlux的body数据只能打开一次,当我获取完MultipartData之后,想通过gateway把body数据传输到后台服务,此时body数据为空,后台服务获取不到数据。

3. 解决方案

3.1 解决方案一

      既然body数据只能打开一次,那打开之后能不能在设置body呢,application/json类型的接口我就是这么做的,区别是application/json数据可以把Flux中的数据转换成字符串类型,比较好处理。multipart/form-data数据需要借助api,处理成Part类型的数据,怎样把Part类型的数据在转化成body呢,我尝试了很多种方式,最终以失败告终。因为工期等原因,我没有太深入的研究这部分内容,如果各位有好的方式,还望多多指教,如果后期我研究出来了,我会来更新。

3.2 解决方案二

      第二种解决方案,是阻断gateway的filter链,此时不在通过gateway本身的机制把请求转发到后台服务了,而是直接发起对后台的调用。这种方式比较复杂,需要考虑分布式的一些问题,比如如何获取后端服务,如何对后端服务进行负载均衡调用等。工期也不允许我这么做,所以我没有采用第二种方式。

3.3 解决方案三

      第三种方案,就是把multipart/form-data数组转换成string(在这之前我借用了网上一种方式,缓存body,使body可以多次获取,但是缓存的方式与通过api获取MultipartData数据互斥),然后解析string,数据转换成string之后,是类似如下的格式:

--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="file"; filename="测试文件上传.txt"
Content-Type: application/octet-stream

测试文件上传
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="uploader"

test
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="appId"

testapp
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="isTemp"

0
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="tenantId"

000000
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="msgId"

c0dc333c-373a-4326-87ec-ddd64e2bf081
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="orgId"

orgId
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_
Content-Disposition: form-data; name="timestamp"

1661162528991
--joHDquiO2YE1x7arAlPHp9qOVpgrL-1q_--

      当上传的是文本文件的时候,这个格式还勉强可以接受,如果上传文件是类似图片的二进制数据,格式就非常难以阅读了,不过不影响解决问题,
      我写了一段儿代码,来解析这种格式,在windows和linux上亲测可用,虽然非常难看,但确实可以解决问题,代码如下所示:

public static Map<String, String> readMultiFormData(String s, String splitStr, List<String> fileParam) {
   try {
      log.info("MultiFormDataUtil.readMultiFormData lineSeparator:{} , s : {}, splitStr : {} ", System.lineSeparator(), s, splitStr);
      String [] strArray = s.split(splitStr);
      String sysSeparter = System.lineSeparator();
      Map<String, String> paramMap = new HashMap<>();

      Arrays.stream(strArray).map(item ->{
         try {
            return item.replaceAll(sysSeparter, ";");
         } catch (Exception e) {
            return "";
         }
      }).filter(item ->{
         //去除空的
         item = item.trim();
         return StringUtils.isNotEmpty(item);
      }).map(item->{
         item = item.trim();
         while (';' == item.charAt(0)) {
            item = item.substring(1);
         }
         while (';' == item.charAt(item.length()-1)) {
            item = item.substring(0, item.length()-1);
         }
         return item;
      }).filter(item ->{
         return item.contains("Content-Disposition");
      }).forEach(item ->{
         parseData(paramMap, item, ";", fileParam);
      });

      return paramMap;

   } catch (Exception e) {
      log.error("MultiFormDataUtil.readMultiFormData error " + e.getMessage(), e);
      return Collections.EMPTY_MAP;
   }
}

private static Map<String, String> parseData(Map<String, String> paramMap, String str, String sep, List<String> fileParam) {
   log.info("MultiFormDataUtil.parseData str:{}, sep:{}", str, sep);
   String key = "";
   String[] array = str.split(sep);
   for(int i=0; i<array.length; i++) {
      String ss = array[i].trim();
      if(ss.startsWith("name=")) {
         key = ss.substring(ss.indexOf('=') + 2, ss.length()-1);
         key = key.trim();
         String value = "";
         if(fileParam.contains(key)) {
            //是file时寻找file名称
            value = findFileName(array);
            value = value.trim();
         } else {
            //不是file时计算key的值
            value = array[array.length-2];
            value = value.trim();
         }

         if(paramMap.get(key) == null) {
            paramMap.put(key, value);
         } else {
            String oldValue = paramMap.get(key);
            paramMap.put(key, oldValue + ";" + value);
         }
         break;
      }
   }
   return paramMap;
}

/**
 * 解析file名称
 * @return
 */
private static String findFileName(String [] arrays) {
   try {
      String fileName = Arrays.stream(arrays).filter(item ->{
         return item.contains("filename") ? true : false;
      }).map(item ->{
         item = item.substring(item.indexOf('=') + 2, item.length()-1);
         return item;
      }).findFirst().orElse("");
      return fileName;
   } catch (Exception e) {
      return "";
   }
}

      入口方法readMultiFormData需要三个参数
      第一个参数是待解析的文本内容。
      第二个参数是分隔符,可以通过contentType.getParameter(“boundary”)的方式获取
      第三个参数是上传文件的参数名,我代码里是file
      解析出来的结果如下图所示:
在这里插入图片描述
      有用的到的朋友可以尝试下,如果大家有更好的解决这个问题的方式,不要忘了告诉我,感谢!

4. 总结

      本文介绍了在spring-cloud-gateway中通过multipart/form-data上传文件时,获取参数的三种思路并给出了第三种思路的实现方式。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值