OKHTTP 实现微服务间文件上传和下载(okhttp response leak)

1.Feign框架需要集成模块feign-form才能支持文件上传的消息体格式。

2.不论是独立使用Feign,还是使用Spring Cloud Feign,下载文件时的返回值都必须为feign.Response类型。

添加依赖

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.3.1</version>
        </dependency>

复杂文件上传接口

    @ApiOperation("文件上传")
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @ResponseBody
    public ResultEx<InnerUploadVo> tokenUpload(@RequestPart(value = "uploadFile") MultipartFile uploadFile, @ModelAttribute InnerUploadDto dto) {
        CommonUploadDto commonUploadDto = new CommonUploadDto();
        commonUploadDto.setApp(dto.getApp());
        commonUploadDto.setUserId(loginUserInfoHelper.getUserId(false));
        commonUploadDto.setBizKey(dto.getBizKey());
        commonUploadDto.setFileType(dto.getFileType());
        ResultEx<InnerUploadVo> innerUploadVoResultEx = fileInfoFacade.upload(uploadFile, commonUploadDto);
        if (innerUploadVoResultEx.isFailed()) {
            return innerUploadVoResultEx;
        }
        InnerUploadVo innerUploadVo = innerUploadVoResultEx.getData();
        innerUploadVo.setFileUrl(contentProperties.getBaseUrl() + File.separator + innerUploadVo.getFile());
        return ResultEx.ok(innerUploadVo);
    }

OKHttp实现文件上传

OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        MediaType mediaType = MediaType.parse("multipart/form-data");
        RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
                .addFormDataPart("uploadFile", "D:/112.jpg",
                        RequestBody.create(MediaType.parse("application/octet-stream"),
                                new File("D:/112.jpg")))
                .build();
        Request request = new Request.Builder()
                .url("http://ip:port/inner_file/upload?app=CIPS&fileType=ID-CARD-FRONT&bizKey=salary")
                .method("POST", body)
                .addHeader("Content-Type", "multipart/form-data")
                .build();
        Response response = client.newCall(request).execute();

        System.err.println("response: " + response.body().string());

文件服务下载功能

     @ApiOperation("文件下载")
    @RequestMapping(value = "/download/{fileNo}", method = {RequestMethod.POST, RequestMethod.GET})
    public ResultEx download(@ApiParam(name = "文件编号") @PathVariable("fileNo") String fileNo, HttpServletResponse response, HttpServletRequest request) {
        String realFileNo = getRealFileNo(fileNo);

        FileInfo fileInfo = fileInfoFacade.getByFileNo(realFileNo);
        if (ObjectUtil.isEmpty(fileInfo)) {
            return ResultEx.makeResult(ResultCode.DATA_NOT_EXISTS);
        }

        response.setCharacterEncoding(request.getCharacterEncoding());
        ContentTypeEnum contentTypeEnum = ApiEnum.parse(ContentTypeEnum.class, fileInfo.getSuffix().toLowerCase());
        if (ObjectUtil.isEmpty(contentTypeEnum)) {
            contentTypeEnum = ContentTypeEnum.DEFAULT;
        }
        response.setContentType(contentTypeEnum.getText());
        FileInputStream fis = null;
        try {
            if (contentTypeEnum.getValue().equals(ContentTypeEnum.DEFAULT.getValue())) {
                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileInfo.getFileName(), "utf-8"));
            } else {
                response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileInfo.getFileName(), "utf-8"));
            }
            File file = new File(fileInfo.getFileUri());
            fis = new FileInputStream(file);

            IOUtils.copy(fis, response.getOutputStream());
            response.flushBuffer();
        } catch (Exception e) {
            e.printStackTrace();
            return ResultEx.fail();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return ResultEx.ok();
    }
    
     /**
     * 获取文件真实编号
     */
    private String getRealFileNo(String fileNo) {
        if (fileNo.contains(".")) {
            return fileNo.substring(0, fileNo.lastIndexOf("."));
        }
        return fileNo;
    }

OKHttp实现文件下载

public static void main(String[] args) throws IOException {
        // 全是OKhttp中的类定义
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        MediaType mediaType = MediaType.parse("text/plain");
        RequestBody body = RequestBody.create(mediaType, "");
        Request request = new Request.Builder()
                .url("http://ip:port/inner_file/download/2cb5005324b8726ab9f02cba18a5bf51")
                .method("POST", body)
                .build();
        Response response = client.newCall(request).execute();
        byte[] bytes = response.body().bytes();

        String filePath = "E:/";
        String fileName = "test.jpg";
        fileFromBytes(bytes, filePath, fileName);


        String value = "1.0";
        if (StringUtils.isNotEmpty(value)) {
            System.err.println(value.matches("^([1-9][0-9]*)+(.[0-9]{1,2})?$"));
        }
    }

    public static File fileFromBytes(byte[] bytes, String filePath, String fileName) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {

            file = new File(filePath + fileName);
            if (!file.getParentFile().exists()) {
                //文件夹不存在 生成
                file.getParentFile().mkdirs();
            }
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file;
    }

