Spring Boot + vue-element 开发个人博客项目实战教程(二十六、文章封面上传)

143 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情

文章封面上传

文章封面上传功能是一个新的知识点,我们后端要添加新的接口,上传图片的接口,这里就涉及到文件的上传,保存路径等操作,这也是在以后的工作中经常使用的文件操作,这个也是一个重点,希望大家认真对待学习。

我们先去写后端图片上传的功能,图片的存储等操作。希望大家能了解图片上传的业务逻辑思路。

1、后端功能

和之前的业务流程一样,我们先写一个图片上传的接口,打开ArticleService.java

	/**
     * 上传文件
     *
     * @param file
     * @return
     */
    String uploadFile(MultipartFile file);

这里说一下MultipartFile对象,有些同学可能没有学过或不了解,我这里简单的说明一下。

使用MultipartFile这个类主要是来实现以表单的形式进行文件上传功能。首先MultipartFile是一个接口,并继承自InputStreamSource,且在InputStreamSource接口中封装了getInputStream方法,该方法的返回类型为InputStream类型,这也就是为什么MultipartFile文件可以转换为输入流。通过以下代码即可将MultipartFile格式的文件转换为输入流。

这个MultipartFile有一些常用的方法。

  • getName

getName方法获取的是前后端约定的传入文件的参数的名称,在SpringBoot后台中则是通过 @Param("uploadFile") 注解定义的内容。

  • getOriginalFileName

getOriginalFileName方法获取的是文件的完整名称,包括文件名称+文件拓展名

  • getContentType

getContentType方法获取的是文件的类型,注意是文件的类型,不是文件的拓展名。

  • getSize

getSize方法用来获取文件的大小,单位是字节。

  • getInputStream

getInputStream方法用来将文件转换成输入流的形式来传输文件,会抛出IOException异常。

还有一些其他的方法,大家可以自己去查找,以上这些我们会经常用到。

接口有了,我们再去写实现方法,打开ArticleServiceImpl.java实现类。

通过上边对MultipartFile的解释,下面我们就使用到了。

	@Override
    public String uploadFile(MultipartFile file) {
        try {
            // 获取文件md5值
            String md5 = FileUtils.getMd5(file.getInputStream());
            // 获取文件扩展名
            String extName = FileUtils.getExtName(file.getOriginalFilename());
            // 重新生成文件名
            String fileName = md5 + extName;
            // 判断文件是否已存在
            if (!exists(ARTICLE + fileName)) {
                // 不存在则继续上传
                upload(ARTICLE, fileName, file.getInputStream());
            }

            // 返回文件访问路径
            return getFileAccessUrl(ARTICLE + fileName);

        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件上传失败");
        }
        return null;
    }
  1. 首先我们拿到上传的图片之后,我们先进行MD5然后拿到文件的MD5值进行图片文件名重新命名,为的就是防止文件名重复,被覆盖,就会导致图片数据丢失。
  2. 然后通过getOriginalFilename获取图片的后缀格式。
  3. 拼接起来组成新的图片名称。
  4. 为了防止丢失数据,再次进行图片名称判断,有就直接返回地址,没有则继续上传图片或文件。

以上会用到几个方法和工具类如下:

FileUtils工具类:

package com.blog.personalblog.util;

import cn.hutool.core.util.StrUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.codec.binary.Hex;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;

/**
 * 文件md5工具类
 *
 */
@Log4j2
public class FileUtils {

    /**
     * 获取文件md5值
     *
     * @param inputStream 文件输入流
     * @return {@link String} 文件md5值
     */
    public static String getMd5(InputStream inputStream) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("md5");
            byte[] buffer = new byte[8192];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                md5.update(buffer, 0, length);
            }
            return new String(Hex.encodeHex(md5.digest()));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 得到文件扩展名
     *
     * @param fileName 文件名称
     * @return {@link String} 文件后缀
     */
    public static String getExtName(String fileName) {
        if (StrUtil.isBlank(fileName)) {
            return "";
        }
        return fileName.substring(fileName.lastIndexOf("."));
    }

}

exists方法:

本地路径需要在配置文件中配置,打开application.yml配置文件,然后配置如下:

upload:
  local:
    path: /blog/uploadFile/
    url: http://localhost:9090/blog
	/**
     * 本地路径
     */
    @Value("${upload.local.path}")
    private String localPath;

	/**
     * 访问url
     */
    @Value("${upload.local.url}")
    private String localUrl;

    private static final String ARTICLE = "articles/";


	/**
     * 判断文件是否存在
     *
     * @param filePath 文件路径
     * @return
     */
    public Boolean exists(String filePath){
        return new File(localPath + filePath).exists();
    }

getFileAccessUrl方法:

    /**
     * 获取文件访问url
     *
     * @param filePath 文件路径
     * @return
     */
    public String getFileAccessUrl(String filePath) {
        return localUrl + localPath + filePath;
    }

upload方法:

private void upload(String path, String fileName, InputStream inputStream) throws IOException {
    File directory = new File(localPath + path);
    if (!directory.exists()) {
        if (!directory.mkdirs()) {
            log.error("创建目录失败");
        }
    }
    // 写入文件
    File file = new File(localPath + path + fileName);
    if (file.createNewFile()) {
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
        byte[] bytes = new byte[1024];
        int length;
        while ((length = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, length);
        }
        bos.flush();
        inputStream.close();
        bis.close();
        bos.close();
    }
}

然后我们在去写controller层接口,打开ArticleController.java,这个就不要记日志了,我们日志那边没有做文件的处理,所以会报错,这里暂时不需要记日志。

	/**
     * 上传网站logo封面
     * @param file
     * @return 返回logo地址
     */
    @ApiOperation(value = "上传网站logo封面")
    @PostMapping("upload")
    public JsonResult<String> uploadImg(@RequestParam(value = "file") MultipartFile file) {
        String s = articleService.uploadFile(file);
        return JsonResult.success(s);
    }

好了,上传图片的功能已经实现,我们接下来用postman测试一下。

image.png