使用Spring进行文件的上传和下载

概览

  • 使用Spring进行文件的上传和下载
    • Spring上传文件接口设计
    • dubbo接口设计
      • 上传文件流的RPC的接口设计
    • Spring文件下载接口设计
    • dubbo接口设计
      • 下载文件流的RPC的接口设计
    • spring上传文件大小控制

使用Spring进行文件的上传和下载

本文主要介绍在Spring框架下面调用微服务的dubbo rpc接口进行文件的上传和下载,以及记录在实现过程中遇到的一些容易出错的地方。

Spring上传文件接口设计

contoller层的代码实现如下所示:

    @PostMapping("/submitEvidence")
    public BaseResponse<?> submitEvidence(@RequestParam("id") Long id, @RequestParam("label") String label,
                                          @RequestParam(value = "file") MultipartFile file) {
           uploadEvidence(id, label, file);
           return BaseResponse.success().errorMsg("操作成功").build();
        }
    }

使用postman请求上传文件接口,具体参数如下图所示:postman请求截图
Service层代码实现如下所示:

public void uploadEvidence(Long takeDownId, String label, MultipartFile multipartFile) {
        if(Objects.isNull(multipartFile)) {
            throw new RunTimeException("上传的文件不能为空");
        }
        String fileName = multipartFile.getOriginalFilename();
        InputStream file = null;
        try {
            file = multipartFile.getInputStream();
        } catch (IOException e) {}
        byte[] fileBytes = new byte[20 * 1024 * 1024];
        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            outputStream =  new ByteArrayOutputStream();
            inputStream = multipartFile.getInputStream();
            try {
                byte[] buffer = new byte[1024];
                int read = inputStream.read(buffer);
                while (read != -1) {
                    outputStream.write(buffer, 0, read);
                    read = inputStream.read(buffer);
                }
            } catch (Exception e) {
                log.error("处理返回值失败" + e.getMessage());
                throw new RunTimeException("上传文件失败");
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            outputStream.flush();
            fileBytes = outputStream.toByteArray();
        } catch (IOException e) {
        }finally {
            if(outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                    throw new RuntimeException("上传文件失败");
                }
            }
        }
        UploadEvidenceNetCraftReq req = UploadEvidenceNetCraftReq.builder()
                        .takeDownId(takeDownId.intValue()).label(label).fileName(fileName).fileBytes(fileBytes).build();
        // outVendor是一个dubbo框架下的rpc服务,用于上传文件
        BaseResponse baseResponse = outerVendorsService.uploadEvidence(req);
        ...
    }

dubbo接口设计

上传文件流的RPC的接口设计

我们构建的RPC接口采用的是dubbo框架,最初始的接口设计,将HttpServletResponse作为接口的参数类型传参,结果报错
io.netty.handler.codec.EncoderException: java.lang.IllegalStateException: Serialized class

Dubbo报错:io.netty.handler.codec.EncoderException: java.lang.IllegalStateException: Serialized class

HttpServletResponse不能被dubbo作为接口参数序列化,于是转而求其次,将可序列化的类型byte[]作为传输流的参数
outerVendorsService服务提供的上传文件的rpc接口:uploadEvidence接口,具体代码设计如下所示:

BaseResponse<UploadEvidenceRsp> uploadEvidence(UploadEvidenceReq req);
@Data
@Builder
@Jacksonized
public class UploadEvidenceReq implements Serializable {
    private Integer takeDownId;
   //使用可序列化的byte数组作为入参
    private byte[] fileBytes;

    private String fileName;

    private String label;
}

@Data
@Setter
@Getter
public class UploadEvidenceRsp implements Serializable {
    private Integer file_id;

    @JsonProperty("error_code")
    private String errorCode;

    @JsonProperty("error_message")
    private String errorMessage;
}

Spring文件下载接口设计

文件下载的相关接口有两种实现:一种是将HttpServletResponse作为controller层的传参引入,将流写入到HttpServletResponse中,然后返回前端,但是在使用过程中,直接在HttpServletResponse的示例中setHeader失败,于是转而选择构ResponseEntity的方式来进行http返回值的构造,具体实现如下所示:

@GetMapping("/searchForEvidence")
public ResponseEntity searchForEvidence(@RequestParam String id) {
    return searchForEvidence(id);
}

使用postman请求下载文件接口,具体参数如下图所示:
下载文件接口
Service层代码实现如下所示:

