记录文件通过HttpClient传输中遇到的问题

前言:

某天接到一个需求,需要出一个外部调用接口,其中包含文档数据,找了项目中封装好的HttpClientUtil但是Content-Type基本上都是application/json 或 application/xml,不是上传文档的类型,而文档使用的是multipart/form-data,因此如果使用提供的doPost方法因为类型不一致会直接报错

于是需要自己封装一个HttpClientUtil,专门用于传输文档,下面是整体的代码,中间文档部分addBinaryBody可以抽取出来,将文件创建一个文件数组传进来,321上代码!

package com;

import com.google.gson.Gson;
import com.springbootdemo.cn.demo.util.JSONUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.util.ObjectUtils;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class HttpUtils {
    public static void main(String[] args) throws Exception {

        }

    public static HashMap uploadFile(String url, Map bodyMap, Map headMap) throws IOException {

        File file = new File("C:\\Users\\Administrator\\Documents\\test测试1.xlsx");
        File file1 = new File("C:\\Users\\Administrator\\Documents\\test测试2.xlsx");
        File file2 = new File("C:\\Users\\Administrator\\Documents\\test测试3.xlsx");

        //创建一个HttpClient对象,并对象进行配置
        HttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);

        //设置请求头
        httpPost.setHeader("Connection", "Keep-Alive");
        if (!ObjectUtils.isEmpty(headMap)) {
            Iterator var8 = headMap.entrySet().iterator();

            while(var8.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry)var8.next();
                httpPost.setHeader((String)entry.getKey(), (String)entry.getValue());
            }
        }

        String personnelTransferFormInfos = JSONUtil.objectToJson(bodyMap.get("data"));


        // 构建上传文件的实体
        HttpEntity entity = MultipartEntityBuilder.create()
                //确保处理多部分请求时符合 RFC 6532 标准
                // 特别是处理包含中文或其他非ASCII字符的国际化内容时
                //HttpMultipartMode.RFC6532(在不设置这项时会导致文档名称出现编码格式错误)
                .setMode(HttpMultipartMode.RFC6532)
                .addBinaryBody("file", file)
                .addBinaryBody("file", file1)
                .addBinaryBody("file", file2)
                //设置文本类型的数据编码格式,当没有设置这项时
                // json数据中的中文会存在编码格式错误的问题
                .addTextBody("data", personnelTransferFormInfos, ContentType.APPLICATION_JSON.withCharset("UTF-8"))
                .build();

        httpPost.setEntity(entity);

        // 发送请求并获取响应
        HttpResponse response = httpClient.execute(httpPost);

        // 处理响应
        System.out.println(response.getStatusLine());

        //对返回的响应数据进行格式转换
        Gson gson = new Gson();
        String resultStr = EntityUtils.toString(response.getEntity(), "UTF-8");
        return gson.fromJson(resultStr, HashMap.class);

    }

}

在这个方法中有几点需要注意

1.如果需要在header中放一些校验权限的数据记得添加这段代码,将请求头的信息放到httpPost中

        //设置请求头
        httpPost.setHeader("Connection", "Keep-Alive");
        if (!ObjectUtils.isEmpty(headMap)) {
            Iterator var8 = headMap.entrySet().iterator();

            while(var8.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry)var8.next();
                httpPost.setHeader((String)entry.getKey(), (String)entry.getValue());
            }
        }

2.在构建文件上传实体时需要设置模式,确保处理多部分请求时符合 RFC 6532 标准,这部分代码如果不设置 RFC 6532 会导致上传的文件名中的中文名称由于编码格式不同而导致乱码。

3.同上,在构建json数据时也需要设置文本的字符集,如果不指定文本的字符集很可能会出现编码格式不一致而导致乱码。

        // 构建上传文件的实体
        HttpEntity entity = MultipartEntityBuilder.create()
                //确保处理多部分请求时符合 RFC 6532 标准
                // 特别是处理包含中文或其他非ASCII字符的国际化内容时
                //HttpMultipartMode.RFC6532(在不设置这项时会导致文档名称出现编码格式错误)
                .setMode(HttpMultipartMode.RFC6532)
                .addBinaryBody("file", file)
                .addBinaryBody("file", file1)
                .addBinaryBody("file", file2)
                //设置文本类型的数据编码格式,当没有设置这项时
                // json数据中的中文会存在编码格式错误的问题
                .addTextBody("data", personnelTransferFormInfos, ContentType.APPLICATION_JSON.withCharset("UTF-8"))
                .build();

上述的2,3是这次个人使用HttpClient遇到的一个坑,也是查了很久尝试出来的。

然后就觉得万事大吉了,对方法进行了测试,ok,没有问题,提交代码发版!

然鹅。。。。。。。。

又有一个点没注意到,通过同时的提示才发现的。项目中,由于外部接口需要上统一网关,通过网关进行请求,由于项目使用了微服务,所以都是通过FeignClient调用的,所以就照着别人写的抄了一份,结果部署后发现报错了。。。。。。

为什么会报错?寻找问题出在哪里,在查看线上日志后发现错误并不在业务包中,按道理来说如果存在报错是会打印出报错日志的,但是并没有,那就说明是并没有请求成功。于是查看的网关服务上的代码,也没有发现什么问题,冥思苦想时,同事一语道破梦中人,问题出在Feign接口上,开始也说过,和平常的请求类型并不相同,文档使用的是multipart/form-data类型,所以需要在请求类型中添加consumes属性,属性值指定为MediaType.MULTIPART_FORM_DATA_VALUE

    @PostMapping(value = "/uploadFile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

在入参处指定入参类型为RequestPart,由于使用的是post请求且同时存在文件所以就不能使用RequestBody,因为带有文件的类型与文本格式并不相同,所以如果存在表单数据同时需要传输就需要添加RequestParam注解

    String uploadFile(@RequestParam("data") String data,@RequestPart(name = "file", required = false) MultipartFile[] fileArr);

更改后,部署,发版,测试,ok!完美调用业务代码,至此才算是结束了这个功能!

整个功能可以进行抽取,抽取后可以封装成一个单独的工具类使用,由于同时上传文件和文本数据所以传输格式和通常的请求不同,这种传输方式也是第一次接触,同时可能还有其他的方式传输文件,我看大多数童靴都使用的文件流进行传输,将文件转换为byte数组字符串,然后加密在网络中进行传输,当接收放接收到这些数据后对加密的byte数组字符串进行解密,然后再转为文件流,通过minio上传文件管理服务,这种方案在搜索到的结果中很常见。有些坑踩了后无法即使脱坑,也是对这种形式不太熟悉导致的,也希望能够给踩同样坑的童鞋带来一些帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

*

613 次浏览