首页 > 其他分享 >SpringBoot 上传图片

SpringBoot 上传图片

时间:2024-04-19 17:16:07浏览次数:31  
标签:picture return SpringBoot upload String file 上传 图片

1 概述

新做的博客系统需要在 markdown 文本中插入图片, 之前完成过上传图片的相关配置,但未做总结,借着这个机会,对于 springboot 上传图片接口的相关配置和操作,做一个系统性阐述。以作为未来相关业务的参考。

本文主要阐述后端相关配置,少量前端(vue3)内容仅是为了作为测试。

2 配置文件

配置相关信息仅需两步:

  1. yaml 文件中配置 相关路径静态资源
  2. 在配置类中配置静态资源处理器。

2.1 yaml 文件配置

为保证上传路径的 可配置性,这里的上传路径相关字符串全部配置在 application.yaml 文件中,然后再使用 @Value() 注解注入即可。

注:无关配置已省略

upload:
  upload-path: ./upload
  image:
    user: ${upload.upload-path}/image/avatar # 用户图片(包含头像和封面)路径
    common: ${upload.upload-path}/image/common # 通用图片(主要是文章公告等图片)路径

spring:
    # 配置静态资源路径
  web:
    resources:
      static-locations: classpath:/static/, file:${upload.upload-path}

  # 限制上传文件最大值
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

2.2 配置类

这里主要配置静态资源处理器,可以理解为请求 url 到文件路径的映射。

/**
 * @author gs_huang
 * @date 2024/4/9 11:19
 */
@Configuration
public class ResourceConfig implements WebMvcConfigurer {

    @Value("${upload.upload-path}/")
    private String uploadPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/upload/**").addResourceLocations("file:" + uploadPath);
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }
}

3 保存图片

配置好上传路径和静态资源处理器后,就需要向外提供保存图片的接口了。

这里需要在 service 层和 controller 层提供相应的保存图片以及把相关数据保存至数据库的方法。

3.1 service 层

1、service 接口:

public interface PictureService extends IService<Picture> {

    /**
     * @param url 请求 url
     * @param file 图片文件
     * @return 上传反馈
     */
    Picture uploadImage(String url, MultipartFile file);

}

注意我这里继承 IService<Picture> 是因为我使用了 Mybatis-Plus,不影响本文阐述功能。

2、serviceImpl 实现类:

@Service
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
    implements PictureService{

    private final Logger logger = LoggerFactory.getLogger(PictureServiceImpl.class);

    @Value("${upload.image.common}/")
    private String commonPath;

    /**
     * 日期路径格式
     */
    private final String datePathFormat = "yyyy/MM/dd/";

    /**
     * 日期格式
     */
    private final SimpleDateFormat sdf = new SimpleDateFormat(datePathFormat);

    @Override
    public Picture uploadImage(String url, MultipartFile file) {
        //1.获取日期字符串
        String formatDate = sdf.format(new Date());

        //2.获取新文件名
        String newFileName = getNewFileName(file.getOriginalFilename());

        //3.保存图片

        //3.1 判断文件夹是否存在,不存在则创建
        String imageDirPath = commonPath + formatDate;
        File imageDir = new File(imageDirPath);
        if (!imageDir.exists()){
            imageDir.mkdirs();
        }

        //3.2 拼接文件完整路径
        String imageFilePath = imageDirPath + "/" + newFileName;

        //3.3 保存
        try {
            file.transferTo(new File(imageDir.getAbsoluteFile(), newFileName));

        } catch (Exception e){
            logger.error("文件 {} 保存失败", imageFilePath, e);
            return null;
        }

        //4.拼接请求路径

        //4.1 正则匹配请求前缀
        Pattern pattern = Pattern.compile("(.*)/admin/picture.*");
        Matcher matcher = pattern.matcher(url);

        String urlPrefix = "http://localhost:18080";

        while(matcher.find()){
            urlPrefix = matcher.group(1);
        }
        String urlPath = urlPrefix + "/upload/image/common/" + formatDate + newFileName;

        //5.构造存储数据
        Picture picture = new Picture();

        Date nowDate = new Date();
        picture.setCreated(nowDate);
        picture.setEdited(nowDate);
        picture.setPath(urlPath);
        picture.setStatus(1);

        String pictureId = IdWorker.getIdStr(picture);
        picture.setId(pictureId);

        //6.图片数据存入数据库
        save(picture);

        //7.返回图片信息
        return picture;
    }

    /**
     * 根据旧文件名生成新文件名,使用 uuid 生成
     * @param oldName 旧文件名
     * @return 新文件名
     */
    public String getNewFileName(String oldName){

        //1.旧名称判空(这里使用了 hutools)
        if (StrUtil.isEmpty(oldName)){
            return null;
        }

        //2.正则匹配获取文件后缀
        Pattern sufixPattern = Pattern.compile(".*(\\..*)");
        Matcher matcher = sufixPattern.matcher(oldName);

        //3.UUID 生成文件新名称(这里使用了 hutools)
        String newFileName = UUID.randomUUID().toString();
        if (matcher.find()){
            newFileName += matcher.group(1);
        }

        //4.返回新名称
        return newFileName;
    }
}

