Spring实现文件上传的配置详解

添加依赖

主要用来解析request请求流,获取文件字段名、上传文件名、content-type、headers等内容组装成FileItem

        <!--添加fileupload依赖-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>

构建单例bean

CommonsMultipartResolver,将request请求从类型HttpServletRequest转化成MultipartHttpServletRequest,从MultipartHttpServletRequest可以获取上传文件的各种信息文件名、文件流等内容

注意:该bean的beanName要写成multipartResolver,否则无法获取到该bean

@Bean
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
    // 上传限制最大字节数 -1表示没限制
    commonsMultipartResolver.setMaxUploadSize(-1);
    // 每个文件限制最大字节数 -1表示没限制
    commonsMultipartResolver.setMaxUploadSizePerFile(-1);
    commonsMultipartResolver.setDefaultEncoding(StandardCharsets.UTF_8.name());
    return commonsMultipartResolver;
}

#DispatcherServlet
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
	try {
		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		if (logger.isDebugEnabled()) {
			logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
		}
	}
	catch (NoSuchBeanDefinitionException ex) {
		// Default is no multipart resolver.
		this.multipartResolver = null;
		if (logger.isDebugEnabled()) {
			logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
					"': no multipart request handling provided");
		}
	}
}

校验请求

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // multipartResolver不为空 且 request请求头中的content-type以multipart/开头
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                         "this typically results from an additional MultipartFilter in web.xml");
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                         "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // 解析请求
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

解析请求

@Override
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
    Assert.notNull(request, "Request must not be null");
    MultipartParsingResult parsingResult = parseRequest(request);
    return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
                                                  parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
    String encoding = determineEncoding(request);
    // 获取FileUpload实例
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
        // 将request请求解析成FileItem
        List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
        return parseFileItems(fileItems, encoding);
    }
    catch (FileUploadBase.SizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
    }
    catch (FileUploadBase.FileSizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
    }
    catch (FileUploadException ex) {
        throw new MultipartException("Failed to parse multipart servlet request", ex);
    }
}

// ctx->将request进行了包装
public List<FileItem> parseRequest(RequestContext ctx)
    throws FileUploadException {
  List<FileItem> items = new ArrayList<FileItem>();
  boolean successful = false;
  try {
    // 通过ctx构建FileItem流的迭代器
    FileItemIterator iter = getItemIterator(ctx);
    // FileItemFactory创建FileItem的工厂对象
    FileItemFactory fac = getFileItemFactory();
    if (fac == null) {
      throw new NullPointerException("No FileItemFactory has been set.");
    }
    // 判断是否itemValid是否为true,是否有可读文件
    while (iter.hasNext()) {
      final FileItemStream item = iter.next();
      // Don't use getName() here to prevent an InvalidFileNameException.
      // 文件名称
      final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
      // 构建FileItem
      FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
          item.isFormField(), fileName);
      items.add(fileItem);
      try {
        // 将FileItemStreamImpl流拷贝到fileItem的输出流中(系统会自建文件)
        Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
      } catch (FileUploadIOException e) {
        throw (FileUploadException) e.getCause();
      } catch (IOException e) {
        throw new IOFileUploadException(format("Processing of %s request failed. %s",
            MULTIPART_FORM_DATA, e.getMessage()), e);
      }
      final FileItemHeaders fih = item.getHeaders();
      fileItem.setHeaders(fih);
    }
    successful = true;
    return items;
  } catch (FileUploadIOException e) {
    throw (FileUploadException) e.getCause();
  } catch (IOException e) {
    throw new FileUploadException(e.getMessage(), e);
  } finally {
    if (!successful) {
      for (FileItem fileItem : items) {
        try {
          fileItem.delete();
        } catch (Throwable e) {
          // ignore it
        }
      }
    }
  }
}