public ResponseEntity fetchEvidence(Long takeDownId){
    FetchEvidenceNetCraftReq req = FetchEvidenceNetCraftReq.builder().takeDownId(takeDownId.intValue()).build();
    BaseResponse<QueryEvidenceRsp> rsp = outerVendorsService
            .fetchEvidence(req);
    if(Objects.isNull(rsp)) {
        throw new RunTimeException("拉取文件失败");
    }
    byte[] outPutBytes = null;
    if(Objects.nonNull(rsp) && rsp.isSuccess() == true) {
        QueryEvidenceRsp evidenceRsp = rsp.getResult();
        if(Objects.nonNull(evidenceRsp.getErrorCode())){
            String message = "errorCode:" + evidenceRsp.getErrorCode() + ",errorMessage:" + evidenceRsp.getErrorMessage();
            throw new RunTimeException(message);
        }
        outPutBytes = evidenceRsp.getFileBytes();
        String fileName = evidenceRsp.getFileName();
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentType(MediaType.valueOf(MediaType.APPLICATION_OCTET_STREAM_VALUE));// 设置文件格式
        responseHeaders.setContentLength(outPutBytes.length);
        responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName);// 设置文件名
        return new ResponseEntity<>(outPutBytes, responseHeaders, HttpStatus.OK);
    }
    throw new RunTimeException("拉取文件失败");
}

public class QueryEvidenceRsp implements Serializable {
    byte[] fileBytes;
    String fileName;
    String errorCode;
    String errorMessage;
}

dubbo接口设计

下载文件流的RPC的接口设计

参照之前的上传文件的设计,服务提供的下载文件的rpc接口设计:

@Data
@Setter
@Getter
public class QueryEvidenceRsp implements Serializable {
    byte[] fileBytes;
    String fileName;
    String errorCode;
    String errorMessage;
}