3.2 controller 层

/**
 * @author gs_huang
 * @date 2024/4/9 9:52
 */
@RestController
@RequestMapping("/admin/picture")
public class PictureController {

    @Autowired
    private PictureService pictureService;

    @PostMapping("/uploadImage")
    public Result uploadImage(HttpServletRequest request, @RequestParam("file") MultipartFile file){

        if (file == null) {
            return Result.formatError("文件错误");
        }
        Picture picture = pictureService.uploadImage(request.getRequestURL().toString(), file);

        return picture != null ? Result.success(picture) : Result.error("上传图片失败");
    }
}

4 前端调用

这里我开发的是 vue3 整合 v-md-editor 后,markdown 文本上传图片的功能,所以测试也是使用的其提供的回调方法。

这里有个坑,即 v-md-editor 提供的上传图片回调方法必须使用 formdata 格式上传,否则后端会报错!

4.1 api 接口

以下我自己封装的 axios 实例 http.js,如果你有自己的实例,可以忽略以下代码.

// axios 基础封装
import axios from 'axios'
import { authStore } from '@/stores/auth'
import router from '@/router'

const httpInstance = axios.create({
  baseURL: 'http://localhost:18081',
  timeout: 5000
})

// 拦截器

// axios 请求拦截器
httpInstance.interceptors.request.use(config => {
  config.headers['token'] = authStore().token
  return config
}, e => Promise.reject(e))

// axios 响应拦截器
httpInstance.interceptors.response.use(res => {
  if ("token" in res.headers) {
    authStore().setToken(res.headers["token"])
  }

  if (res.data.code === 401) {
    router.push('/login')
    authStore().removeToken();
    authStore().removeUserAuth();
  }

  return res.data
}, e => {
  return Promise.reject(e)
})

// 暴露出请求实例
export default httpInstance

这里会用到上述 httpInstance 实例。
picture.js:

import httpInstance from '@/utils/http'

export function uploadImageAPI(file) {
  return httpInstance({
    url: `/admin/picture/uploadImage`,
    method: 'post',
    data: file
  })
}

4.2 调用接口

这里会忽略掉无关代码。

AddNotice.vue:

<script setup>
import { ElMessage } from "element-plus";
import { uploadImageAPI } from "@/apis/admin/picture";

const uploadImage = async (insertImage, file) => {
  const formData = new FormData();
  formData.append("file", file);

  const res = await uploadImageAPI(formData);
  if (res.code === 200) {
    insertImage({
      url: res.data.path,
      desc: res.data.id,
    });

    ElMessage({
      type: "success",
      message: "图片上传成功",
      plain: true,
    });
  } else {
    ElMessage({
      type: "error",
      message: res.msg,
      plain: true,
    });
  }
};

// 上传图片
const handleUploadImage = (event, insertImage, files) => {
  console.log(files[0]);

  uploadImage(insertImage, files[0]);
};
</script>

<template>
  <div class="add-notice">
    <v-md-editor
      v-model="addNotice.content"
      height="calc(100% - 150px)"
      :include-level="[1, 2, 3]"
      @save="saveLocal"
      @blur="saveLocal"
      :disabled-menus="[]"
      @upload-image="handleUploadImage"
    ></v-md-editor>
  </div>
</template>

5 效果展示

6 踩坑记录

对于 v-md-editor 中图片上传的数据格式,必须要使用 formdata 来进行封装才行,而不是一味的修改请求 headers 中的 Content-Type

