& 文件透传整理

通用文件上传

在工作开发当中,文件上传是一个很常见的功能,例如:Excel导入解析、司机身份证OCR识别等,都会用到文件上传功能。

不管是微服务架构应用,还是单体架构应用,都会有一个通用文件上传接口,来实现统一的文件上传功能。例:

    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "单文件上传")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(name = "file", value = "文件", required = true, dataTypeClass = MultipartFile.class),
            @ApiImplicitParam(name = "type", value = "文件用途<br> " +
                    "1:司机身份证信息<br> " +
                    "2:OCR识别<br> " +
                    "3:备案数据Excel导入<br> " +
                    "4:物料数据Excel导入",
                    required = true, dataTypeClass = String.class)
    })
    @PostMapping("/upload")
    public ServerResponse<FileDto> upload(@RequestParam("file") MultipartFile file,
                                          @RequestParam(value = "type",required = false) String type){
        return  ServerResponse.createBySuccess(uploadService.upload(file,type));
    }

对于Web前端开发同学而言,往往需要先调用文件上传接口,拿到文件上传后的id,再调用业务接口,才能完成该功能。

如果能只调用一次接口,就完成上面的逻辑,是否可行呢?

文件透传-单体架构应用

整体分析

文件上传接口:返回给前端的是文件的详细信息,例如:文件id、文件路径、文件大小等。

业务接口:接收的是文件的id,然后根据文件id获取文件信息,处理业务逻辑。

文件透传接口:将文件上传完之后接着调用业务接口,将业务接口响应的信息原封不动的返回给前端。

代码示例:

环境:SpringBoot、华为云文件存储等,使用什么环境无所谓,思路都一样。

    /**
     * 功能描述: 文件上传(支持批量)并透传到其他接口
     */
   //若使用此接口,业务接口接收文件id时,必须使用fileIds进行接收
    //application/x-www-form-urlencoded
    //application/json
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "文件上传并透传其他接口")
    @ApiImplicitParams(value = {
            @ApiImplicitParam(name = "file1", value = "文件(多个文件file1,file2,file3...)", required = true, dataTypeClass = MultipartFile.class),
            @ApiImplicitParam(name = "type", value = "文件用途<br> " +
                    "1:司机身份证信息<br> " +
                    "2:OCR识别<br> " +
                    "3:备案数据Excel导入<br> " +
                    "4:物料数据Excel导入",
                    required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "destUrl", value = "透传接口的URL(当destUrl为空时,代表不透传其他接口,只进行文件上传)", required = false, dataTypeClass = String.class),
           @ApiImplicitParam(name = "contentType", value = "透传接口的请求方式<br>" +
                    "application/x-www-form-urlencoded<br>" +
                    "application/json<br>" +
                    "当destUrl有值时,此项必填", required = false, dataTypeClass = String.class),
            @ApiImplicitParam(name = "otherParam", value = "透传接口的其他参数,如没有可不填", required = false, dataTypeClass = String.class),
    })
    @PostMapping("/uploadPassthrough")
    public Object uploadPassthrough(HttpServletRequest request){
        return uploadService.uploadPassthrough(request);
    }
    @Autowired
    private FileService fileService;

    @Value("${server.port}")
    private String port;

    @Autowired
    private TransactionService transactionService;

    private static final String DEST_URL = "destUrl";
    private static final String TOKEN = "token";
    private static final String CONTENT_TYPE = "contentType";
    private static final String FILE_TYPE = "fileType";
    private static final String FILE_IDS = "fileIds";


    @Override
    public Object uploadPassthrough(HttpServletRequest request) {
        String token = request.getHeader(TOKEN);
        if (StrUtils.isBlank(token)) {
            throw new BusinessTypeException("令牌为空!");
        }
        if (request instanceof StandardMultipartHttpServletRequest) {
            StandardMultipartHttpServletRequest multipartRequest = (StandardMultipartHttpServletRequest) request;
            Map<String, String[]> paramterMap = multipartRequest.getParameterMap();
            String[] destUrls = paramterMap.get(DEST_URL);
            String[] contentTypes = paramterMap.get(CONTENT_TYPE);
            String[] fileTypes = paramterMap.get(FILE_TYPE);

            if(!arrayIsEmpty(fileTypes)){
                throw new BusinessTypeException("参数不全!");
            }

            //destUrls不为空 但contentType为空
            if (arrayIsEmpty(destUrls) && !arrayIsEmpty(contentTypes)) {
                throw new BusinessTypeException("参数不全!");
            }

            List<Long> fileIdList = new ArrayList<>();
            List<FileDto> fileDtoList = new ArrayList<>();
            MultiValueMap<String, MultipartFile> fileMultiValueMap = multipartRequest.getMultiFileMap();
            if (fileMultiValueMap.isEmpty()) {
                throw new BusinessTypeException("上传文件为空!");
            }

            //手动开启事务
            transactionService.begin();
            try {
                for (Map.Entry<String, List<MultipartFile>> fileEntry : fileMultiValueMap.entrySet()) {
                    if (fileEntry.getValue() != null && !fileEntry.getValue().isEmpty()) {
                        MultipartFile file = fileEntry.getValue().get(0);
                        //this.getFileDto()是上传文件,并返回FileDto
                        FileDto fileDto = this.getFileDto(file, fileTypes[0]);
                        //将文件信息保存到数据库
                        FileDto upload = fileService.save(fileDto);
                        if (null != upload.getId()) {
                            fileIdList.add(upload.getId());
                            fileDtoList.add(upload);
                        }
                    }
                }
                if (CollectionUtils.isEmpty(fileIdList)) {
                    throw new BusinessTypeException("上传文件失败,请稍候重试!");
                }
            }catch (Exception e){
                //此处可以选择回滚并删除上传的文件
                //transactionService.rollback();

                //示例代码 无需回滚 (有文件删除任务调度)
                log.error(e.getMessage(),e);
                throw new BusinessTypeException("上传文件失败,请稍候重试!");
            }finally {
                transactionService.commit();
            }

            if (arrayIsEmpty(destUrls)) {
                paramterMap.remove(DEST_URL);
                paramterMap.remove(CONTENT_TYPE);
                String destUrl = destUrls[0];
                String contentType = contentTypes[0];
                //开始调用业务接口
                String restString = this.sendPost(token, destUrl, contentType, paramterMap, fileIdList);
                if (StrUtils.isNotBlank(restString)) {
                    return restString;
                } else {
                    throw new BusinessTypeException("操作失败");
                }
            } else {
                //把文件信息返回
                return ServerResponse.createBySuccess(fileDtoList);
            }
        }
        throw new BusinessTypeException("操作失败");
    }