@Data
@Builder
@Jacksonized
public class FetchEvidenceNetCraftReq implements Serializable {
    private Integer takeDownId;
}

    @Override
    public BaseResponse fetchEvidence(FetchEvidenceNetCraftReq req) {
        if(Objects.isNull(netCraftConfig) || Objects.isNull(netCraftConfig.getAccessNetCraftDomain())) {
            log.error("netCraft config is not set");
            return BaseResponse.fail().errorCode(ErrorCode.BUSINESS_EXCEPTION.getCode())
                    .errorMsg("netCraft config is not set").build();
        }
        String fetch_evidence = "https://" + netCraftConfig.getAccessNetCraftDomain()
                + netCraftConfig.getFETCH_EVIDENCE();
        Map<String, String> headers = new HashMap<>();
        headers.put("content-type", "application/json");
        headers.put("Authorization", netCraftConfig.getAccessNetCraftAuthToken());
        ByteArrayOutputStream  outputStream = new ByteArrayOutputStream();
        byte[] fileBytes;
        String fileName;
        try {
            outputStream =  new ByteArrayOutputStream();
            fileName = getFromOctetStream(fetch_evidence + "?takedown_id="
                    + req.getTakeDownId(), headers, outputStream);
            if(Objects.isNull(fileName)) {
                return BaseResponse.fail().errorCode(ErrorCode.BUSINESS_EXCEPTION.getCode())
                        .errorMsg("获取netcraft证据文件失败").build();
            }
            outputStream.flush();
            fileBytes = outputStream.toByteArray();
        } catch (Exception e) {
            log.error("获取证据文件失败:" + e.getMessage());
            return BaseResponse.fail().errorCode(ErrorCode.BUSINESS_EXCEPTION.getCode())
                    .errorMsg("获取netcraft证据文件失败").build();
        } finally {
            if(outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
        NetCraftQueryEvidenceRsp rsp = new NetCraftQueryEvidenceRsp();
        if(Objects.nonNull(fileName)) {
            NetCraftErrorMessageRsp errorMessageRsp = JsonUtils.fromCamelJson(fileName, NetCraftErrorMessageRsp.class);
            if(Objects.nonNull(errorMessageRsp)
                    && Objects.nonNull(errorMessageRsp.getErrorCode())
                    && Objects.nonNull(errorMessageRsp.getErrorMessage())) {
                rsp.setErrorCode(errorMessageRsp.getErrorCode());
                rsp.setErrorMessage(errorMessageRsp.getErrorMessage());
                return BaseResponse.success(rsp).build();
            }
            rsp.setFileBytes(fileBytes);
            rsp.setFileName(fileName);
            return BaseResponse.success(rsp).build();
        }
        return BaseResponse.fail().errorCode(ErrorCode.BUSINESS_EXCEPTION.getCode())
                .errorMsg("获取netcraft证据文件失败").build();
    }

    public static String getFromOctetStream(String url, Map<String, String> headers, OutputStream outputStream) {
        return getFromOctetStream(url, headers, OK_HTTP_CLIENT_30s, outputStream);
    }
  public static String getFromOctetStream(String url, Map<String, String> headers, String client, OutputStream outputStream) {
        Request.Builder requestBuilder = new Request.Builder();
        requestBuilder.url(url);
        if (headers != null && headers.size() > 0) {
            for (String s : headers.keySet()) {
                requestBuilder.addHeader(s, headers.get(s));
            }
        }
        requestBuilder.get();
        Request req = requestBuilder.build();
        try (Response response = okHttpClientMap.get(client).newCall(req).execute()) {
            log.info("okhttp send get,resp:{}", JsonUtils.toJson(response));
            if (null != response.body()) {
                InputStream inputStream = response.body().byteStream();
                try {
                    byte[] buffer = new byte[1024];
                    int read = inputStream.read(buffer);
                    while (read != -1) {
                        outputStream.write(buffer, 0, read);
                        read = inputStream.read(buffer);
                    }
                } catch (Exception e) {
                    log.error("处理返回值失败" + e.getMessage());
                    return null;
                } finally {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                String contentDisposition = response.header("Content-Disposition");
                if (Objects.isNull(contentDisposition)) {
                    log.error("调用netcraft获取证据接口, 获取文件名失败");
                    return outputStream.toString();
                }
                // 解析文件名
                return contentDisposition.substring(contentDisposition.indexOf("filename=") + 9);
            }

spring上传文件大小控制

在spring配置文件application.properties中,通过配置下面两个参数的值来限制文件的大小

spring.servlet.multipart.max-file-size=-1
spring.servlet.multipart.max-request-size=-1

spring.servlet.multipart.max-file-size配置限制上传单个文件的大小,为-1代表不限制
spring.servlet.multipart.max-request-size配置限制http中上传总文件的大小,为-1代表不限制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/553725.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记(4月17日)vector底层原理

1.vector<vector>底层原理 vector是表示可变大小数组的序列容器&#xff0c;相当于一个动态的数组&#xff0c;比数组优越的在于它具有可动态改变的大小&#xff0c;同时&#xff0c;它写成了类模板&#xff0c;说明可以适用于其他类型&#xff0c;包括vector本身&#…

AI赋能自动化测试:智能接口自动化测试数据生成平台设计思路

目录 1.背景 2.名词解释 3.设计目标 4.设计思路及折衷 4.1阶段性任务 4.2方案选型 4.2.1 设计方案选型 4.2.1.1 原始数据获取模块 4.2.1.2 数据构造模块 4.2.1.3 预执行模块 4.2.1.4 覆盖率反馈调整模块 4.2.1.5 预测模型 4.2.2 技术选型 5.系统设计 5.1 项目架…

LeetCode669:修剪二叉搜索树

题目描述 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即&#xff0c;如果没有被移除&#xff0c;原有的父代子代关系都应…

vivo怎么录屏?玩转手机新体验!(2024最新)

“最近新入手了一款vivo手机&#xff0c;感觉用起来真的很顺手&#xff0c;各种功能都很强大。昨天和朋友一起打游戏&#xff0c;突然想到要记录下精彩瞬间&#xff0c;但是找遍了手机&#xff0c;都没有找到录屏功能。我想请教一下各位vivo手机用户&#xff0c;你们通常在哪里…

解决动态规划问题

文章目录 动态规划的定义动态规划的核心思想青蛙跳阶问题解法一&#xff1a;暴力递归解法二&#xff1a;带备忘录的递归解法&#xff08;自顶向下&#xff09;解法三&#xff1a;动态规划&#xff08;自底向上&#xff09; 动态规划的解题套路什么样的问题考虑使用动态规划&…

转录组上游分析,Count计算

本期教程原文链接&#xff1a;转录组定量&#xff0c;最简单的操作&#xff0c;你会吗&#xff1f; 本期教程 第六章 转录本定量分析 定量软件有RSEM,eXpress,salmoe,kallisto&#xff0c;featureCounts。在网络中吗&#xff0c;都有比较详细的教程&#xff0c;大家可以自己去…

最新UI发卡盗U,支持多语言,更新UI界面,支持多个主流钱包,附带系统搭建教程

环境&#xff1a;Linux系统 进入宝塔安装环境&#xff1a;Nginx 1.22.1 MySQL 8.0 php7.4 phpMyAdmin 5.2 按照说明去安装环境&#xff0c;如果没有找到MySQL8.0版本去"软件商店"搜索Mysql切换至8.0 1.上传开源源码 2.上传数据库文件 3.上传猴导入数据库文件 4.修…

SpringBoot+FreeMaker

目录 1.FreeMarker说明2.SpringBootFreeMarker快速搭建Pom文件application.properties文件Controller文件目录结构 3.FreeMarker数据类型3.1.布尔类型3.2.数值类型3.3.字符串类型3.4.日期类型3.5.空值类型3.6.sequence类型3.7.hash类型 4.FreeMarker指令assign自定义变量指令if…

HTML学习笔记:(一)基础方法

Html格式 里面文件使用平台为&#xff1a;w3school 1、基础功能&#xff1a; <html><head> <title>这是我的第一个html页面,会显示在浏览器的标题栏中</title> </head> <!--修改背景颜色 --> <body bgcolor"yellow"> …

QtQuick 学习笔记(二)按钮组件

1. QPushButton 功能 按压按钮&#xff0c;用于接受用户点击事件&#xff0c;可显示设定的字符串提示信息&#xff0c;但需要父组件作为容器&#xff0c;多用以执行命令或触发事件 常用函数 QPushButton&#xff1a;&#xff1a;QPushButton&#xff08;const QString &…

RHCE1

unit1.定时任务和延迟任务项目 1.在系统中设定延迟任务要求如下: 在系统中建立easylee用户&#xff0c;设定其密码为easylee 延迟任务由root用户建立 要求在5小时后备份系统中的用户信息文件到/backup中确保延迟任务是使用非交互模式建立 再使用chmod修改权限&#xff1a; 确保…

HarmonyOS真机调试页面运行卡顿/黑屏解决方法,亲测有效

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 用mate40等发行时间相对较早但系统是HarmonyOS4.0的真机调试 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 程序点击容易卡顿或黑屏 原因分析&#xff1a; CPU兼容问题导致屏幕…

Text2sql的一些技巧

最近看到了一篇关于text2sql的文章&#xff0c;以及一些论文。对使用模型做text2sql给了一些不错的建议。 参考文章&#xff1a;24年大模型潜力方向&#xff1a;大浪淘沙后的Text-to-SQL和Agent - 知乎 论文&#xff1a;https://arxiv.org/pdf/2403.09732.pdf 关于模型的建议 …

STM32H7定时器TIM1-TIM17中断、PWM实现

STM32H7定时器TIM1-TIM17中断、PWM实现 高级定时器硬件框图定时器模式时基输出PWM定时器输入捕获 TIM1-TIM17的中断配置TIM1-TIM17的PWM输出 STM32H7 支持的定时器有点多&#xff0c;要简单的区分下。STM32H7 支持 TIM1-TIM8&#xff0c;TIM12-TIM17 共14 个定时器&#xff0c;…

使用API有效率地管理Dynadot域名,锁定账户中的域名

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

Zynq7000系列中的时钟管理

PS&#xff08;处理系统&#xff09;时钟子系统生成的所有时钟都源自三个可编程PLL&#xff08;锁相环&#xff09;中的一个&#xff1a;CPU、DDR和I/O。时钟子系统的主要组件如图25-1所示。 在正常工作期间&#xff0c;PLL被启用&#xff0c;并由PS_CLK时钟引脚驱动。在启用P…

6.6Python之集合的基本语法和特性

集合&#xff08;Set&#xff09;是Python中的一种无序、不重复的数据结构。集合是由一组元素组成的&#xff0c;这些元素必须是不可变数据类型&#xff0c;但在集合中每个元素都是唯一的&#xff0c;即集合中不存在重复的元素。 集合的基本语法&#xff1a; 1、元素值必须是…

24卫生高级职称报名时间汇总⏰报名全流程

⏰卫生高级职称&#xff08;网上报名&#xff09;时间汇总 ✔️陕西&#xff1a;4月23日-5月24日 ✔️上海&#xff1a;4月23日-5月24日 ✔️重庆&#xff1a;4月23日—5月24日 ✔️黑龙江&#xff1a;4月23日-5月24日 ✔️浙江&#xff1a;4月23日-5月24日 ✔️云南&#xff1…

面试自救指南:女生如何巧答私密问题

在面试过程中&#xff0c;女性应聘者可能会遇到一些私人问题&#xff0c;这些问题可能涉及婚姻、家庭、生育等方面。面对这些问题&#xff0c;如何回答才能既保持真实又不失礼节呢&#xff1f; 当遇到关于婚姻状况的问题时&#xff0c;您可以选择回答&#xff1a;“我目前的婚姻…

【Python深度学习系列】网格搜索神经网络超参数:权重初始化方法(案例+源码)

这是我的第262篇原创文章。 一、引言 在深度学习中&#xff0c;超参数是指在训练模型时需要手动设置的参数&#xff0c;它们通常不能通过训练数据自动学习得到。超参数的选择对于模型的性能至关重要&#xff0c;因此在进行深度学习实验时&#xff0c;超参数调优通常是一个重要的…
最新文章