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

时间:2024-04-21 07:08:18

概览

  • 使用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中,通过配置下面两个参数的值来限制文件的大小

sp