封装 formdata 关键代码:

const formData = new FormData();
formData.append("file", file);

封装完成后即可将 formdata 作为 file 对象传输给后端。

但是对于 elementUI 中的文件上传,则可以使用原格式,不需要封装 formdata,即修改 headers 即可。

修改 headers 示例如下:

export function uploadImage(file) {
  return request({
    headers: {
      'Content-Type': 'multipart/form-data',
    },
    url: `/admin/picture/uploadImage`,
    method: 'post',
    data: file
  })
}

7 写在最后

v-md-editor 官网:https://code-farmer-i.github.io/vue-markdown-editor/zh/

关于 v-md-editor 图片上传,参考:https://code-farmer-i.github.io/vue-markdown-editor/zh/senior/upload-image.html

最后感慨一句,vue3 整合 v-md-editor 成功后,是真的帅啊!

标签:picture,return,SpringBoot,upload,String,file,上传,图片
From: https://www.cnblogs.com/huang-guosheng/p/18146420

相关文章

  • 基于springboot的图书个性化推荐系统
     介绍图书个性化推荐系统的主要使用者分为管理员和学生,实现功能包括管理员:首页、个人中心、学生管理、图书分类管理、图书信息管理、图书预约管理、退换图书管理、管理员管理、留言板管理、系统管理,学生:首页、个人中心、图书预约管理、退换图书管理、我的收藏管理,前台首页;首页......
  • 基于Springboot+vue的图书管理系统
    ​ 介绍基于Springboot+vue的图书管理系统系统主要分为管理员角色和用户角色,具体的功能设计包括注册登录管理、个人中心管理、用户信息管理、图书信息管理、借阅信息管理,论坛信息管理等模块。软件架构开发系统:Windows10/11架构模式:MVCJDK版本:JavaJDK1.8开发工具:ID......
  • 文件体积达到 1 GB 甚至 1 TB 的图片会呈现何种内容?
    作者:文森特汪的热血链接:https://www.zhihu.com/question/360608822/answer/1253394326来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。我就存了一张1.34G的图片,存在电脑里这是它的截图<imgsrc="https://picx.zhimg.com/50/v2-8136c3e43f6cbffb2aa4......
  • springBoot 读写分离 事务
      ShardingSphere-JDBC事务均用主库。 1.添加pom.xml<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId><ver......
  • jquery 实现获取本地图片并展示
    文件不能直接设置为img元素的src属性值。要展示file文件,需要在img属性中使用JavaScript来实现,可以参考以下代码:html页面代码:<inputtype="file"id="photoInput"accept=".jpg,.jpeg,.png"multiple><imgid="uploaded-image"/>然后在js中监听文件输入:......
  • blog.admin net8发布二级目录,及图片上传路径处理
    1、发布二级目录,修改以下配置,及对应的二级目录名跟配置的一致 2、图片上传a、修改后台api代码imgController.cspublicasyncTask<MessageModel<string>>InsertPicture([FromForm]UploadFileDtodto){if(dto.file==null||!dto.file.Any())returnFail......
  • Springboot 下载模板
    文件路径在项目的resources下面  路径 resources/tempExcel/导入模板.xlsx后端代码:/***下载模板**@paramresponse*@throwsIOException*/@RepeatSubmit@PostMapping("/download/template")publicvoiddownloadTemplate(HttpServletResponseresponse)throws......
  • obsidian和typora图片兼容问题
    1.打开obsidian的第三方插件-->插件市场找到CustomAttachmentLocation下载(当然这个也是需要一点魔法的)2.配置obsidian的文件与链接3.配置obsidian的CustomAttachmentLocation插件4.typora的文件路径配置一样即可最后对比,两个程序之间可以相互查看文件了......
  • springBoot 读写分离
    1.pom.xml配置<!--读写分离api--><dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.0.0-RC1......
  • vscode使用PasteImage插入图片
    vscode使用PasteImage插入图片需求在vscode中写Markdown文件,经常需要插入图片,使用插件PasteImage进行简单配置后,就可以方便插入图片并自动存放到相应路径的文件夹中安装及配置安装从扩展商店搜索PasteImage并安装即可配置vscode设置中搜索PasteImage,找到PasteImage:......