#解析获取到的fileItems
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
  MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
  Map<String, String[]> multipartParameters = new HashMap<>();
  Map<String, String> multipartParameterContentTypes = new HashMap<>();

  // Extract multipart files and multipart parameters.
  for (FileItem fileItem : fileItems) {
    // 是否是表单字段(下面的解析可以看到构建时该字段传参 fileName == null),也就是文件名是否为空
    if (fileItem.isFormField()) {
      String value;
      String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
      try {
        value = fileItem.getString(partEncoding);
      }
      catch (UnsupportedEncodingException ex) {
        if (logger.isWarnEnabled()) {
          logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
              "' with encoding '" + partEncoding + "': using platform default");
        }
        value = fileItem.getString();
      }
      String[] curParam = multipartParameters.get(fileItem.getFieldName());
      if (curParam == null) {
        // simple form field
        multipartParameters.put(fileItem.getFieldName(), new String[] {value});
      }
      else {
        // array of simple form fields
        String[] newParam = StringUtils.addStringToArray(curParam, value);
        multipartParameters.put(fileItem.getFieldName(), newParam);
      }
      multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
    }
    else {
      // multipart file field 构建MultipartFile
      CommonsMultipartFile file = createMultipartFile(fileItem);
      // 以文件字段名为key (files)
      multipartFiles.add(file.getName(), file);
      if (logger.isDebugEnabled()) {
        logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() +
            " bytes with original filename [" + file.getOriginalFilename() + "], stored " +
            file.getStorageDescription());
      }
    }
  }
  return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}

主要逻辑是这行代码FileItemIterator iter = getItemIterator(ctx);,FileItem流迭代器的构造

#构造方法
FileItemIteratorImpl(RequestContext ctx)
    throws FileUploadException, IOException {
  if (ctx == null) {
    throw new NullPointerException("ctx parameter");
  }

  // 获取request的content-type,需要以multipart/ 开头
  String contentType = ctx.getContentType();
  if ((null == contentType)
      || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
    throw new InvalidContentTypeException(
        format("the request doesn't contain a %s or %s stream, content type header is %s",
            MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
  }

  // 获取request的输入流
  InputStream input = ctx.getInputStream();

  // 获取内容长度 content-length 从request中取
  @SuppressWarnings("deprecation") // still has to be backward compatible
  final int contentLengthInt = ctx.getContentLength();

  // 通过request.getHeader()取
  final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
      // Inline conditional is OK here CHECKSTYLE:OFF
      ? ((UploadContext) ctx).contentLength()
      : contentLengthInt;
  // CHECKSTYLE:ON

  // sizeMax限制流大小 -1则不限制
  if (sizeMax >= 0) {
    if (requestSize != -1 && requestSize > sizeMax) {
      throw new SizeLimitExceededException(
          format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
              Long.valueOf(requestSize), Long.valueOf(sizeMax)),
          requestSize, sizeMax);
    }
    input = new LimitedInputStream(input, sizeMax) {
      @Override
      protected void raiseError(long pSizeMax, long pCount)
          throws IOException {
        FileUploadException ex = new SizeLimitExceededException(
            format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
                Long.valueOf(pCount), Long.valueOf(pSizeMax)),
            pCount, pSizeMax);
        throw new FileUploadIOException(ex);
      }
    };
  }

  // 获取字符编码
  String charEncoding = headerEncoding;
  if (charEncoding == null) {
    charEncoding = ctx.getCharacterEncoding();
  }

  // 通过content-type = multipart/form-data; boundary=--------------------------205940049223747054037567
  // 获取boundary的值分隔符(一串随机字符?)并转化为字节数组
  boundary = getBoundary(contentType);
  if (boundary == null) {
    throw new FileUploadException("the request was rejected because no multipart boundary was found");
  }

  // 进度更新器
  notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
  try {
    // 构建多元流
    multi = new MultipartStream(input, boundary, notifier);
  } catch (IllegalArgumentException iae) {
    throw new InvalidContentTypeException(
        format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
  }
  // 设置请求头编码
  multi.setHeaderEncoding(charEncoding);

  // 跳过序言
  skipPreamble = true;
  // 开始找第一个文件项目
  findNextItem();
}

接着再来看下MultipartStream的构建

#MultipartStream构造
public MultipartStream(InputStream input,  // request输入流
    byte[] boundary,  // 边界 字节数组
    int bufSize, // 缓冲区大小 默认4096
    ProgressNotifier pNotifier) {

  if (boundary == null) {
    throw new IllegalArgumentException("boundary may not be null");
  }
  // We prepend CR/LF to the boundary to chop trailing CR/LF from
  // body-data tokens.   CR 回车\r LF 换行\n
  // protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
  this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
  // 缓冲区大小判断
  if (bufSize < this.boundaryLength + 1) {
    throw new IllegalArgumentException(
        "The buffer size specified for the MultipartStream is too small");
  }

  this.input = input;
  // 重新确定缓冲区大小
  this.bufSize = Math.max(bufSize, boundaryLength * 2);
  // 创建缓冲区 用来从读inputStream 接受数据
  this.buffer = new byte[this.bufSize];
  this.notifier = pNotifier;

  // 边界数组
  this.boundary = new byte[this.boundaryLength];
  this.keepRegion = this.boundary.length;

  // 将BOUNDARY_PREFIX数组和入参boundary数组的内容按序复制到新的boundary中
  System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
      BOUNDARY_PREFIX.length);
  System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
      boundary.length);

  // head和tail为缓冲区操作的索引
  // 0 <= head < bufSize
  // 0 <= tail <= bufSize
  head = 0;
  tail = 0;
}

