怎样实现图片上传至服务器

打印 上一主题 下一主题

主题 1020|帖子 1020|积分 3060

在绝大多数的项目中都会涉及到文件上传等,下面我们来说一下技能派中是怎样实现原生图片上传的,这个功能提及来简单,但其实对于技能照旧有磨练的。图片的上传涉及到IO读写,一个文件上传的功能,就可以把IO流涉及到的知识点全覆盖,好比字节流ByteArrayInputStream、缓存流BufferedOutputStream、文件File的读写权限、文件魔数等等。
如果你想实现一个自己的文件读写Util类,必要考虑的细节照旧很多的,好比静态资源的设置、图片巨细限制、前端图片上传组件,后端图片吸收参数MutiparHttpServletRequest等等。
业务先容

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


  •         发表文章时
  •         上传文章封面时
  •         上传用户头像时
看发表文章时,涉及到四种方式:


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

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

代码实现



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

  1. image:
  2.   abs-tmp-path: /tmp/storage/
  3.   web-img-path: /forum/image/
  4.   tmp-upload-path: /tmp/forum/
  5.   cdn-host:
  6.   oss:
  7.     type: local
  8.     prefix: paicoding/
  9.     endpoint:
  10.     ak:
  11.     sk:
  12.     bucket:
  13.     host: https://cdn.tobebetterjavaer.com
  14. spring:
  15.   web:
  16.     resources:
  17.       # 支持本地图片上传之后的链接,其中 file:///d的用于win系统,后面的file: 适用于mac/linux系统
  18.       static-locations:
  19.         - classpath:/static/
  20.         - file:///d:${image.abs-tmp-path}
  21.         - 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 注解使其和设置文件中的图片设置关联起来。

  1. @Setter
  2. @Getter
  3. @Component
  4. @ConfigurationProperties(prefix = "image")
  5. public class ImageProperties {
  6.     /**
  7.      * 存储绝对路径
  8.      */
  9.     private String absTmpPath;
  10.     /**
  11.      * 存储相对路径
  12.      */
  13.     private String webImgPath;
  14.     /**
  15.      * 上传文件的临时存储目录
  16.      */
  17.     private String tmpUploadPath;
  18.     /**
  19.      * 访问图片的host
  20.      */
  21.     private String cdnHost;
  22.     private OssProperties oss;
  23.     public String buildImgUrl(String url) {
  24.         if (!url.startsWith(cdnHost)) {
  25.             return cdnHost + url;
  26.         }
  27.         return url;
  28.     }
  29. }
复制代码


  • @Setter 和 @Getter 是lombok提供的注解,如许我们就不用写冗长的getter 和setter。
  • @Component 表示这个类是一个 Spring管理的Bean。
  • @ConfigurationProperties是Spring Boot中用于将外部设置文件(如application.properties
  • 或者application.yml)中的属性绑定到Java类的一个注解。通过使用这个注解,我们可以将设置文件中的值自动映射到有相应字段的Java类中。参数prefix = "image" 表示将设置文件image作为前缀的属性绑定到该类中。

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


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

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

  1. <input type = "file"
  2.         accept = "image/*"
  3.         id = "upload"
  4.         class = "click-input"
  5. />
复制代码
当选择图片后,会触发change变乱。

  1.       upload.on("change", function (e) {
  2.         let objUrl = getObjectURL(this.files[0]) //获取图片的路径,该路径不是图片在本地的路径
  3.         if (objUrl) {
  4.           console.log("uploadImg", this.value)
  5.           uploadImg(() => (this.value = null), objUrl)
  6.         }
  7.       })
复制代码

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


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


  1. //建立一?可存取到?file的url
  2. const getObjectURL = function (file) {
  3.   let url = null
  4.   if (window.createObjectURL != undefined) {
  5.     // basic
  6.     url = window.createObjectURL(file)
  7.   } else if (window.URL != undefined) {
  8.     // mozilla(firefox)
  9.     url = window.URL.createObjectURL(file)
  10.   } else if (window.webkitURL != undefined) {
  11.     // webkit or chrome
  12.     url = window.webkitURL.createObjectURL(file)
  13.   }
  14.   return url
  15. }
