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

news2024/11/17 23:42:24

概览

  • 使用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.coloradmin.cn/o/1610710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv9改进策略 | 添加注意力篇 | 利用ILSVRC冠军得主SENetV1改善网络模型特征提取能力

一、本文介绍 本文给大家带来的改进机制是SENet&#xff08;Squeeze-and-Excitation Networks&#xff09;其是一种通过调整卷积网络中的通道关系来提升性能的网络结构。SENet并不是一个独立的网络模型&#xff0c;而是一个可以和现有的任何一个模型相结合的模块(可以看作是一…

项目实践:贪吃蛇

引言 贪吃蛇作为一项经典的游戏&#xff0c;想必大家应该玩过。贪吃蛇所涉及的知识也不是很难&#xff0c;涉及到一些C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。这里我会介绍贪吃蛇的一些思路。以及源代码也会给大家放到文章末尾。 我们最终的…

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

【Gradle如何安装配置及使用的教程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

双链表的实现

我们知道链表其实有很多种&#xff0c;什么带头&#xff0c;什么双向啊&#xff0c;我们今天来介绍双向带头循环链表&#xff0c;了解了这个其他种类的链表就很简单了。冲冲冲&#xff01;&#xff01;&#xff01; 链表的简单分类 链表有很多种&#xff0c;什么带头循环链表&…

tcp-learner 数据包分析 20240420

输入输出&#xff1a; 数据包分析&#xff1a; learner和Adapter建立连接。 Learner让Adapter发送RST Adapter没有从SUT抓到任何回复&#xff0c;于是向learner发送timeout learner给adapter发送reset命令&#xff0c;让SUT重置。 这是第一次初始化&#xff0c;由于Adapter和…

Spring Boot后端与Vue前端融合:构建高效旅游管理系统

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

【简单讲解下npm常用命令】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【hackmyVM】whitedoor靶机

文章目录 信息收集1.IP地址2.端口探测nmapftp服务 3.访问主页 漏洞利用1.反弹shell2.尝试提权3.base64解密 提权1.切换用户2.john爆破3.切换Gonzalo用户4.vim提权 信息收集 1.IP地址 ┌─[✗]─[userparrot]─[~] └──╼ $fping -ag 192.168.9.0/24 2> /dev/null192.168…

ZYNQ NVME高速存储之EXT4文件系统

前面文章分析了高速存储的各种方案&#xff0c;目前主流的三种存储方案是&#xff0c;pcie switch高速存储方案&#xff0c;zynq高速存储方案&#xff0c;fpga高速存储方案。虽然三种高速存储方案都可以实现高速存储&#xff0c;但是fpga高速存储方案是最烂的&#xff0c;fpga…

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决

Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决 文章目录 Android Studio 新建Android13 代码提示Build Tools revision XX is corrupted无法编译解决一、前言二、分析解决1、原因分析2、解决方法 三、其他1、Android13 新项目无法编…

什么是时间序列分析

时间序列分析是现代计量经济学的重要内容&#xff0c;广泛应用于经济、商业、社会问题研究中&#xff0c;在指标预测中具有重要地位&#xff0c;是研究统计指标动态特征和周期特征及相关关系的重要方法。 一、基本概念 经济社会现象随着时间的推移留下运行轨迹&#xff0c;按…

随身WiFi真实测评推荐!格行vs新讯随身wifi对比,公认最好的随身WiFi格行随身wifi有什么优势?

在当前移动网络高度发达的时代&#xff0c;随身 WiFi 已成为人们出差、旅行等场景中不可或缺的工具。格行和新讯是目前比较受欢迎的无线随身wifi。本次评测将对比分析这两款产品的区别&#xff0c;做为随身WiFi推荐第一名的格行随身wifi到底有什么优势呢&#xff1f; 品牌对比&…

[阅读笔记15][Orca]Progressive Learning from Complex Explanation Traces of GPT-4

接下来是微软的Orca这篇论文&#xff0c;23年6月挂到了arxiv上。 目前利用大模型输出来训练小模型的研究都是在模仿&#xff0c;它们倾向于学习大模型的风格而不是它们的推理过程&#xff0c;这导致这些小模型的质量不高。Orca是一个有13B参数的小模型&#xff0c;它可以学习到…

C++ 内存分区管理

一、栈区&#xff08;Stack&#xff09; 栈区用来存储函数的参数值、局部变量的值等数据。栈区是自动分配和释放的&#xff0c;函数执行时会在栈区分配空间&#xff0c;函数执行结束时会自动释放这些空间。栈区的数据是连续分配的&#xff0c;由系统自动管理。 注意事项&…

layui框架实战案例(27):弹出二次验证

HTML容器 <button class"layui-btn layui-btn-sm layui-btn-danger" lay-event"delete"><i class"layui-icon layui-icon-delete"></i>批量删除</button>删除封装函数 function delAll(school_id, school_name) {var lo…

牛x之路 - Day1

Day1 微积分之屠龙宝刀&#xff08;武林秘籍&#xff09; 之前的一些东西都在pdf上记得笔记&#xff0c; 没有在这个上面展示一遍&#xff0c;只好学到相关内容的时候再提叙啦&#xff1b;所以其实再写这个小记的时候&#xff0c;我已经看了一半的书&#xff0c;但是不要紧&am…

每日学习笔记:C++ STL算法之移除容器元素

本文API 移除元素 remove(beg, end, value) remove_if(beg, end, op) remove_copy(sourceBeg, sourceEnd, destBeg, value) remove_copy_if(sourceBeg, sourceEnd, destBeg, op) 移除连续重复的元素 unique(beg, end) unique(beg, end, op) unique_copy(sourceBeg, sourceEnd, …

Ribbon 添加快速访问区域

添加快速访问区域挺简单的&#xff0c;实例如下所示&#xff1a; void QtRightFuncDemo::createQuickAccessBar() { RibbonQuickAccessBar* quickAccessBar ribbonBar()->quickAccessBar(); QAction* action quickAccessBar->actionCustomizeButton(); act…

单链表的简单应用

目录 一、顺序表的问题及思考 二、链表的概念及结构 三、单链表的实现 3.1 增 3.1.1 尾插 3.1.2 头插 3.1.3 指定位置前插入 3.1.4 指定位置后插入 3.2 删 3.2.1 尾删 3.2.2 头删 3.2.3 指定位置删除 3.2.4 指定位置后删除 3.2.5 链表的销毁 3.3 查 3.4 改 四…