接着看findNextItem方法,找第一个文件项目

/**
 * Called for finding the next item, if any.
 *
 * @return True, if an next item was found, otherwise false.
 * @throws IOException An I/O error occurred.
 */
private boolean findNextItem() throws IOException {
  if (eof) {
    return false;
  }
  // 开始为null
  if (currentItem != null) {
    currentItem.close();
    currentItem = null;
  }
  for (;;) {
    boolean nextPart;
    if (skipPreamble) {
      // 丢弃直到边界分隔符的所有数据 再读取边界
      nextPart = multi.skipPreamble();
    } else {
      // 直接读取边界
      nextPart = multi.readBoundary();
    }
    if (!nextPart) {
      if (currentFieldName == null) {
        // Outer multipart terminated -> No more data
        eof = true;
        return false;
      }
      // Inner multipart terminated -> Return to parsing the outer
      multi.setBoundary(boundary);
      currentFieldName = null;
      continue;
    }
    // 解析头部 multi.readHeaders()从缓冲区解析到所有请求头的字符串
    // 接着getParsedHeaders将字符串按\r\n分隔,因为每一行数据都是一个请求头内容
    FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
    if (currentFieldName == null) {
      // We're parsing the outer multipart
      // 获取上传文件字段参数名name = files
      String fieldName = getFieldName(headers);
      if (fieldName != null) {
        String subContentType = headers.getHeader(CONTENT_TYPE);  // img/jpeg
        if (subContentType != null
            &&  subContentType.toLowerCase(Locale.ENGLISH)
            .startsWith(MULTIPART_MIXED)) {
          currentFieldName = fieldName;
          // Multiple files associated with this field name
          byte[] subBoundary = getBoundary(subContentType);
          multi.setBoundary(subBoundary);
          skipPreamble = true;
          continue;
        }
        // 文件名 IMG_0908.JPG
        String fileName = getFileName(headers);
        // 根据字段名、文件名、请求头等构建FileItemStream对象
        currentItem = new FileItemStreamImpl(fileName,
            fieldName, headers.getHeader(CONTENT_TYPE),
            fileName == null, getContentLength(headers));
        // 设置请求头
        currentItem.setHeaders(headers);
        // ++items
        notifier.noteItem();
        // 当期有可用item
        itemValid = true;
        return true;
      }
    } else {
      String fileName = getFileName(headers);
      if (fileName != null) {
        currentItem = new FileItemStreamImpl(fileName,
            currentFieldName,
            headers.getHeader(CONTENT_TYPE),
            false, getContentLength(headers));
        currentItem.setHeaders(headers);
        notifier.noteItem();
        itemValid = true;
        return true;
      }
    }
    multi.discardBodyData();
  }
}

下图为缓冲区数据,首行为boundary分隔符内容也就是上文提到的----加一串计算出来的随机字符,\r\n后接着为content-Disposition和content-type请求头,可以看到 HEADER_SEPARATOR头部分隔符\r\n\r\n 和分隔符之前的为请求头数据

另外boundary字节数组对应的内容为--------------------------031262929361076583805179,下图的首行内容比其多两个-

下图为解析完后的头部

接下来再看下FileItemStreamImpl的构造过程,比较简单

