当前位置: 首页 > news >正文

如何实现图片上传至服务器

在绝大多数的项目中都会涉及到文件上传等,下面我们来说一下技术派中是如何实现原生图片上传的,这个功能说起来简单,但其实对于技术还是有考验的。图片的上传涉及到IO读写,一个文件上传的功能,就可以把IO流涉及到的知识点全覆盖,比如字节流ByteArrayInputStream、缓存流BufferedOutputStream、文件File的读写权限、文件魔数等等。

如果你想实现一个自己的文件读写Util类,需要考虑的细节还是很多的,比如静态资源的配置、图片大小限制、前端图片上传组件,后端图片接收参数MutiparHttpServletRequest等等。

业务介绍

技术派中关于图片上传的入口有三处:

  •         发表文章时
  •         上传文章封面时
  •         上传用户头像时

看发表文章时,涉及到四种方式:

  •         通过编辑器的菜单添加图片。
  •         直接复制一张图片粘贴到编辑器中
  •         复制外部的图片链接(markdown格式),到编辑器中。
  •         导入MD文件到编辑器中(如果图片有连接时)。

这四种方式都会出发图片上传功能(严格一点,后面两个还涉及到图片转链)后台的接口都是一样的,都调用的是ImageRestController,上传图片调用的是upload方法,请求参数为HttpServletRequest;转存图片链接调用的是save方法,参数为图片的外部链接。响应的结果为ResVo<ImageVo> ,其中包含最关键的信息------图片路径。

代码实现

第一步,在dev/application-image.yml文件中添加图片的配置

image:abs-tmp-path: /tmp/storage/web-img-path: /forum/image/tmp-upload-path: /tmp/forum/cdn-host:oss:type: localprefix: paicoding/endpoint:ak:sk:bucket:host: https://cdn.tobebetterjavaer.comspring:web:resources:# 支持本地图片上传之后的链接,其中 file:///d的用于win系统,后面的file: 适用于mac/linux系统static-locations:- classpath:/static/- file:///d:${image.abs-tmp-path}- file:${image.abs-tmp-path}

来解释一下参数的含义:

  •         abs-tmp-path: 存储的绝对路径
  •         web-image-path: 图片在Web应用中的相对路径
  •         tmp-upload-path: 上传文件的临时存储目录
  •         cdn-host: 图片的CDN访问域名(本地不需要)
  •         oss: 图片上传到阿里云OSS时的配置
  •         spring: web: resources: static-locations 是Spring Boot提供的一种加载静态资源的机制。

静态资源通常包括CSS、JavaScript、图片等文件,通过设置    spring: web: resources: static-locations,我们可以告诉 Spring Boot 在哪些位置查找静态资源。 Spring Boot 的默认静态资源位置包括;

当我们为spring.web. resources. static-locations 提供自定义的值时,Spring Boot会覆盖这些默认值。在技术派的项目结构中,我们将CSS和javaScript,以及一些图片资源放在了,paicoding-ui模块static目录下。

也就意味着,在我们的前端页面中,如果遇到类似这样的<link href="/css/views/home.css" rel="stylesheet" />请求时,Spring Boot将会从 classpath:/static/目录下去找。

注意,我们还指定了另外两个静态资源位置: file:///d:${image.abs-tmp-path} 和 file:${image.abs-tmp-path} ,前者用于Windows系统 ,后者用于macOS和Linux系统。用macOs举例,我们会把图片保存在 /tmp/storage/forum/image目录下。

也就是说,我们可以通过 http://127.0.0.1:8080/forum/image/20230423060009676_69.jpg这种形式访问图片。

file:/是一个URI(统一资源标识符)的方案,表示在本地文件上的系统资源。例如,如果你想要引用本地文件系统上的一个文件,可以使用file:/。以下是一些实例:

  • file:/c:/path/to/your/file.txt : 表示在Windows系统上的  c:/path/to/your/file.txt文件。
  • file:/Users/username/path/to/your/file.txt : 表示在macOS或Linux系统上的/Users/username/path/to/your/file.txt文件。

第二步,新建ImageProperties.java类

使用 @ConfigurationProperties 注解使其和配置文件中的图片配置关联起来。

@Setter
@Getter
@Component
@ConfigurationProperties(prefix = "image")
public class ImageProperties {/*** 存储绝对路径*/private String absTmpPath;/*** 存储相对路径*/private String webImgPath;/*** 上传文件的临时存储目录*/private String tmpUploadPath;/*** 访问图片的host*/private String cdnHost;private OssProperties oss;public String buildImgUrl(String url) {if (!url.startsWith(cdnHost)) {return cdnHost + url;}return url;}
}
  • @Setter 和 @Getter 是lombok提供的注解,这样我们就不用写冗长的getter 和setter。
  • @Component 表示这个类是一个 Spring管理的Bean。
  • @ConfigurationProperties是Spring Boot中用于将外部配置文件(如application.properties
  • 或者application.yml)中的属性绑定到Java类的一个注解。通过使用这个注解,我们可以将配置文件中的值自动映射到有相应字段的Java类中。参数prefix = "image" 表示将配置文件image作为前缀的属性绑定到该类中。

第三步,构建前端上传组件和发起上传请求。

