zoukankan      html  css  js  c++  java
  • & 文件透传整理

    通用文件上传

    在工作开发当中,文件上传是一个很常见的功能,例如: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;
        }    
    }    
  • 相关阅读:
    清除浮动的集中方式
    css动画
    [Asp.net]c#中的斜杠和反斜杠
    C# ASPX页面做上传提示:超过了最大请求长度,解决方法
    在asp.net中用客户端上传控件上传文件( 需要注意的)
    jquery clone 与 clone(true) 的区别
    【转载】用div+css模拟表格对角线
    .net2.0中的Json序列化数据
    jquery操作checkbox的选中与不选中,解决只有一次有用。
    转:不同服务器数据库之间的数据操作[含远程数据库备份]
  • 原文地址:https://www.cnblogs.com/doagain/p/15548718.html
Copyright © 2011-2022 走看看