复制代码
  这段代码使用三种不同的方式来创建暂时URL,以确保兼容性:
  1. window.createObjectURL(file): 这是一个比较旧的方法,用于创建临时URL。在现代的浏览器中,这个方法可能被废弃。
复制代码
  1. window.URL.createObjectURL(file): 这是一个比较新的方法,用于创建按临时URL。在许多现代浏览器中(如FIrefox、Chrome、Edge等),这个方法已经取代了window.createObjectURL。
复制代码
  1. window.webkitURL.createObjectURL(file): 这是一个WenKit特定的方法,用于创建临时的URL。在基于WebKit的浏览器中(如旧版本的Chrome和Safari)这个方法可能是唯一可用的方法。
复制代码
再来看uploadImge方法(实用jQuery的Ajax实现图片上传):
  1. // 上传头图到服务器
  2.       function uploadImg(callback, objUrl) {
  3.         let uploadPic = upload[0].files[0]
  4.         console.log("准备上传", uploadPic)
  5.         if (!checkFileSize(uploadPic)) {
  6.           return;
  7.         }
  8.         let file = new FormData()
  9.         file.append("image", uploadPic)
  10.         $.ajax({
  11.           url: "/image/upload",
  12.           type: "post",
  13.           data: file,
  14.           cache: false,
  15.           contentType: false,
  16.           processData: false,
  17.           success: function (data) {
  18.             console.log("response data", data);
  19.             if (data.status.code > 0) {
  20.               // 图片上传失败
  21.               toastr.error(data.status.msg, "图片上传失败!");
  22.               return;
  23.             }
  24.             const {result: { imagePath },} = data || {}
  25.             defaults['cover'] = imagePath;
  26.             //将图片路径存入src中,显示出图片
  27.             pic.attr("src", objUrl).css('visibility', 'visible') // 展示图片
  28.             $('.upload-icon-up').css('visibility', 'hidden') // 隐藏上传
  29.             callback();
  30.             toastr.info("图片上传成功!");
  31.           },
  32.           error : function(jqXHR, textStatus, errorThrown) {
  33.             toastr.error(jqXHR.responseText, "图片上传失败!");
  34.           },
  35.         })
  36.       }
复制代码

解释一下代码:

这段代码是一个用于上传图片到服务器的函数。下面是对代码的解释:
```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接受图片并处理


  1. @Permission(role = UserRole.LOGIN)
  2. @RequestMapping(path = {"image/", "admin/image/", "api/admin/image/",})
  3. @RestController
  4. @Slf4j
  5. public class ImageRestController {
  6.     @Autowired
  7.     private ImageService imageService;
  8.     /**
  9.      * 图片上传
  10.      *
  11.      * @return
  12.      */
  13.     @RequestMapping(path = "upload")
  14.     public ResVo<ImageVo> upload(HttpServletRequest request) {
  15.         ImageVo imageVo = new ImageVo();
  16.         try {
  17.             String imagePath = imageService.saveImg(request);
  18.             imageVo.setImagePath(imagePath);
  19.         } catch (Exception e) {
  20.             log.error("save upload file error!", e);
  21.             return ResVo.fail(StatusEnum.UPLOAD_PIC_FAILED);
  22.         }
  23.         return ResVo.ok(imageVo);
  24.     }
复制代码

来详细解释一下。


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


第五步·定义ImageService接口

很简单,不在解释。
  1.     /**
  2.      * 保存图片
  3.      *
  4.      * @param request
  5.      * @return
  6.      */
  7.     String saveImg(HttpServletRequest request);
  8. }
复制代码


第六步,实现ImageService接口。

  1. @Override
  2.     public String saveImg(HttpServletRequest request) {
  3.         MultipartFile file = null;
  4.         if (request instanceof MultipartHttpServletRequest) {
  5.             file = ((MultipartHttpServletRequest) request).getFile("image");
  6.         }
  7.         if (file == null) {
  8.             throw ExceptionUtil.of(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "缺少需要上传的图片");
  9.         }
  10.         // 目前只支持 jpg, png, webp 等静态图片格式
  11.         String fileType = validateStaticImg(file.getContentType());
  12.         if (fileType == null) {
  13.             throw ExceptionUtil.of(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "图片只支持png,jpg,gif");
  14.         }
  15.         try {
  16.             return imageUploader.upload(file.getInputStream(), fileType);
  17.         } catch (IOException e) {
  18.             log.error("Parse img from httpRequest to BufferedImage error! e:", e);
  19.             throw ExceptionUtil.of(StatusEnum.UPLOAD_PIC_FAILED);
  20.         }
  21.     }
复制代码

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


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


第七步,定义ImageUploader接口。

  1. public interface ImageUploader {
  2.     String DEFAULT_FILE_TYPE = "txt";
  3.     Set<MediaType> STATIC_IMG_TYPE = new HashSet<>(Arrays.asList(MediaType.ImagePng, MediaType.ImageJpg, MediaType.ImageWebp, MediaType.ImageGif));
  4.     /**
  5.      * 文件上传
  6.      *
  7.      * @param input
  8.      * @param fileType
  9.      * @return
  10.      */
  11.     String upload(InputStream input, String fileType);
  12.     /**
  13.      * 获取文件类型
  14.      *
  15.      * @param input
  16.      * @param fileType
  17.      * @return
  18.      */
  19.     default String getFileType(ByteArrayInputStream input, String fileType) {
  20.         if (StringUtils.isNotBlank(fileType)) {
  21.             return fileType;
  22.         }
  23.         MediaType type = MediaType.typeOfMagicNum(FileReadUtil.getMagicNum(input));
  24.         if (STATIC_IMG_TYPE.contains(type)) {
  25.             return type.getExt();
  26.         }
  27.         return DEFAULT_FILE_TYPE;
  28.     }
  29. }
复制代码
来解释一下这段代码。


  •         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:
  1.     public static String getMagicNum(ByteArrayInputStream inputStream) {
  2.         byte[] bytes = new byte[28];
  3.         inputStream.read(bytes, 0, 28);
  4.         inputStream.reset();
  5.         return bytesToHex(bytes);
  6.     }
复制代码

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

  1.     @Test
  2.     public  void testMagic() throws FileNotFoundException {
  3.         FileInputStream fileInputStream = new FileInputStream("docs/imgs/init_00.jpg");
  4.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  5.         byte[] buffer = new byte[4039];
  6.         int bytesRead;
  7.         try {
  8.             while ((bytesRead = fileInputStream.read(buffer)) != -1) {
  9.    
  10.                 byteArrayOutputStream.write(buffer, 0, bytesRead);
  11.             }
  12.         } catch (IOException e) {
  13.             e.printStackTrace();
  14.         }
  15.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
  16.         //算魔数
  17.         String magicNum = FileReadUtil.getMagicNum(byteArrayInputStream);
  18.         System.out.println(magicNum);
  19.         //根据魔数判断文件类型
  20.         MediaType mediaType = MediaType.typeOfMagicNum(magicNum);
  21.         System.out.println("文件类型" + mediaType);
  22.     }
复制代码
解释:
这段代码是一个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接口。

  1. @Slf4j
  2. @ConditionalOnExpression(value = "#{'local'.equals(environment.getProperty('image.oss.type'))}")
  3. @Component
  4. public class LocalStorageWrapper implements ImageUploader {
  5.     @Autowired
  6.     private ImageProperties imageProperties;
  7.     private Random random;
  8.     public LocalStorageWrapper() {
  9.         random = new Random();
  10.     }
  11.     @Override
  12.     public String upload(InputStream input, String fileType) {
  13.         // 记录耗时分布
  14.         StopWatchUtil stopWatchUtil = StopWatchUtil.init("图片上传");
  15.         try {
  16.             if (fileType == null) {
  17.                 // 根据魔数判断文件类型
  18.                 InputStream finalInput = input;
  19.                 byte[] bytes = stopWatchUtil.record("流转字节", () -> StreamUtils.copyToByteArray(finalInput));
  20.                 input = new ByteArrayInputStream(bytes);
  21.                 fileType = getFileType((ByteArrayInputStream) input, fileType);
  22.             }
  23.             String path = imageProperties.getAbsTmpPath() + imageProperties.getWebImgPath();
  24.             String fileName = genTmpFileName();
  25.             InputStream finalInput = input;
  26.             String finalFileType = fileType;
  27.             FileWriteUtil.FileInfo file = stopWatchUtil.record("存储", () -> FileWriteUtil.saveFileByStream(finalInput, path, fileName, finalFileType));
  28.             return imageProperties.buildImgUrl(imageProperties.getWebImgPath() + file.getFilename() + "." + file.getFileType());
  29.         } catch (Exception e) {
  30.             log.error("Parse img from httpRequest to BufferedImage error! e:", e);
  31.             throw ExceptionUtil.of(StatusEnum.UPLOAD_PIC_FAILED);
  32.         } finally {
  33.             log.info("图片上传耗时: {}", stopWatchUtil.prettyPrint());
  34.         }
  35.     }
  36.     /**
  37.      * 获取文件临时名称
  38.      *
  39.      * @return
  40.      */
  41.     private String genTmpFileName() {
  42.         return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddhhmmssSSS")) + "_" + random.nextInt(100);
  43.     }
复制代码


  • 该类使用了@ConditionalOnExpression 注解,表示只有在设置文件中的 image.oss.type属性为“local”时才会实例化该类。
    1. imageProperties: 一个ImageProperties 类型的对象,用于获取图片相关的配置信息。
    复制代码
  • random: 一个 Random范例的对象,用于生成随机数。
    1. upload(InputStream input, String fileType): 实现 ImageUploader接口中的upload方法,用于将给定的输入流保存到本地的文件系统。首先检查fileType是否为null,如果是,则根据输入流中的字节数据的魔术确定文件的类型,然后,根据配置文件中的路径设置和文件类型,将文件保存到本地文件,并返回文件的URL。
    复制代码
    1. genTmpFileName(): 一个辅助方法,用于生成临时文件名,他使用当前日期和一个随机数生成文件名。
    复制代码
  • 其中调用了 FileWriteUtil.saveFileByStream(finalInput, path, fileName, finalFileType) 方法来对图片举行保存。
  1.     public static FileInfo saveFileByStream(InputStream stream, FileInfo fileInfo) throws FileNotFoundException {
  2.         if (!StringUtils.isBlank(fileInfo.getPath())) {
  3.             mkDir(new File(fileInfo.getPath()));
  4.         }
  5.         String tempAbsFile = fileInfo.getPath() + "/" + fileInfo.getFilename() + "." + fileInfo.getFileType();
  6.         BufferedOutputStream outputStream = null;
  7.         InputStream inputStream = null;
  8.         FileInfo var6;
  9.         try {
  10.             inputStream = new BufferedInputStream(stream);
  11.             outputStream = new BufferedOutputStream(new FileOutputStream(tempAbsFile));
  12.             int len = inputStream.available();
  13.             //判断长度是否大于4K
  14.             if (len <= 4096) {
  15.                 byte[] bytes = new byte[len];
  16.                 inputStream.read(bytes);
  17.                 outputStream.write(bytes);
  18.             } else {
  19.                 int byteCount = false;
  20.                 byte[] bytes = new byte[4096];
  21.                 //1M逐个读取
  22.                 int byteCount;
  23.                 while((byteCount = inputStream.read(bytes)) != -1) {
  24.                     outputStream.write(bytes, 0, byteCount);
  25.                 }
  26.             }
  27.             var6 = fileInfo;
  28.             return var6;
  29.         } catch (Exception var16) {
  30.             log.error("save stream into file error! filename: {} e: {}", tempAbsFile, var16);
  31.             var6 = null;
  32.         } finally {
  33.             try {
  34.                 if (outputStream != null) {
  35.                     outputStream.flush();
  36.                     outputStream.close();
  37.                 }
  38.                 if (inputStream != null) {
  39.                     inputStream.close();
  40.                 }
  41.             } catch (IOException var15) {
  42.                 log.error("close stream error!", var15);
  43.             }
  44.         }
  45.         return var6;
  46.     }
复制代码
该方法的参数包罗输入流和一个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发送的图片数据到后端,后端处理上传哀求并将图片保存到本地文件系统,末了将图片路径返回给前端举行展示。  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表