我们先来看比较简单的一种,上传文章封面,在发表文章的页面,点击保存按钮,会弹出文章封面的上传模态框。

代码非常简单,用了一个input组件,type为file,接受的文件类型为image。

<input type = "file"accept = "image/*"id = "upload"class = "click-input"
/>

当选择图片后,会触发change事件。

      upload.on("change", function (e) {let objUrl = getObjectURL(this.files[0]) //获取图片的路径,该路径不是图片在本地的路径if (objUrl) {console.log("uploadImg", this.value)uploadImg(() => (this.value = null), objUrl)}})

在事件回调函数中,代码会执行以下操作。

  •     使用 getObjectURL函数获取选中文件(this.files[0])的临时URL。
  •         注意,这里的this指向触发事件的文件输入元素。
  • 检查objUrl是否存在。如果存在,继续。
  • 输出this.value到控制台,这里的this.value是选中文件的本地路径(例如:Windows下是: C:\fakepath\file.jpg)
  • 调用uploadImg函数,并将一个回调函数和objUrl作为参数传递。这个回调函数将在uploadImage函数内部执行(图片上传完成后)。回调函数中,将文件输入元素的value属性设置为null,以清除选中文件。

来看一下getObjectURL函数(创建一个临时URL,用于访问本地文件):

//建立一?可存取到?file的url
const getObjectURL = function (file) {let url = nullif (window.createObjectURL != undefined) {// basicurl = window.createObjectURL(file)} else if (window.URL != undefined) {// mozilla(firefox)url = window.URL.createObjectURL(file)} else if (window.webkitURL != undefined) {// webkit or chromeurl = window.webkitURL.createObjectURL(file)}return url
}

  这段代码使用三种不同的方式来创建临时URL,以确保兼容性:

 window.createObjectURL(file): 这是一个比较旧的方法,用于创建临时URL。在现代的浏览器中,这个方法可能被废弃。
window.URL.createObjectURL(file): 这是一个比较新的方法,用于创建按临时URL。在许多现代浏览器中(如FIrefox、Chrome、Edge等),这个方法已经取代了window.createObjectURL。
 window.webkitURL.createObjectURL(file): 这是一个WenKit特定的方法,用于创建临时的URL。在基于WebKit的浏览器中(如旧版本的Chrome和Safari)这个方法可能是唯一可用的方法。

再来看uploadImge方法(实用jQuery的Ajax实现图片上传):

 // 上传头图到服务器function uploadImg(callback, objUrl) {let uploadPic = upload[0].files[0]console.log("准备上传", uploadPic)if (!checkFileSize(uploadPic)) {return;}let file = new FormData()file.append("image", uploadPic)$.ajax({url: "/image/upload",type: "post",data: file,cache: false,contentType: false,processData: false,success: function (data) {console.log("response data", data);if (data.status.code > 0) {// 图片上传失败toastr.error(data.status.msg, "图片上传失败!");return;}const {result: { imagePath },} = data || {}defaults['cover'] = imagePath;//将图片路径存入src中,显示出图片pic.attr("src", objUrl).css('visibility', 'visible') // 展示图片$('.upload-icon-up').css('visibility', 'hidden') // 隐藏上传callback();toastr.info("图片上传成功!");},error : function(jqXHR, textStatus, errorThrown) {toastr.error(jqXHR.responseText, "图片上传失败!");},})}

解释一下代码:

这段代码是一个用于上传图片到服务器的函数。下面是对代码的解释:

```javascript
// 上传头图到服务器
function uploadImg(callback, objUrl) {
  let uploadPic = upload[0].files[0] // 获取上传的图片文件
  console.log("准备上传", uploadPic) // 打印准备上传的图片信息

  if (!checkFileSize(uploadPic)) { // 检查文件大小是否符合要求
    return; // 如果不符合要求,直接返回
  }

  let file = new FormData() // 创建一个新的FormData对象
  file.append("image", uploadPic) // 将上传的图片文件添加到FormData对象中
  $.ajax({ // 使用jQuery的ajax方法发送POST请求
    url: "/image/upload", // 请求的URL地址
    type: "post", // 请求类型为POST
    data: file, // 请求的数据为FormData对象
    cache: false, // 禁用缓存
    contentType: false, // 不设置Content-Type请求头
    processData: false, // 不处理数据
    success: function (data) { // 请求成功时的回调函数
      console.log("response data", data); // 打印响应数据
      if (data.status.code > 0) { // 判断图片上传是否失败
        // 图片上传失败
        toastr.error(data.status.msg, "图片上传失败!"); // 显示错误提示信息
        return; // 结束函数执行
      }

      const {result: { imagePath },} = data || {} // 从响应数据中提取图片路径
      defaults['cover'] = imagePath; // 将图片路径存入defaults对象中的cover属性

      // 将图片路径存入src中,显示出图片
      pic.attr("src", objUrl).css('visibility', 'visible') // 展示图片
      $('.upload-icon-up').css('visibility', 'hidden') // 隐藏上传按钮

      callback(); // 调用回调函数
      toastr.info("图片上传成功!"); // 显示成功提示信息
    },
    error : function(jqXHR, textStatus, errorThrown) { // 请求失败时的回调函数
      toastr.error(jqXHR.responseText, "图片上传失败!"); // 显示错误提示信息
    },
  })
}
```

这段代码定义了一个名为`uploadImg`的函数,该函数接受两个参数:`callback`和`objUrl`。`callback`是一个回调函数,在图片上传成功后会被调用;`objUrl`是图片的URL地址。

函数内部首先获取上传的图片文件,并打印出准备上传的图片信息。然后通过调用`checkFileSize`函数来检查文件大小是否符合要求,如果不符合要求则直接返回。

接下来,创建一个新的`FormData`对象,并将上传的图片文件添加到其中。然后使用jQuery的`ajax`方法发送POST请求,将`FormData`对象作为请求的数据发送给服务器的`/image/upload`接口。

在请求成功时,会打印响应数据,并根据响应结果判断图片上传是否失败。如果上传失败,会显示错误提示信息并结束函数执行。如果上传成功,会从响应数据中提取图片路径,并将其存入`defaults`对象的`cover`属性中。然后通过修改DOM元素的样式,将图片路径存入`src`属性中,并显示图片。同时,隐藏上传按钮,并调用传入的回调函数。最后,显示成功提示信息。

在请求失败时,会显示错误提示信息。

解释:

  • 函数 uploadlmg 接受两个参数:-个回调函数callback和一个对象URL  objUrl。
  • 从文件输入框 upload 中获取要上传的图片文件(uploadPic)
  • 使用checkFilesize函数检査文件大小,如果文件大小不符合要求,则终止执行。
  • 创建一个新的FormData对象,并将图片添加到其中。FormData是一个Web API,它提供了一种在浏览器中方便地构造、发送表单数据的方法。它主要用于发送包含二进制文件和键值对的数据,如图片、视频、文档等。FormData对象可以与Aiax一起使用,以便在不刷新页面的情况下将表单数据发送到服务器。当使用FormData时,浏览器会自动将数据编码为 multipart/form-data 格式,这是一种特殊的格式,允许在表单中包含二进制文件数据,如图片或视频。
  • 使用jQuery的 $.ajax 方法发起一个异步的POST请求,将图片文件发送到服务器的 /image/upload 接口。
  • 在ajax方法中,设置一些关键的参数,如:cache:false表示禁用浏览器缓存;contentType: false表示不设置内容类型(让浏览器自动设置);processData:false表示不对数据进行预处理。
  • 定义一个success回调函数,当服务器成功响应时触发。检査响应数据中的状态(data.status.code)。如果大于0,表示图片上传失败,显示错误消息。否则,从响应数据中获取图片路径(imagePath),将图片路径设置为pic元素的src属性,使图片可见。调用回调函数callback。显示图片上传成功的提示信息
  • 定义一个error回调函数,当请求发生错误时触发。在这个回调函数中,显示错误消息。

第四步,在ImageRestController接受图片并处理

@Permission(role = UserRole.LOGIN)
@RequestMapping(path = {"image/", "admin/image/", "api/admin/image/",})
@RestController
@Slf4j
public class ImageRestController {@Autowiredprivate ImageService imageService;/*** 图片上传** @return*/@RequestMapping(path = "upload")public ResVo<ImageVo> upload(HttpServletRequest request) {ImageVo imageVo = new ImageVo();try {String imagePath = imageService.saveImg(request);imageVo.setImagePath(imagePath);} catch (Exception e) {log.error("save upload file error!", e);return ResVo.fail(StatusEnum.UPLOAD_PIC_FAILED);}return ResVo.ok(imageVo);}

来详细解释一下。

  • @RequestMapping(path = "image/")注解用于指定控制器处理的请求路径为"image"。
    
  • @RestController 注解表示这是一个用于处理RESTful风格请求的控制器。
  • @Slf4j 注解用于自动注入一个SLF4J日志对象
    
  • 使用 @Autowired 注解将一个 ImageService(用于处理图片保存和转链的关键类)自动注入到控制器中。
  • 接下来,让我们看看这个控制器中的upload方法,这个方法用于处理图片上传请求。
  • 使用 @RequestMapping(path = "upload") 注解将此方法映射到”image/upload“路径。
  • 该方法接受一个HttpServletRequest参数,该参数代表客户端发送的Http请求。
  • 在该方法的内部,调用imageService.saveImg(request)方法将图片保存到服务器,并获取图片路径。
  • 将图片路径设置到ImageVo对象中,然后将ImageVo对象作为数据返回给客户端。

第五步·定义ImageService接口

很简单,不在解释。

    /*** 保存图片** @param request* @return*/String saveImg(HttpServletRequest request);
}

第六步,实现ImageService接口。

 @Overridepublic String saveImg(HttpServletRequest request) {MultipartFile file = null;if (request instanceof MultipartHttpServletRequest) {file = ((MultipartHttpServletRequest) request).getFile("image");}if (file == null) {throw ExceptionUtil.of(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "缺少需要上传的图片");}// 目前只支持 jpg, png, webp 等静态图片格式String fileType = validateStaticImg(file.getContentType());if (fileType == null) {throw ExceptionUtil.of(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "图片只支持png,jpg,gif");}try {return imageUploader.upload(file.getInputStream(), fileType);} catch (IOException e) {log.error("Parse img from httpRequest to BufferedImage error! e:", e);throw ExceptionUtil.of(StatusEnum.UPLOAD_PIC_FAILED);}}

这个方法的主要功能是从HTTP请求中提取图片并保存,描述一下该方法的逻辑:

  •         首先,检查HttpServletRequest 是否是 MultipartHttpServletRequest ,如果是,则从请求中获取名为 “image” 的文件并将其保存到MultipartFile对象中。
  • MultipartHttpServletRequest是一个Java接口,他继承自HttpServletRequest 接口。在Spring框架中,这个接口用于处理包含文件上传的HTTP请求,即请求内容类型为 multipart/form-data。 在这种请求类型中,表单数据可以包含文本字段和二进制文件,如图片、视频、或者文档。
  • 如果file为空(即未上传任何文件),则抛出一个异常,表示请求中缺少需要上传的图片。
  • 接下来,验证图片文件类型。当前方法只支持jpg、png和webp等静态图片格式。
  • validateStaticImg方法检查传入的内容类型是否属于这些支持的类型,并返回相应的文件类型,如果文件不支持,则抛出一个异常。
    
  • 从MultipartFile对象中获取输入流,然后传递给upload方法,如果在上传的过程中发生任何错误,例如将图片转换为BufferedImage时出现问题,将抛出一个异常。

第七步,定义ImageUploader接口。

public interface ImageUploader {String DEFAULT_FILE_TYPE = "txt";Set<MediaType> STATIC_IMG_TYPE = new HashSet<>(Arrays.asList(MediaType.ImagePng, MediaType.ImageJpg, MediaType.ImageWebp, MediaType.ImageGif));/*** 文件上传** @param input* @param fileType* @return*/String upload(InputStream input, String fileType);/*** 获取文件类型** @param input* @param fileType* @return*/default String getFileType(ByteArrayInputStream input, String fileType) {if (StringUtils.isNotBlank(fileType)) {return fileType;}MediaType type = MediaType.typeOfMagicNum(FileReadUtil.getMagicNum(input));if (STATIC_IMG_TYPE.contains(type)) {return type.getExt();}return DEFAULT_FILE_TYPE;}
}

来解释一下这段代码。

  •         DEFAULT_FILE_TYPE: 一个默认的文件类型,用于表示当前文件类型无法识别时的默认值。在这个接口中,他被设置为“txt”。
  •         STATIC_IMG_TYPE: 一个包含支持的静态图片类型的集合。包括PNG、JPG、WebP和GIF等类型。
  •         String upload(InputStream input, String fileType);这是一个需要实现的抽象方法,用于将输入流中的文件保存。
  •         default String getFileType(ByteArrayInputStream input, String fileType) {: 这是一个默认实现的方法,用于根据输入流中的文件内容和给定的文件类型获取最终的文件类型。首先检查fileType是否为空,如果为空直接返回,然后使用MediaType.typeOfMagicNum()方法通过文件的魔数来判断文件类型。如果文件类型属于支持的静态图片类型集合,则返回对应的扩展名。否则,返回默认的文件类型。

来看一下获取文件魔数的静态方法 getMagicNum:

    public static String getMagicNum(ByteArrayInputStream inputStream) {byte[] bytes = new byte[28];inputStream.read(bytes, 0, 28);inputStream.reset();return bytesToHex(bytes);}

假如是一张jpg的文件,我们来看一下魔术是多少?

    @Testpublic  void testMagic() throws FileNotFoundException {FileInputStream fileInputStream = new FileInputStream("docs/imgs/init_00.jpg");ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[4039];int bytesRead;try {while ((bytesRead = fileInputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, bytesRead);}} catch (IOException e) {e.printStackTrace();}ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());//算魔数String magicNum = FileReadUtil.getMagicNum(byteArrayInputStream);System.out.println(magicNum);//根据魔数判断文件类型MediaType mediaType = MediaType.typeOfMagicNum(magicNum);System.out.println("文件类型" + mediaType);}

解释:

这段代码是一个Java方法,名为`testMagic`,它没有返回值(void)并且声明了可能抛出`FileNotFoundException`异常。下面是对代码的详细解释:

1. 首先,创建一个`FileInputStream`对象`fileInputStream`,用于读取指定路径下的文件"docs/imgs/init_00.jpg"。
2. 创建一个`ByteArrayOutputStream`对象`byteArrayOutputStream`,用于存储从文件中读取的数据。
3. 定义一个长度为4039的字节数组`buffer`,用于临时存储每次从文件中读取的数据。
4. 使用循环结构,通过调用`fileInputStream.read(buffer)`方法从文件中读取数据,并将读取到的字节数赋值给变量`bytesRead`。
5. 如果`bytesRead`不等于-1,表示还有数据可以读取,将`buffer`中的数据写入`byteArrayOutputStream`中,从索引0开始,写入`bytesRead`个字节。
6. 如果在读取文件过程中发生`IOException`异常,捕获该异常并打印堆栈跟踪信息。
7. 创建一个`ByteArrayInputStream`对象`byteArrayInputStream`,使用`byteArrayOutputStream.toByteArray()`方法将`byteArrayOutputStream`中的数据转换为字节数组作为参数传入。
8. 调用`FileReadUtil.getMagicNum(byteArrayInputStream)`方法获取魔数(magic number),并将结果赋值给字符串变量`magicNum`。
9. 输出魔数`magicNum`。
10. 调用`MediaType.typeOfMagicNum(magicNum)`方法根据魔数判断文件类型,并将结果赋值给`MediaType`类型的变量`mediaType`。
11. 输出文件类型`mediaType`。

总结:这段代码主要用于读取指定路径下的文件,并将其内容转换为字节数组,然后根据魔数判断文件类型,并输出魔数和文件类型。

来看输出结果:

Java字节码文件(.class)的魔数是一个4字节的十六进制: 0xCAFEBABE。魔数是文件格式的标识符,用于表示文件类型。

第八步,新建LocalStorageWrapper类实现ImageUploader接口。

@Slf4j
@ConditionalOnExpression(value = "#{'local'.equals(environment.getProperty('image.oss.type'))}")
@Component
public class LocalStorageWrapper implements ImageUploader {@Autowiredprivate ImageProperties imageProperties;private Random random;public LocalStorageWrapper() {random = new Random();}@Overridepublic String upload(InputStream input, String fileType) {// 记录耗时分布StopWatchUtil stopWatchUtil = StopWatchUtil.init("图片上传");try {if (fileType == null) {// 根据魔数判断文件类型InputStream finalInput = input;byte[] bytes = stopWatchUtil.record("流转字节", () -> StreamUtils.copyToByteArray(finalInput));input = new ByteArrayInputStream(bytes);fileType = getFileType((ByteArrayInputStream) input, fileType);}String path = imageProperties.getAbsTmpPath() + imageProperties.getWebImgPath();String fileName = genTmpFileName();InputStream finalInput = input;String finalFileType = fileType;FileWriteUtil.FileInfo file = stopWatchUtil.record("存储", () -> FileWriteUtil.saveFileByStream(finalInput, path, fileName, finalFileType));return imageProperties.buildImgUrl(imageProperties.getWebImgPath() + file.getFilename() + "." + file.getFileType());} catch (Exception e) {log.error("Parse img from httpRequest to BufferedImage error! e:", e);throw ExceptionUtil.of(StatusEnum.UPLOAD_PIC_FAILED);} finally {log.info("图片上传耗时: {}", stopWatchUtil.prettyPrint());}}/*** 获取文件临时名称** @return*/private String genTmpFileName() {return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddhhmmssSSS")) + "_" + random.nextInt(100);}
  • 该类使用了@ConditionalOnExpression 注解,表示只有在配置文件中的 image.oss.type属性为“local”时才会实例化该类。
  • imageProperties: 一个ImageProperties 类型的对象,用于获取图片相关的配置信息。
  • random: 一个 Random类型的对象,用于生成随机数。
  • upload(InputStream input, String fileType): 实现 ImageUploader接口中的upload方法,用于将给定的输入流保存到本地的文件系统。首先检查fileType是否为null,如果是,则根据输入流中的字节数据的魔术确定文件的类型,然后,根据配置文件中的路径设置和文件类型,将文件保存到本地文件,并返回文件的URL。
  •  genTmpFileName(): 一个辅助方法,用于生成临时文件名,他使用当前日期和一个随机数生成文件名。
  • 其中调用了 FileWriteUtil.saveFileByStream(finalInput, path, fileName, finalFileType) 方法来对图片进行保存。
    public static FileInfo saveFileByStream(InputStream stream, FileInfo fileInfo) throws FileNotFoundException {if (!StringUtils.isBlank(fileInfo.getPath())) {mkDir(new File(fileInfo.getPath()));}String tempAbsFile = fileInfo.getPath() + "/" + fileInfo.getFilename() + "." + fileInfo.getFileType();BufferedOutputStream outputStream = null;InputStream inputStream = null;FileInfo var6;try {inputStream = new BufferedInputStream(stream);outputStream = new BufferedOutputStream(new FileOutputStream(tempAbsFile));int len = inputStream.available();//判断长度是否大于4Kif (len <= 4096) {byte[] bytes = new byte[len];inputStream.read(bytes);outputStream.write(bytes);} else {int byteCount = false;byte[] bytes = new byte[4096];//1M逐个读取int byteCount;while((byteCount = inputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, byteCount);}}var6 = fileInfo;return var6;} catch (Exception var16) {log.error("save stream into file error! filename: {} e: {}", tempAbsFile, var16);var6 = null;} finally {try {if (outputStream != null) {outputStream.flush();outputStream.close();}if (inputStream != null) {inputStream.close();}} catch (IOException var15) {log.error("close stream error!", var15);}}return var6;}

该方法的参数包括输入流和一个FileInfo对象,其中FileInfo对象包含了文件路径、文件名和文件类型。

代码实现了一下功能:

  • 检查文件路径是否存在,如果不存在则创建文件夹。
  • 根据文件路径、文件名和文件类型创建一个临时的绝对路径。
  • 使用BufferedInputStream和BufferedOUtputStream对输出流和输入流进行缓冲处理,以提高文件的读写性能。
  • 判断输入流的长度是否大于4KB。如果小于等于4KB,就一次性读取所有字节并写入输出流,否则,以4KB的块逐步读取输入流并写入输出流,直到内容都被处理。
  • 在操作完成后,返回FileInfo对象。

小结:

简单总结一下,本地图片上传和保存的逻辑可以分为前端和后端两个部分:

前端(使用Ajax上传):

        a.用户选择一张土图片并上传。

        b.使用FormData对象封装图片数据。FormData对象能够让你通过XMLHttpRequest发送表单数据。

        c. 利用jQuery的$.ajax方法发送一个POST请求,将FormData 对象传递给后端的服务器。

后端(Java代码处理上传和保存):

  •              从HttpServletRequest对象中提取MultipartFile对象,该对象包含了上传的图片数据。
  •               验证图片类型,确保上传的文件是支持的图片格式。
  •                 将MultipartFile对象转换为InputStream,以便后续处理。
  •                 调用一个专门负责处理图片上传的方法(如:upload(),该方法可能需要处理文件类型和文件名等逻辑。)
  •         保存图片到本地的文件系统。这里可以使用一个方法(如: savaFileByStream()),将InputStream保存为文件。保存过程中,可以使用BufferedInputStream和BufferedOutputStream来提高文件读写性能。
  •         将图片的存储路径返回给前端,前端可以使用这个路径来显示上传成功的图片。
  • 这样一来,前端通过Ajax发送的图片数据到后端,后端处理上传请求并将图片保存到本地文件系统,最后将图片路径返回给前端进行展示。  

相关文章:

如何实现图片上传至服务器

在绝大多数的项目中都会涉及到文件上传等&#xff0c;下面我们来说一下技术派中是如何实现原生图片上传的&#xff0c;这个功能说起来简单&#xff0c;但其实对于技术还是有考验的。图片的上传涉及到IO读写&#xff0c;一个文件上传的功能&#xff0c;就可以把IO流涉及到的知识…...

OSPF协议全面学习笔记

作者&#xff1a;BSXY_19计科_陈永跃 BSXY_信息学院 注&#xff1a;未经允许禁止转发任何内容 OSPF协议全面学习笔记 1、OSPF基础2、DR与BDR3、OSPF多区域4、虚链路Vlink5、OSPF报文6、LSA结构1、一类/二类LSA&#xff08;Router-LSA/Network-LSA&#xff09; 更新完善中... 1、…...

acwing算法提高之搜索--剪枝

目录 1 介绍2 训练 1 介绍 本专题用来记录使用dfs剪枝技巧求解的题目。 剪枝有以下思路&#xff1a; 优化搜索顺序。可行性剪枝。最优性剪枝。唯一性剪枝&#xff0c;也叫去除冗余。记忆化搜索&#xff0c;也叫dp。 2 训练 题目1&#xff1a;165小猫爬山 C代码如下&#…...

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Web)上篇

提供具有网页显示能力的Web组件&#xff0c;ohos.web.webview提供web控制能力。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。示例效果请以真机运行为准&#xff0c;当前IDE预览器不支持。 需要权…...

TPU浅谈

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;上篇文章讲了FPGA和ASIC&#xff0c;讲解了 FPGA 如何实现通过“软件”来控制“硬件”&#xff0c;以及我们可以进一步把 FPGA 设计出来的电路变成一块 ASIC 芯片。今天我们来看看TPU。大家可以点击这篇文章TPU深入了解TPU。…...

华为OD机试 - 求字符串中所有整数的最小和(Java JS Python C C++)

题目描述 输入字符串s,输出s中包含所有整数的最小和。 说明: 字符串s,只包含 a-z A-Z 合法的整数包括 1)正整数:一个或者多个0-9组成,如 0 2 3 002 102 2)负整数:负号 – 开头,数字部分由一个或者多个0-9组成,如 -0 -012 -23 -00023 输入描述 包含数字的字符…...

goland设置保存文件时不将4个空格转为TAB

goland设置保存文件时不将4个空格转为TAB 版本&#xff1a;GoLand 2022.3 设置路径&#xff1a; Settings -> Editor -> Code Style -> Go -> Run gofmt图示&#xff1a;...

基于Linux内核的socket编程(TCP)的C语言示例

原文地址&#xff1a;https://www.geeksforgeeks.org/socket-programming-cc/ 服务端&#xff1a; #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h>#…...

【WEEK3】 【DAY4】JSON交互处理第三部分【中文版】

2024.3.14 Thursday 接上文【WEEK3】 【DAY3】JSON交互处理第二部分【中文版】 目录 6.7.编写抽象类6.7.1.原因6.7.2.新建JsonUtils.java6.7.3.在UserController添加一个方法json6验证抽象类可调用6.7.4.在UserController添加一个方法json7验证该抽象类可复用6.7.5.运行 6.8.…...

下载chromedrive,使用自动化

1、先看一下自己浏览器的版本 2、访问 https://googlechromelabs.github.io/chrome-for-testing/...

D-Star 寻路算法

D-Star 寻路算法 下面简写 D-Star 为 D* D算法&#xff1a;D 算法”的名称源自 Dynamic A Star,最初由Anthony Stentz于“Optimal and Efficient Path Planning for Partially-Known Environments”中介绍。它是一种启发式的路径搜索算法&#xff0c; 适合面对周围环境未知或者…...

mysql5.7编译安装

MySQL 5.7在不同操作系统上的编译安装过程略有不同,以下是在Linux系统上编译安装MySQL 5.7的一般步骤: 1. 安装编译所需的依赖包 sudo yum install gcc-c cmake ncurses-devel bison openssl-devel 2. 下载MySQL源码包和Boost库并解压 wget https://dev.mysql.com/get/Dow…...

Java项目实战记录:雷达数据渲染

目录 Java项目实战记录&#xff1a;雷达数据渲染业务背景代码逻辑数据结构颜色渲染MapContent加载数据并输出截图 完整代码GenerateMapImage地图渲染工具测试代码 渲染效果 Java项目实战记录&#xff1a;雷达数据渲染 业务背景 我之前已经成功使用Java语言解析了C处理的雷达数…...

进程的概念 | PCB | Linux下的task_struct | 父子进程和子进程

在讲进程之前首先就是需要去回顾一下我们之前学的操作系统是干嘛的&#xff0c;首先操作系统是一个软件&#xff0c;它是对上提供一个良好高效&#xff0c;稳定的环境的&#xff0c;这是相对于用户来说的&#xff0c;对下是为了进行更好的软硬件管理的&#xff0c;所以操作系统…...

【GPT-SOVITS-03】SOVITS 模块-生成模型解析

说明&#xff1a;该系列文章从本人知乎账号迁入&#xff0c;主要原因是知乎图片附件过于模糊。 知乎专栏地址&#xff1a; 语音生成专栏 系列文章地址&#xff1a; 【GPT-SOVITS-01】源码梳理 【GPT-SOVITS-02】GPT模块解析 【GPT-SOVITS-03】SOVITS 模块-生成模型解析 【G…...

2024HVV行动-进军蓝中研判(log4j2、fastjson、Struts2、Shiro)

1、log4j2 特征&#xff1a; 恶意请求中包含 JNDI 协议地址&#xff0c;如"ldap://"、"rmi://"等&#xff0c;被 log4j2 解析为 JNDI 查找。 原理&#xff1a; 在日志输出中&#xff0c;未对字符进行严格的过滤&#xff0c;执行了 JNDI 协议加载的远程恶…...

亮点抢先看!4月16-17日,百度Create大会开设“AI公开课”,大咖带你打造赚钱工具

3月16日&#xff0c;2024百度Create AI开发者大会正式开放售票&#xff0c;嘉宾套票定价399元。据悉&#xff0c;本次大会以“创造未来&#xff08;Create the Future&#xff09;”为主题&#xff0c;设有20深度论坛、超30节AI公开课、3000平AI互动体验区和AI音乐节等精彩环节…...

【笔记本清灰/实用经验】荣耀Magicbook14-2020款-R5-4500U-清灰实战

清灰有风险&#xff0c;动手需谨慎&#xff0c;本文只分享本人的清灰过程&#xff0c;对使用它所产生的任何后果不任何负责任 文章目录 背景信息准备阶段工具准备信息收集 正式清灰初始化清灰流程放掉身体的静电&#xff08;重要&#xff09;拆笔记本后盖断开电源&#xff08;重…...

如何写好Stable Diffusion的prompt

Stable Diffusion是一种强大的文本到图像生成模型&#xff0c;其效果在很大程度上取决于输入的提示词&#xff08;Prompt&#xff09;。以下是一些关于如何编写有效的Stable Diffusion Prompt的秘诀&#xff1a; 明确描述&#xff1a;尽量清晰地描述你想要的图像内容。使用具体…...

计算机毕业设计 | SpringBoot+vue 移动端社区物业管理系统(附源码+论文)

1&#xff0c; 概述 课题背景 近几年来&#xff0c;随着物业相关的各种信息越来越多&#xff0c;比如报修维修、缴费、车位、访客等信息&#xff0c;对物业管理方面的需求越来越高&#xff0c;我们在工作中越来越多方面需要利用网页端管理系统来进行管理&#xff0c;我们所需…...

玩转C语言——数组初探

一、前言 通过前面的学习&#xff0c;我们已了解C语言的结构变量、分支结构和循环结构。今天&#xff0c;我们一起来认识C语言的另一知识点——数组。先赞后看&#xff0c;养成习惯。 二、数组概念 学习数组&#xff0c;我们要明白数组是什么。在我看来&#xff1a;数组是⼀组…...

Nginx指令配置大全

基本命令 nginx -t 检查配置文件是否有语法错误 nginx -s reload 热加载&#xff0c;重新加载配置文件 nginx -s stop 快速关闭 nginx -s quit 等待工作进程处理完成后关闭配置块介绍 全局块 全局块是默认配置文件从开始到events块之间的…...

富格林:安全出金关注可信操作

富格林悉知&#xff0c;现货黄金投资凭借着诸多优势&#xff0c;成为了热门的投资产品之一&#xff0c;也获得了投资者的追捧。在投资中想要安全盈利出金&#xff0c;投资者一定要沉下心来学习专业知识和技术&#xff0c;这样才能在以后的投资操作中避免亏损&#xff0c;顺畅盈…...

DELETE、TRUNCATE 和 DROP 在MySQL中的区别及使用示例

在MySQL数据库中&#xff0c;DELETE、TRUNCATE TABLE 和 DROP 这三个命令分别适用于不同的数据删除需求&#xff0c;它们在工作原理、应用场景以及特性上有所区别。接下来&#xff0c;我们通过实例演示来明确这三者的不同之处。 DELETE 命令 功能与示例&#xff1a;DELETE 语…...

程序员应该如何选择职业赛道?

程序员选择职业赛道是一个涉及个人兴趣、技能匹配、市场需求和长远发展规划的综合决策过程。以下是一些关键步骤和考虑因素&#xff1a; 自我评估&#xff1a; 技能与专长&#xff1a;分析自己在编程语言、算法、数据结构等方面的现有技能&#xff0c;并思考这些技能更适合前端…...

深入浅出Hive性能优化策略

我们将从基础的HiveQL优化讲起&#xff0c;涵盖数据存储格式选择、数据模型设计、查询执行计划优化等多个方面。会的直接滑到最后看代码和语法。 目录 引言 Hive架构概览 示例1&#xff1a;创建表并加载数据 示例2&#xff1a;优化查询 Hive查询优化 1. 选择适当的文件格…...

利用卷积神经网络进行人脸识别

利用卷积神经网络&#xff08;Convolutional Neural Networks, CNNs&#xff09;进行人脸识别是计算机视觉领域的一个热门话题。下面是一个简化的指南&#xff0c;涵盖了从理论基础到实际应用的各个方面&#xff0c;可以作为你博文的基础内容。 理论基础 卷积神经网络简介&am…...

固态硬盘有坏道怎么恢复数据 固态硬盘坏道怎么修复

固态硬盘是一种高速、低噪音、低功耗的存储设备,但是它也有一个致命的问题——坏道。坏道是指存储芯片中的某些存储单元出现了故障,导致数据无法正常读取或写入。如果你的固态硬盘出现了坏道,那么你的数据就有可能会丢失,带来了很大的困扰。那么,固态硬盘有坏道怎么恢复数…...

adobe animate 时间轴找不到编辑多个帧按钮

如题&#xff0c;找了半天&#xff0c;在时间轴上找不到编辑多个帧按钮,导致无法批量处理帧 然后搜索发现原来是有些版本被隐藏了&#xff0c;需要再设置一下 勾选上就好了...

5 亿欧元巨额奖励!法国国防部启动量子初创公司项目

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨王珩 编译/排版丨沛贤 深度好文&#xff1a;800字丨6分钟阅读 据C4ISNET报道&#xff0c;法国国防部采购机构宣布向五家法国量子计算研究初创公司授予合同&#xff0c;用于开发量子计算技…...

湛江新闻头条最新消息/班级优化大师app

本章介绍函数如何定义和声明&#xff0c;包括如何传入函数及函数如何返回结果&#xff0c;以及一些包括函数指针的知识。 6.1 函数基础 一个典型的function 定义包括以下四个部分&#xff1a; - 返回类型 - 函数名字 - 形参&#xff08;parameter&#xff09;&#xff1b;…...

如何用asp.net做网站/市场宣传推广方案

this简介 在Java中this可以完成三件事情&#xff1a;表示本类属性、表示本类方法、当前对象&#xff08;只是先介绍概念&#xff09; 调用本类属性 在一个类中定义的方法中可以直接访问类中的属性&#xff0c;但是很多时候有可能会出现方法参数名称与属性名称重复的情况&#x…...

上海 微信网站 建站/专业黑帽seo推广

链接&#xff1a;https://vjudge.net/problem/POJ-2481 题意&#xff1a; 有n头牛&#xff0c;每头牛有一个范围&#xff0c;s-e&#xff0c;当一头牛的s大于等于另一头牛和e小于等于另一头牛同时e-s也小于另一头牛 说明另一头牛比这头牛强壮。 求这n头牛每头牛有几个比他强壮。…...

武汉建站之星信息科技有限公司/seo工程师是做什么的

前言那天我和同事一起吃完晚饭回公司加班&#xff0c;然后就群里就有人我说xxx商户说收不到推送&#xff0c;一开始觉得没啥。我第一反应是不是极光没注册上&#xff0c;就让客服通知商户&#xff0c;重新登录下试试。这边打开极光推送的后台进行检查。后面反应收不到推送的越来…...

广西住房与城乡建设厅网站/淄博seo网络公司

查看原文&#xff1a;http://www.sijitao.net/1943.html 先简单说下我碰到的情况。我一个域名的http页面被墙&#xff0c;国内用户可以通过https方式打开。为了seo&#xff0c;也遵循尽量不换网站域名的前提下&#xff0c;现在我想实现的目标是对国外蜘蛛&#xff08;google...…...

做网站怎么给图片加连接/百度搜索推广的定义

.NetCore框架Surging系列&#xff08;一&#xff09;介绍 .NetCore框架Surging系列&#xff08;二&#xff09;HTTP .NetCore框架Surging系列&#xff08;三&#xff09;HTTP本地路由发现过程 .NetCore框架Surging系列&#xff08;四&#xff09;RPC客户端过程 .NetCore框架Sur…...