文件信息类

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("file_info")
public class FileInfo extends DataObjectBase {

    private static final long serialVersionUID = 1L;
    /**
    * 唯一性Id
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
    * 业务编码CODE
    */
    @TableField("biz_type")
    private String bizType;

    /**
    * 业务KEY
    */
    @TableField("biz_key")
    private String bizKey;

    /**
    * 文件类型
    */
    @TableField("file_type")
    private String fileType;

    /**
    * 附件编号
    */
    @TableField("file_no")
    private String fileNo;

    /**
    * 附件后缀
    */
    private String suffix;

    /**
    * 附件URI
    */
    @TableField("file_uri")
    private String fileUri;

    /**
    * 文件名
    */
    @TableField("file_name")
    private String fileName;

    /**
    * 文件大小
    */
    private Long size;

    /**
    * 文件来源应用CODE
    */
    @TableField("app_code")
    private String appCode;

    /**
    * 文件存放渠道
    */
    @TableField("channel_code")
    private String channelCode;

    /**
    * 创建时间
    */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
    * 更新时间
    */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
    * 删除标识
    */
    @TableLogic
    private boolean deleted;

}

类DataObjectBase

public class DataObjectBase implements Serializable {
    public DataObjectBase() {
    }
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

feign声明

@FeignClient(name = "CNT-CONTENT-SERVICE", configuration = FeignConfig.class, fallbackFactory = ContentFallbackFactory.class)
public interface ContentFeign {

    /**
     * 下载
     */
    @GetMapping(value = "/inner_file/download/{fileNo}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    Response download(@PathVariable("fileNo") String fileNo);
}

callback

@Component
@Slf4j
public class ContentFallbackFactory implements FallbackFactory<ContentFeign> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ContentFallbackFactory.class);
    @Override
    public ContentFeign create(Throwable cause) {
        return new ContentFeign(){
            @Override
            public Response download(String fileNo) {
                // 回调策略实现
                return null;
            }
        };
    }
}