#FileItemStreamImpl构造方法
FileItemStreamImpl(String pName, String pFieldName,
    String pContentType, boolean pFormField,
    long pContentLength) throws IOException {
  name = pName;
  fieldName = pFieldName;
  contentType = pContentType;
  formField = pFormField;
  // 创建itemStream流 本质上是从request的inputstream获取数据
  // 从head位置再开始找boundary边界分隔符,若找到将边界的前一个索引赋值给pos变量,并且当前文件可读字符数为pos - head
  final ItemInputStream itemStream = multi.newInputStream();
  InputStream istream = itemStream;
  // 若文件大小限制,超出长度会抛出异常
  if (fileSizeMax != -1) {
    if (pContentLength != -1
        &&  pContentLength > fileSizeMax) {
      FileSizeLimitExceededException e =
          new FileSizeLimitExceededException(
              format("The field %s exceeds its maximum permitted size of %s bytes.",
                  fieldName, Long.valueOf(fileSizeMax)),
              pContentLength, fileSizeMax);
      e.setFileName(pName);
      e.setFieldName(pFieldName);
      throw new FileUploadIOException(e);
    }
    istream = new LimitedInputStream(istream, fileSizeMax) {
      @Override
      protected void raiseError(long pSizeMax, long pCount)
          throws IOException {
        itemStream.close(true);
        FileSizeLimitExceededException e =
            new FileSizeLimitExceededException(
                format("The field %s exceeds its maximum permitted size of %s bytes.",
                    fieldName, Long.valueOf(pSizeMax)),
                pCount, pSizeMax);
        e.setFieldName(fieldName);
        e.setFileName(name);
        throw new FileUploadIOException(e);
      }
    };
  }
  stream = istream;
}

再来看看ItemInputStream,上面的FileItemStreamImpl对象有这个类型参数,主要用来获取请求流的,因为ItemInputStream类是MultipartStream的内部类,能够调用MultipartStream中的input流。

ItemInputStream

public class ItemInputStream extends InputStream implements Closeable {

        // 目前已经读取的字节数
        private long total;

        // 必须保持的字节数可能是分隔符boundary的一部分
        private int pad;

        // 缓冲区的当前偏移
        private int pos;

        // stream流是否关闭
        private boolean closed;

        // 构造方法
        ItemInputStream() {
            findSeparator();
        }

        // 寻找边界分隔符boundary的前一个索引
        private void findSeparator() {
            pos = MultipartStream.this.findSeparator();
            if (pos == -1) {
                if (tail - head > keepRegion) {
                    pad = keepRegion;
                } else {
                    pad = tail - head;
                }
            }
        }

        // 可读取字节数
        @Override
        public int available() throws IOException {
            // 可读=尾-首-边界长度
            if (pos == -1) {
                return tail - head - pad;
            }
            // pos !=-1 说明pos后面是边界了 只能读到这个边界之前的数据
            // 可读 = 边界前的最后一个索引 - 首
            return pos - head;
        }

        private static final int BYTE_POSITIVE_OFFSET = 256;

        // 读取stream流的下一个字符
        @Override
        public int read() throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            if (available() == 0 && makeAvailable() == 0) {
                return -1;
            }
            ++total;
            int b = buffer[head++];
            if (b >= 0) {
                return b;
            }
            // 如果负的 加上256
            return b + BYTE_POSITIVE_OFFSET;
        }

        // 读取字节到给定的缓冲区b中
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            if (len == 0) {
                return 0;
            }
            int res = available();
            if (res == 0) {
                res = makeAvailable();
                if (res == 0) {
                    return -1;
                }
            }
            res = Math.min(res, len);
            System.arraycopy(buffer, head, b, off, res);
            // head加偏移
            head += res;
            total += res;
            return res;
        }

        // 关闭输入流
        @Override
        public void close() throws IOException {
            close(false);
        }

        /**
         * Closes the input stream.
         *
         * @param pCloseUnderlying Whether to close the underlying stream
         *   (hard close)
         * @throws IOException An I/O error occurred.
         */
        public void close(boolean pCloseUnderlying) throws IOException {
            if (closed) {
                return;
            }
            if (pCloseUnderlying) {
                closed = true;
                input.close();
            } else {
                for (;;) {
                    int av = available();
                    if (av == 0) {
                        av = makeAvailable();
                        if (av == 0) {
                            break;
                        }
                    }
                    skip(av);
                }
            }
            closed = true;
        }

        // 跳过缓冲区中给定长度的字节
        @Override
        public long skip(long bytes) throws IOException {
            if (closed) {
                throw new FileItemStream.ItemSkippedException();
            }
            int av = available();
            if (av == 0) {
                av = makeAvailable();
                if (av == 0) {
                    return 0;
                }
            }
            long res = Math.min(av, bytes);
            head += res;
            return res;
        }

        // 试图读取更多的数据,返回可读字节数
        private int makeAvailable() throws IOException {
            if (pos != -1) {
                return 0;
            }

            // 将数据移到缓冲区的开头,舍弃边界
            total += tail - head - pad;
            System.arraycopy(buffer, tail - pad, buffer, 0, pad);

            // Refill buffer with new data.
            head = 0;
            tail = pad;

            for (;;) {
                // 读取tail位置开始读 bufSize-tail长度的字节到buffer缓冲区中
                int bytesRead = input.read(buffer, tail, bufSize - tail);
                if (bytesRead == -1) {
                    // The last pad amount is left in the buffer.
                    // Boundary can't be in there so signal an error
                    // condition.
                    final String msg = "Stream ended unexpectedly";
                    throw new MalformedStreamException(msg);
                }
                if (notifier != null) {
                    notifier.noteBytesRead(bytesRead);
                }
                // tail加偏移
                tail += bytesRead;

                // 再尝试找boundary边界,赋值pos -1
                findSeparator();

                int av = available();

                // 返回可读字节数
                if (av > 0 || pos != -1) {
                    return av;
                }
            }
        }

        // 判断流是否关闭
        public boolean isClosed() {
            return closed;
        }

    }

