springboot实现图片上传、下载功能

写一下后端spring项目经常要做的功能,实现图片上传和下载,这里也把前端代码附上了。可能算是个简单版的,我这里图片上传都存在当前项目的根目录resource下了。

这里包含了,上传文件、下载文件(下载文件流、获取base64),service中还有个文件流转base64的工具方法。

下面是后端代码

Controller

java 复制代码
package com.wft.controller;

import com.wft.model.ActionResult;
import com.wft.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;

@RestController
@RequestMapping("/file")
public class TestController {

    @Autowired
    TestService testService;

    /**
     * 文件上传
     * @param file
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public ActionResult uploadTest(@RequestParam("file") MultipartFile file) throws IOException {
        return testService.upload(file);
    }

    /**
     * 文件下载
     * @param name
     * @return
     * @throws FileNotFoundException
     */
    @GetMapping("/download/{name}")
    @CrossOrigin
    public ResponseEntity<InputStreamResource> downloadTest(@PathVariable("name") String name) throws FileNotFoundException {
        return testService.download(name);
    }

    /**
     * 获取文件的base64编码
     * @param name
     * @return
     * @throws IOException
     */
    @GetMapping("/getBase64/{name}")
    public ActionResult getBase64(@PathVariable("name") String name) throws IOException {
        return testService.getBase64(name);
    }

}

Service接口就不贴了哈

ServiceImpl

java 复制代码
package com.wft.service.impl;

import com.wft.model.ActionResult;
import com.wft.service.TestService;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Service
public class TestServiceImpl implements TestService {

    /**
     * 文件上传逻辑
     * @param file
     * @return
     * @throws IOException
     */
    @Override
    public ActionResult upload(MultipartFile file) throws IOException {
        if(file.isEmpty()) {
            return ActionResult.fail("文件不能为空");
        }
        // 上传的文件名称
        String originalFilename = file.getOriginalFilename();
        // 文件后缀
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        // 使用uuid当前文件名存储,防止名称相同覆盖
        String uuid = UUID.randomUUID().toString();
        String fileName = uuid + suffix;
        // 文件保存的路径(我这里存在当前项目下,所以获取当前项目的绝对路径,然后拼接上图片存放的文件夹的名称)
        String savePath = ResourceUtils.getURL("resource").getPath();
        // 判断是否存在resource目录, 没有则创建
        File dir = new File(savePath);
        if(dir != null && !dir.exists()) {
            dir.mkdir();
        }
        savePath = savePath + File.separator + fileName;
        // 文件上传
        file.transferTo(new File(savePath));
        // 将文件存储的名称uuid和原文件名称返回给前端
        Map<String, Object> map = new HashMap<>();
        map.put("id", uuid);
        map.put("name", originalFilename);
        // 将下载图片的接口(返回文件流)路径返回给前端,前端直接将服务器地址拼上该链接即可回显图片
        map.put("url", "/file/download/" + fileName);
        return ActionResult.success("上传成功", map);
    }

    /**
     * 文件下载逻辑(文件流)
     * @param name
     * @return
     * @throws FileNotFoundException
     */
    @Override
    public ResponseEntity<InputStreamResource> download(String name) throws FileNotFoundException {
        String path = ResourceUtils.getURL("resource").getPath() + File.separator + name;
        File file = new File(path);
        if(!file.exists()) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        // 创建输入流
        InputStream inputStream = new FileInputStream(file);
        // 设置HTTP头部信息
        HttpHeaders headers = new HttpHeaders();
        System.out.println(file.getName() + "---->>>文件名");
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
        // 返回文件流
        ResponseEntity<InputStreamResource> body = ResponseEntity.ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(new InputStreamResource(inputStream));
        return body;
    }

    /**
     * 获取文件的base64编码
     * @param name
     * @return
     * @throws IOException
     */
    @Override
    public ActionResult getBase64(String name) throws IOException {
        String path = ResourceUtils.getURL("resource").getPath() + File.separator + name;
        File file = new File(path);
        if(!file.exists()) {
            return ActionResult.fail("文件不存在");
        }
        byte[] fileContent = Files.readAllBytes(file.toPath());
        String base64 = Base64.getEncoder().encodeToString(fileContent);
        base64 = "data:image/png;base64," + base64;
        return ActionResult.success((Object) base64);
    }

    /**
     * 将文件流转为base64编码(工具方法, service中接口没有该方法)
     * @param response
     * @return
     * @throws IOException
     */
    public String streamToBase64(ResponseEntity<InputStreamResource> response) throws IOException {
        // 获取InputStreamResource对象
        InputStreamResource resource = response.getBody();
        if (resource == null) {
            throw new IllegalArgumentException("Response body is null");
        }
        try (InputStream inputStream = resource.getInputStream();
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {

            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);
            }
            // 将InputStream转换为字节数组
            byte[] fileBytes = byteArrayOutputStream.toByteArray();
            // 使用Base64编码器进行编码
            String base64Encoded = Base64.getEncoder().encodeToString(fileBytes);
            base64Encoded = "data:image/png;base64," + base64Encoded;
            return base64Encoded;
        }
    }
}

代码里面也都写注释了,大家一看应该就明白了。

简单说一下思路把,存图片的时候,我是以uuid当作图片的名称存储的,这样即便是前端是两个文件,但是名称一样,上传之后也不会覆盖掉原来的图片。