其他微服务单元(第三方服务)实现

    @RequestMapping(value = "/credential/download", method = {RequestMethod.POST, RequestMethod.GET})
    @ApiOperation("下載文件")
    public void downloadFile(@RequestParam("fileNo") String fileNo, @RequestParam(value = "suffix", name = "文件后缀,包括点") String suffix,HttpServletResponse response) throws UnsupportedEncodingException {
        Response download = contentFeign.download(fileNo);
        List<String> suffixes = Arrays.asList(".jpg", ".pdf");
        if (!suffixes.contains(suffix.toLowerCase())) {
            throw new RuntimeException("无法下载,不支持的文件类型!");
        }
        response.setHeader("content-type", "multipart/form-data");
        response.setContentType("application/binary;charset=UTF-8");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileNo + suffix, "UTF-8"));
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        byte[] buff = new byte[1024];

        //创建缓冲输入流
        BufferedInputStream bis = null;
        OutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();

            bis = new BufferedInputStream(download.body().asInputStream());
            int read = bis.read(buff);

            // 通过while循环写入到指定了的文件中
            while (read != -1) {
                outputStream.write(buff, 0, buff.length);
                outputStream.flush();
                read = bis.read(buff);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    //出现异常返回给页面失败的信息

                } finally {
                    if (bis != null) {
                        try {
                            bis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

在本地测试,生产环境对应的服务都有使用 

OKHTTP 上传文件 formdata

 /**
     * 写注释是个好习惯
     *
     * @param mFile
     * @param accountIndex
     * @param exportType
     * @param clear
     * @param email
     * @param dimensions
     * @return
     * @throws IOException
     */
    public String upload(MultipartFile mFile, Integer accountIndex, String exportType,
                         Boolean clear, String email, String dimensions) throws IOException {
        // 这里是MultipartFile转File的过程
        File file = new File(Objects.requireNonNull(mFile.getOriginalFilename()));
        FileUtils.copyInputStreamToFile(mFile.getInputStream(), file);
        // url接口路径
        String url = "http://localhost:8080/upload";
        // file是要上传的文件 File()  这边我上传的是excel,其他类型可以自己改这个parse
        RequestBody fileBody = RequestBody.create(MediaType.parse("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), file);//这边是把file写进来,也有写路径的,但我这边是写file文件,parse不行的话可以直接改这个"multipart/form-data"
        // 创建OkHttpClient实例,设置超时时间
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(60L, TimeUnit.SECONDS)
                .writeTimeout(60L, TimeUnit.SECONDS)
                .readTimeout(60L, TimeUnit.SECONDS)
                .build();
        // 不仅可以支持传文件,还可以在传文件的同时,传参数
        MultipartBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM) // 设置传参为form-data格式
                .addFormDataPart("account_index", String.valueOf(accountIndex))
                .addFormDataPart("export_type", exportType)
                .addFormDataPart("clear", String.valueOf(clear))
                .addFormDataPart("email", email)
                .addFormDataPart("dimensions", dimensions)
                .addFormDataPart("file", file.getName(), fileBody) // 中间参数为文件名
                .build();
 
        // 构建request请求体,有需要传请求头自己加
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        Response response = null;
        String result = "";
        try {
            // 发送请求
            response = okHttpClient.newCall(request).execute();
            result = response.body().string();
            log.info(url + "发送请求结果:" + result);
            if (!response.isSuccessful()) {
                log.info("请求失败");
                return "请求失败";
            }
            response.body().close();
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        // 会在本地产生临时文件,用完后需要删除
        if (file.exists()) {
            file.delete();
        }
        return result;
    }

然后controller层的传参需要用@RequestParam或者直接一个请求的实体类,如果使用实体类,千万不要加@RequestBody,不然结合上传文件会失效,上传文件使用@RequestPart("file") MultipartFile file进行传参


(@RequestPart("file") MultipartFile file,
@RequestParam("accountIndex") Integer accountIndex,
@RequestParam("exportType") String exportType,
@RequestParam(value = "clear", required = false) Boolean clear,
@RequestParam("email") String email,
@RequestParam(value = "dimensions", required = false) String dimensions)

入参:示例如上,或者

(@RequestPart("file") MultipartFile file, RequestVo req)

okhttp response leak

WARNING: A connection to https://help.helpling.com/ was leaked. Did you forget to close a response body?

中文response,header中乱码处理

response.setHeader("filename", java.net.URLEncoder.encode(simpleName, "UTF-8"));

解码:
fileName = java.net.URLDecoder.decode(fileName, "UTF-8");

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OkHttp实现带进度条上传文件,可以使用 RequestBody 的子类 ProgressRequestBody 来实现,具体代码如下: ``` public class ProgressRequestBody extends RequestBody { private RequestBody requestBody; private UploadProgressListener progressListener; public ProgressRequestBody(RequestBody requestBody, UploadProgressListener progressListener) { this.requestBody = requestBody; this.progressListener = progressListener; } @Override public MediaType contentType() { return requestBody.contentType(); } @Override public long contentLength() throws IOException { return requestBody.contentLength(); } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink bufferedSink; if (sink instanceof Buffer) { bufferedSink = sink; } else { bufferedSink = Okio.buffer(sink); } CountingSink countingSink = new CountingSink(bufferedSink); BufferedSink progressSink = Okio.buffer(countingSink); requestBody.writeTo(progressSink); progressSink.flush(); progressListener.onProgress(countingSink.getContentLength()); } private class CountingSink extends ForwardingSink { private long contentLength; private long bytesWritten; public CountingSink(Sink delegate) { super(delegate); contentLength = -1; bytesWritten = 0; } @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if (contentLength == -1) { contentLength = contentLength(); } bytesWritten += byteCount; } public long getContentLength() { return contentLength; } public long getBytesWritten() { return bytesWritten; } } } ``` 其中,UploadProgressListener 为上传进度监听器,代码如下: ``` public interface UploadProgressListener { void onProgress(long bytesWritten); } ``` 在上传文件的时候,将 ProgressRequestBody 作为 RequestBody 使用即可: ``` OkHttpClient client = new OkHttpClient(); File file = new File("path/to/file"); RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", file.getName(), new ProgressRequestBody(RequestBody.create(MediaType.parse("application/octet-stream"), file), new UploadProgressListener() { @Override public void onProgress(long bytesWritten) { // 更新上传进度 } })) .build(); Request request = new Request.Builder() .url("http://localhost/upload") .post(requestBody) .build(); Response response = client.newCall(request).execute(); ``` 在 ProgressRequestBody 中,每次写入数据的时候,都会回调 onProgress 方法,可以在这里更新上传进度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值