/**
     * @Description 调用业务接口 必须为Post请求
     * 
     * @Param token 令牌
     * @Param destUrl 业务接口的路径
     * @Param contentType 业务接口的请求方式
     * @Param paramterMap 业务接口的参数
     * @Param fileIdList 文件id集合
     * @return 业务接口的响应信息
     **/
  private String sendPost(String token,
                            String destUrl,
                            String contentType,
                            Map<String, String[]> paramterMap,
                            List<Long> fileIdList) {

        if (paramterMap != null && !paramterMap.isEmpty()) {
            if (StrUtils.isBlank(destUrl)) {
                return null;
            }
            if (!destUrl.startsWith("/")) {
                destUrl = "/".concat(destUrl);
            }
            log.info("转发路径destUrl:{}", destUrl);

            //第一种请求方式 post/json
            if (contentType.equals(MediaType.APPLICATION_FORM_URLENCODED.toString())) {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                headers.add(TOKEN, token);
                MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
                paramterMap.forEach((k, v) -> multiValueMap.add(k, v[0]));
                if (fileIdList != null && !fileIdList.isEmpty()) {
                    fileIdList.forEach(o -> multiValueMap.add(FILE_IDS, o));
                }
                //请求体
                HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(multiValueMap, headers);
                String responeEntity = RestTemplateUtils.getRestTemplate().postForObject(String.format("http://127.0.0.1:%s", port).concat(destUrl), formEntity, String.class);
                log.info("
转发路径响应信息:{}", responeEntity);
                return responeEntity;
            //第二种请求方式 post/param
            } else {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                headers.add(TOKEN, token);
                Map<String, Object> reqMap = new HashMap<>();
                paramterMap.forEach((k, v) -> reqMap.put(k, v[0]));
                if (fileIdList != null && !fileIdList.isEmpty()) {
                    fileIdList.forEach(o -> reqMap.put(FILE_IDS, o));
                }
                String reqData = JsonUtil.beanToJson(reqMap);
                log.error("reqData:{}",reqData);
                HttpEntity<Object> requestEntity = new HttpEntity<>(reqData, headers);
                ResponseEntity<String> exchange = RestTemplateUtils.getRestTemplate().exchange(String.format("http://127.0.0.1:%s", port).concat(destUrl), HttpMethod.POST, requestEntity, String.class);
                log.info("
转发路径响应信息:{}", exchange);
                return exchange.getBody();
            }
        }
        return null;
    }

测试

    @ApiOperation(value = "测试透传接口(application/x-www-form-urlencoded请求方式)")
    @ApiOperationSupport(order = 4)
    @PostMapping("/api/test1")
    public ServerResponse<Object> uploadPassthroughTest1(@RequestParam String fileIds, @RequestParam String customer){
        FileDto fileById = fileService.findFileById(Long.parseLong(fileIds));
        System.out.println("fileById:"+fileById);
        return ServerResponse.createBySuccess(fileIds + customer);
    }

    @ApiOperation(value = "测试透传接口(application/json请求方式)")
    @ApiOperationSupport(order = 5)
    @PostMapping("/api/test2")
    public ServerResponse<Object> uploadPassthroughTest2(@RequestBody Map map){
        return ServerResponse.createBySuccess(JSONUtil.toJsonStr(map));
    }

以上我们完成了对单体架构应用的文件透传接口的改造!!!

文件透传-微服务架构应用

整体分析

微服务架构应用与单体架构应用的文件透传就一个区别:透传

单体架构应用是透传到接口

微服务架构应用是透传到服务

那么在接口透传时,只需要将接口转发到网关服务即可,由网关再转发到子服务。

环境:SpringCloud、SpringBoot、FastDFS等,使用什么环境无所谓,思路都一样。

需要注意:

下面代码示例中的:private static final String SERVICE_NAME="API-GATEWAY";代表网关服务名,
需要与网关的application.name一致

image-20211113154736354

代码示例:

@RestController
@RequestMapping("fileUpDown")
@Api(value = "文件上传下载", tags = "提供文件上传下载接口")
public class UploadDownController {
    private static final Logger logger=LoggerFactory.getLogger(UploadDownController.class);
    private static final String DOWN_FILE="L";
    private static final String DEL_FILE="D";

    private static final String INVALID_TOKEN = "invalid token";
    private static final String SERVICE_NAME="API-GATEWAY";
    @Autowired
    private FastDFSClient fastClient;
    // 声明 bean
    @Bean
    @LoadBalanced // 增加 load balance 特性.
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // 注入
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private UpDownFileService logService;

    /**
     * 功能描述: 文件上传(支持批量)并透传到其他服务
     */

    @PostMapping("/upload")
    @ApiOperation(value = "文件上传(支持批量)并透传到其他服务" , notes = "head中需增加userId与token",produces = "application/json")
    public String uploadFiles(HttpServletRequest request, HttpServletResponse response) {
        //操作通用返回类
        ResultResponse resp=new ResultResponse();

        String userId=request.getHeader("userId");
        String token=request.getHeader("token");
        if(StringUtils.isBlank(userId)||StringUtils.isBlank(token)) {
            resp.setCode("401");
            resp.setMessage(INVALID_TOKEN);
            logger.error("token或userId为空,请求拒绝!userId:{},token:{}",userId,token);
            return JSONObject.toJSONString(resp);
        }
        if(request instanceof StandardMultipartHttpServletRequest) {
            long start=System.currentTimeMillis();
            List<String> fileNameList= new ArrayList<String>();
            List<String> filePathList= new ArrayList<String>();
            List<String> fileSizeList= new ArrayList<String>();
            StandardMultipartHttpServletRequest multipartRequest=(StandardMultipartHttpServletRequest)request;
            MultiValueMap<String, MultipartFile> fileMultiValueMap = multipartRequest.getMultiFileMap();
            if(fileMultiValueMap!=null&&!fileMultiValueMap.isEmpty()) {
                for (Map.Entry<String,List<MultipartFile>> fileEntry: fileMultiValueMap.entrySet()) {
                    if(fileEntry.getValue()!=null&&!fileEntry.getValue().isEmpty()) {
                        try {
                            UpFilePo upFilePo=new UpFilePo();
                            MultipartFile file=fileEntry.getValue().get(0);
                            String utf8FileName=file.getOriginalFilename();
                            //文件上传操作
                            String filePath=fastClient.uploadFileWithMultipart(file);
                            upFilePo.setFileNm(utf8FileName);
                            upFilePo.setFilePath(filePath);
                            upFilePo.setFileSize(String.valueOf(file.getSize()));
                            upFilePo.setUsrId(Long.parseLong(userId));
                            //log insert
                            logService.insert(upFilePo);
                            fileNameList.add(file.getOriginalFilename());
                            filePathList.add(filePath);
                            fileSizeList.add(String.valueOf(file.getSize()));
                            logger.info("文件上传成功!filePath:{} 耗时:{}",filePath,System.currentTimeMillis()-start);
                        } catch (FastDFSException e) {
                            logger.error(e.getMessage(), e);
                            resp.setCode(ConstantCode.CommonCode.FILE_UP_EXCEPTION.getCode());
                            resp.setMessage(ConstantCode.CommonCode.FILE_UP_EXCEPTION.getMsg());
                        }catch (Exception e) {
                            logger.error(e.getMessage(), e);
                            resp.setCode(ConstantCode.CommonCode.EXCEPTION.getCode());
                            resp.setMessage(ConstantCode.CommonCode.EXCEPTION.getMsg());
                        }
                    }

                }
            }
            Map<String, String[]> paramterMap=multipartRequest.getParameterMap();
            String[] destUrls=paramterMap.get("destUrl");
            if(destUrls!=null&&destUrls[0].length()>0) {
                //移除重定向url参数
                paramterMap.remove("destUrl");
                String destUrl=destUrls[0];
                String restString= sendPost(userId,token,destUrl, paramterMap,fileNameList,filePathList, fileSizeList);
                if(StringUtils.isNotBlank(restString)) {
                    return restString;
                }else {
                    resp.setCode(ConstantCode.CommonCode.EXCEPTION.getCode());
                    resp.setMessage("响应结果为空!");
                }
            }else {
                //把文件路径返回
                resp.setData(filePathList);
            }
        //无文件直接透传
        }else if(request instanceof RequestFacade){
            RequestFacade req=(RequestFacade)request;
            Map<String, String[]> paramterMap=new LinkedHashMap<String,String[]>();
            paramterMap.putAll(req.getParameterMap());
            String[] destUrls=paramterMap.get("destUrl");
            if(destUrls!=null&&destUrls.length>0) {
                //移除重定向url参数
                paramterMap.remove("destUrl");
                String destUrl=destUrls[0];
                String restString= sendPost(userId,token,destUrl, paramterMap,null,null, null);
                if(StringUtils.isNotBlank(restString)) {
                    return restString;
                }else {
                    resp.setCode(ConstantCode.CommonCode.EXCEPTION.getCode());
                    resp.setMessage("响应结果为空!");
                }
            }else {
                //返回失败
                resp.setCode(ConstantCode.CommonCode.EXCEPTION.getCode());
                resp.setMessage(ConstantCode.CommonCode.EXCEPTION.getMsg());
            }
        }
        return JSONObject.toJSONString(resp);
    }

    /**
     * 发送post请求到其他服务
     * 
     * @param userId
     * @param token
     * @param paramterMap
     *            请求的所有参数
     * @param fileNameList
     *            文件名集合
     * @param filePathList
     *            文件路径集合
     * @param fileSizeList
     *               文件大小(bytes)
     * @return
     */
    private String sendPost(String userId,
                            String token,
                            String destUrl,
                            Map<String, String[]> paramterMap,
                            List<String> fileNameList,
                            List<String> filePathList,
                            List<String> fileSizeList) {
        if(paramterMap!=null&&!paramterMap.isEmpty()) {
            if(StringUtils.isNotBlank(destUrl)) {
                if(!destUrl.startsWith("/")) {
                    destUrl="/".concat(destUrl);
                }
                logger.info("转发路径destUrl:{}",destUrl);
                //设置请求头
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                headers.add("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
                headers.add("userId", userId);
                headers.add("token", token);
                MultiValueMap<String, Object> multiValueMap= new LinkedMultiValueMap<String, Object>();
                paramterMap.forEach((k,v)-> multiValueMap.add(k, v[0]));
                if(fileNameList!=null&&!fileNameList.isEmpty()) {
                    fileNameList.forEach(o-> multiValueMap.add("fileNames", o));
                }
                if(filePathList!=null&&!filePathList.isEmpty()) {
                    filePathList.forEach(o-> multiValueMap.add("filePaths", o));
                }
                if(fileSizeList!=null&&!fileSizeList.isEmpty()) {
                    fileSizeList.forEach(o-> multiValueMap.add("fileSize", o));
                }
                //请求体
                HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<MultiValueMap<String, Object>>(multiValueMap, headers);

                String responeEntity=null;
                try {
                    //此处转发到网关服务
                    responeEntity=restTemplate.postForObject(String.format("http://%s", SERVICE_NAME).concat(destUrl), formEntity,String.class);
                }catch(Exception e){
                    logger.error("
");
                    logger.error(e.getMessage(), e);
                    //如果发生异常删除上传文件
                    if(filePathList!=null&&!filePathList.isEmpty()) {
                        filePathList.forEach(filepath-> {
                            try {
                                fastClient.deleteFile(filepath);
                                FileLogPo downFilePo=new FileLogPo();
                                downFilePo.setUsrId(Long.parseLong(userId));
                                downFilePo.setFilePath(filepath);
                                downFilePo.setType(DEL_FILE);//删除
                                logService.insert(downFilePo);
                            } catch (FastDFSException e1) {
                                logger.error(e1.getMessage(),e1);
                            }
                        });
                    }
                    ResultResponse resp=new ResultResponse();
                    resp.setCode(ConstantCode.CommonCode.EXCEPTION.getCode());
                    resp.setMessage(e.getMessage());
                    return JSONObject.toJSONString(resp);
                }
                logger.info("
转发路径响应信息:{}",responeEntity);
                return responeEntity;
            }
        }
        return null;
    }    
}    
原文地址:https://www.cnblogs.com/doagain/p/15548718.html