然后图片上传完之后,我把回显图片的路径返回给前端了,前端可以使用服务器地址(当然开发环境下会有跨域问题,一般会直接用前缀)拼上返回的这个路径即可回显图片。

返回的这个url其实就是后端编写好的一个接口,返回的是个文件流,前端直接将完整的请求的后端的路径放在img标签的src上,其实就相当于发送了请求,所以这里注意这种方式回显要求后端将改接口放在白名单中(即该接口不需要token校验),否则前端就不能像正常路径一样直接放在img的src上回显,就要像普通的接口一样调用接口,然后通过URL.createObjectURL(new Blob(res))的方式转为路径再复制给src。

然后我上面封装了了个ActionResult的返回给前端的包装类,也在这贴一下吧:

ActionResult

java 复制代码
package com.wft.model;

import lombok.Data;

@Data
public class ActionResult<T> {

    private Integer code;

    private String msg;

    private T data;


    public static ActionResult success() {
        ActionResult jsonData = new ActionResult();
        jsonData.setCode(200);
        jsonData.setMsg("success");
        return jsonData;
    }

    public static ActionResult success(String msg) {
        ActionResult jsonData = new ActionResult();
        jsonData.setCode(200);
        jsonData.setMsg(msg);
        return jsonData;
    }

    public static ActionResult success(Object object) {
        ActionResult jsonData = new ActionResult();
        jsonData.setData(object);
        jsonData.setCode(200);
        jsonData.setMsg("success");
        return jsonData;
    }

    public static ActionResult success(String msg, Object object) {
        ActionResult jsonData = new ActionResult();
        jsonData.setData(object);
        jsonData.setCode(200);
        jsonData.setMsg(msg);
        return jsonData;
    }

    public static ActionResult fail(Integer code, String message) {
        ActionResult jsonData = new ActionResult();
        jsonData.setCode(code);
        jsonData.setMsg(message);
        return jsonData;
    }

    public static ActionResult fail(String msg, String data) {
        ActionResult jsonData = new ActionResult();
        jsonData.setMsg(msg);
        jsonData.setData(data);
        return jsonData;
    }

    public static ActionResult fail(String msg) {
        ActionResult jsonData = new ActionResult();
        jsonData.setMsg(msg);
        jsonData.setCode(400);
        return jsonData;
    }
}

接下来再贴一下前端代码:

java 复制代码
<template>
  <div class="wft-test">
    <el-upload
      class="avatar-uploader"
      :action="baseURL + '/file/upload'"
      :show-file-list="false"
      :on-success="handleAvatarSuccess"
    >
      <img v-if="imageUrl" :src="baseURL + imageUrl" class="avatar" />
      <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
    </el-upload>

    <!-- 测试下载图片 获取文件流 -->
    <el-button @click="testDownload('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">测试下载图片流</el-button>

    <!-- 测试下载图片 获取base64 -->
    <el-button @click="testDownloadBase64('12b83c8d-3d54-420d-a191-bd750fa571c4.png')">测试下载图片base64</el-button>
    <img style="width: 200px;height: 200px;" v-if="imgBase64" :src="imgBase64" alt="">

  </div>
</template>
<script setup lang='ts'>
import { ref } from 'vue';
import request from '@/utils/request';

const imageUrl = ref("");
const imgBase64 = ref("");

// 上传成功回调
function handleAvatarSuccess(res: any) {
  if(res.code == 200) {
    imageUrl.value = res.data.url  // 正常成功回显
  }
}

/**
 * 测试下载图片(获取文件流)
 * @param fileName 文件名
 */
function testDownload(fileName: string) {
  request({
    url: `/file/download/${fileName}`,
    method: 'get',
    responseType: 'blob'
  }).then((res: any) => {
    const link = document.createElement('a')
    link.href = URL.createObjectURL(new Blob([res]))
    link.download = 'test.png'
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  })
}

/**
 * 测试下载图片(获取文件base64编码)
 * @param fileName 
 */
function testDownloadBase64(fileName: string) {
  request({
    url: `/file/getBase64/${fileName}`,
    method: 'get'
  }).then((res: any) => {
    if(res.code == 200) {
      imgBase64.value = res.data
    }
  })
}

</script>
<style scoped>
.wft-test {
  width: 100%;
  height: 100%;
}

.avatar-uploader .avatar {
  width: 178px;
  height: 178px;
  display: block;
}
</style>

<style>
.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  text-align: center;
}
</style>
相关推荐
myNameGL几秒前
linux安装idea
java·ide·intellij-idea
青春男大2 分钟前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
HaiFan.38 分钟前
SpringBoot 事务
java·数据库·spring boot·sql·mysql
2401_8827275744 分钟前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
我要学编程(ಥ_ಥ)1 小时前
一文详解“二叉树中的深搜“在算法中的应用
java·数据结构·算法·leetcode·深度优先
music0ant1 小时前
Idea 添加tomcat 并发布到tomcat
java·tomcat·intellij-idea
计算机徐师兄1 小时前
Java基于SSM框架的无中介租房系统小程序【附源码、文档】
java·微信小程序·小程序·无中介租房系统小程序·java无中介租房系统小程序·无中介租房微信小程序
源码哥_博纳软云1 小时前
JAVA智慧养老养老护理帮忙代办陪诊陪护小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
追逐时光者2 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
大梦百万秋2 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful