持续创作,加速成长!这是我参与「掘金日新计划 · 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;
}
- 首先我们拿到上传的图片之后,我们先进行MD5然后拿到文件的MD5值进行图片文件名重新命名,为的就是防止文件名重复,被覆盖,就会导致图片数据丢失。
- 然后通过getOriginalFilename获取图片的后缀格式。
- 拼接起来组成新的图片名称。
- 为了防止丢失数据,再次进行图片名称判断,有就直接返回地址,没有则继续上传图片或文件。
以上会用到几个方法和工具类如下:
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测试一下。