接受请求

请求解析完成后就可以以文件对象接收了,参数类型为MultipartFile,可强转为CommonsMultipartFile,参数名需要与上传文件的fieldName相对应或者也可以用@RequestParam注解指定参数名

@RequestMapping(value = "file/upload", method = RequestMethod.POST)
@ResponseBody
public Object uploadFile(MultipartFile[] files, HttpServletRequest request, HttpServletResponse response) throws IOException {
  ....... 上传逻辑
  return CommonResult.succ("上传成功");
}

可以用postman测试,content-type的boundary显示是请求发送时计算,不知道怎么算的反正是一串随机数,是用来分隔多个文件内容的,在源码中可以看到不能缺失否则解析过程中会报错

以上就是Spring实现文件上传的配置详解的详细内容,更多关于Spring文件上传的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringMVC实现文件上传下载功能

    目录 导入需要的依赖包 一.单个文件上传 二.多个文件上传 三.上传文件列表显示 四.文件下载 今天遇到文件上传的问题,使用Ajax方式进行提交,服务器一直报错The current request is not a multipart request,看了网上很多方法,最后才找到,我在表单提交的时候使用了序列化$('#postForm').serialize(),但是这种方式,只能传递一般的参数,上传文件的文件流是无法被序列化并传递的.所以一直在报错.后来就直接使用submint(),放弃使用

  • SpringMVC上传文件的两种方法

    在该示例中,阐述了SpringMVC如何上传文件. 1.上传页面upload.jsp <body> <form action="/TestSpringMVC3/data/uploadfile" enctype="multipart/form-data" method="post"> file:<input type="file" name="file"><br>

  • SpringMVC实现文件上传与下载

    本文实例为大家分享了SpringMVC实现文件上传与下载的具体代码,供大家参考,具体内容如下 0.环境准备 1.maven依赖 <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope&

  • Spring框架实现文件上传功能

    在Java中实现文件的上传有多种方式,如smartUpload或是使用Strus2,本文与大家分享使用Spring框架中的MultipartFile类来实例文件的上传. 不啰嗦了,直接上干货.先是编写了一个实现文件上传的类FileUploadingUtil,此类中定义了两个对外公开的方法,upload和getFileMap. 前者需要传入一个Map参数,是用户提交的表单中的文件列表,最终返回值的也是一个Map类型对象,其键名为上传的文件名称,键值为文件在服务器上的存储路径:后者主要是用于测试用途

  • SpringBoot实现单文件上传

    SpringBoot实现单文件上传功能,供大家参考,具体内容如下 架构为springboot+thymeleaf,采用ajax方式提交 1. 页面testFile.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>测试文件上传</title> <script src="../static/jquery/jquery-2.

  • Springmvc实现文件上传

    本文实例为大家分享了Springmvc实现文件上传的具体代码,供大家参考,具体内容如下 1.环境搭建: 在maven的pom.xml文件中导入两个依赖 1).commons-fileupload 2).commons-io 在resources目录下的springmvc.xml文件中配置multipartResolver <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http:/

  • Spring实现文件上传的配置详解

    添加依赖 主要用来解析request请求流,获取文件字段名.上传文件名.content-type.headers等内容组装成FileItem <!--添加fileupload依赖--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</versio

  • SpringBoot整合MinIO实现文件上传的方法详解

    目录 前言 1. MinIO 简介 2. MinIO 安装 3. 整合 Spring Boot 4. 配置nginx 5. 小结 前言 现在 OSS 服务算是一个基础服务了,很多云服务厂商都有提供这样的服务,价格也不贵,松哥自己的网站用的就是类似的服务. 不过对于中小公司来说,除了购买 OSS 服务之外,也可以自己搭建专业的文件服务器,自己搭建专门的文件服务器的话,曾经比较专业的做法是 FastDFS,松哥之前也专门为之录过视频发在 B 站上,感兴趣的小伙伴可以自行查看.不过 FastDFS 搭

  • 最详细的文件上传下载实例详解(推荐)

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般选择采用apache的开源工具common-fileupload这个文件上传组件.这个common-fileupload上传组件的jar包可以去apache官网上面下载,也可以在struts的lib文件夹下面找到,stru

  • Bootstrap Fileinput文件上传组件用法详解

    最近时间空余,总结了一些关于bootstrap fileinput组件的一些常见用法,特此分享到我们平台,供大家参考,同时也方便以后的查找.本文写的不好还请见谅. 一.效果展示 1.原始的input type='file',简直不忍直视. 2.不做任何装饰的bootstrap fileinput:(bootstrap fileinput初级进化) 3.bootstrap fileinput高级进化:中文化.可拖拽上传.文件扩展名校验(如果不是需要的文件,不让上传) 拖拽上传 上传中 4.boot

  • Java 文件上传的实例详解

    Java 文件上传的实例详解 java 文件上传 Java文件上传,介绍几种常用的方法,也是经过本人亲手调试过的 1.jspsmartupload 这个组件用起来是挺方便的,不过就是只适合小文件上传,如果大文件上传的话就不行,查看了一下他的代码,m_totalBytes = m_request.getContentLength(); m_binArray = new byte[m_totalBytes];居然把整个上传文件都读到内存去了,那如果是上传几十M的文件,同时几个用户上传,服务器稳挂,不

  • jQuery File Upload文件上传插件使用详解

    jQuery File Upload 是一个Jquery文件上传组件,支持多文件上传.取消.删除,上传前缩略图预览.列表显示图片大小,支持上传进度条显示:支持各种动态语言开发的服务器端. 官网链接:https://github.com/blueimp/jQuery-File-Upload/wiki 特点:拖放支持:上传进度条:图像预览:可定制和可扩展的:兼容任何服务器端应用平台(PHP, Python, Ruby on Rails, Java, Node.js, Go etc.). 使用方法:

  • Webwork 实现文件上传下载代码详解

    本文主要从三个方面给大家介绍webwork文件上传下载知识,包括以下三个方面: 1. 包装 Request 请求 2. 获取文件上传的解析类 3. 项目实战配置和使用 Web上传和下载应该是很普遍的一个需求,无论是小型网站还是大并发访问的交易网站.WebWork 当然也提供了很友好的拦截器来实现对文件的上传,让我们可以专注与业务逻辑的设计和实现,在实现上传和下载时顺便关注了下框架上传下载的实现. 1. 包装 Request 请求 •每次客户端请求 Action 时,都会调用 WebWork 调度

  • PHP基于session.upload_progress 实现文件上传进度显示功能详解

    本文实例讲述了PHP基于session.upload_progress 实现文件上传进度显示功能.分享给大家供大家参考,具体如下: 介绍 session.upload_progress 是PHP5.4的新特征. 当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度. 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态. 当一个上传在处理中,同时POST

  • NetCore 3.0文件上传和大文件上传的限制详解

    NetCore文件上传两种方式 NetCore官方给出的两种文件上传方式分别为"缓冲"."流式".我简单的说说两种的区别, 1.缓冲:通过模型绑定先把整个文件保存到内存,然后我们通过IFormFile得到stream,优点是效率高,缺点对内存要求大.文件不宜过大. 2.流式处理:直接读取请求体装载后的Section 对应的stream 直接操作strem即可.无需把整个请求体读入内存, 以下为官方微软说法 缓冲 整个文件读入 IFormFile,它是文件的 C# 表

  • ssm框架Springmvc文件上传实现代码详解

    一.上传: 1)编写前台文件上传表单.Method必须为post,enctype为mutipart/form-data <body> <%--文件上传 1)method必须指定为post 2)enctype必须指定为multipart/form-data --%> <h1>头像上传</h1> <form action="${pageContext.request.contextPath}/admin/headpic" method=

随机推荐