Vue springboot 上传下载文件

前言

项目要求:从前端把用户的图片/文件上传到后端存到服务器中,及从服务器中下载相应的图片/文件。

由于springboot的Tomcat是内置的,不容易配置,且若采用后端提供url来下载文件,很可能要面对不好解决的跨域问题。

故本文章采用的把文件流放在http请求的body中的方式,使得文件能够在vue前端和spring后端间传输。

并通过简单的方式解决了 下载后的文件乱码/损坏 问题。

后端Springboot

ImageController

@RestController
@RequestMapping("api")
public class ImageController {
    @Autowired
    private ImageService imageService;
    private String uuidName;

    @PostMapping(value = "/image/upload")
    public ResponseEntity upload(@RequestParam("file") MultipartFile uploadFile){
        uuidName = UUID.randomUUID().toString();
        String fileName = uploadFile.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        uuidName += suffix;

        imageService.saveImage(uuidName, uploadFile);
        return new ResponseEntity(HttpStatus.CREATED);
    }

    @PostMapping(value = "/image/export")
    public ResponseEntity export(@Validated @RequestBody String fileName){
        return imageService.exportImage(fileName);
    }

    @DeleteMapping(value = "/image/{id}")
    public ResponseEntity delete(@PathVariable Integer id, @Validated @RequestBody String fileName){
        imageService.delete(id, fileName);
        return new ResponseEntity(HttpStatus.OK);
    }
}

ImageService

public interface ImageService {

    /**
     * delete
     * @param id
     * @param fileName
     */
    //@CacheEvict(allEntries = true)
    void delete(Integer id, String fileName);

    /**
     * saveImage
     * @param uuidName 通过UUID重新命名
     * @param uploadFile 图片文件
     */
    void saveImage(String uuidName, MultipartFile uploadFile);

    /**
     * exportImage
     * @param fileName
     * @return
     */
    ResponseEntity exportImage(String fileName);
}

ImageServiceImpl

public class ImageServiceImpl implements ImageService {
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Integer id, String fileName) {
        String deleteFilePath = ImageConstants.AGREEMENT_FOLDER + fileName;
        try {
            FileUtils.deleteFile(new File(deleteFilePath));
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    @Override
    public void saveImage(String uuidName, MultipartFile uploadFile){
        String uploadPath = ImageConstants.AGREEMENT_FOLDER;

        // 如果目录不存在则创建
        File uploadDir = new File(uploadPath);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        /*
        // 获取源文件名
        String fileName = uploadFile.getOriginalFilename();
//        //获取源文件名
//        String originalFileName = fileName.substring(0, fileName.lastIndexOf("."));
        // 获取源文件名后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        */
        File file = new File(uploadPath, uuidName);
        try {
            uploadFile.transferTo(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public ResponseEntity exportImage(String fileName){
        String exportPath = ImageConstants.AGREEMENT_FOLDER + fileName;
        try {
            File file = new File(exportPath);
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Disposition", "attachment; filename=" + fileName);
            headers.add("Content-Length", String.valueOf(file.length()));
            return  ResponseEntity
                    .ok()
                    .headers(headers)
                    .contentLength(file.length())
                    .contentType(MediaType.parseMediaType("application/octet-stream"))
                    .body(new FileSystemResource(file));
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
        }
        return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
}

ImageConstants

public interface ImageConstants {

    /** 存储目录名称 */
    public static final String AGREEMENT_FOLDER_NAME = "_image";
    /** 存储目录 */
    public static final String AGREEMENT_FOLDER = Constants.ROOT_FOLDER + AGREEMENT_FOLDER_NAME + File.separator;

}

FileUtils

public class FileUtils {
    /**
     * 递归删除文件
     * @param file
     */
    public static void deleteFile(File file) {
        // 判断是否是一个目录, 不是的话跳过, 直接删除; 如果是一个目录, 先将其内容清空.
        if(file.isDirectory()) {
            // 获取子文件/目录
            File[] subFiles = file.listFiles();
            // 遍历该目录
            for (File subFile : subFiles) {
                // 递归调用删除该文件: 如果这是一个空目录或文件, 一次递归就可删除.
                // 如果这是一个非空目录, 多次递归清空其内容后再删除
                deleteFile(subFile);
            }
        }
        // 删除空目录或文件
        file.delete();
    }
}

前端Vue

image.vue

<!-- 上传图片 -->
<el-upload
  ref="upload"
  :multiple="false"
  :file-list="fileList"
  :auto-upload="false"
  :limit="1"
  :http-request="importConfirm"
  :before-upload="beforeUpload"
  action
  accept=".jpeg, .jpg, .png"
>
  <el-button slot="trigger" size="small" type="primary" plain>选取文件</el-button>
  <el-button
      style="margin-left: 10px;"
      size="small"
      type="success"
      plain
      @click="submitUpload"
  >上传到服务器</el-button>-->
  <div
     slot="tip"
     class="el-upload__tip"
  >注意:只能上传jpeg/png文件,且文件大小不能超过{{ fileSizeLimit }}MB
  </div>
</el-upload>

<!-- 显示图片 -->
<div v-show="isShow" style="margin: 20px auto">
  <p>{{ imageName }}</p>
  <el-image :src="imageLink" />
</div>

<script>
import { del, upload, download } from '@/api/image'
export default {
  data() {
    return {
      delLoading: false, isShow: false,
      imageLink: '', imageName: '',
    }
  },
  created() {
    // 略
  },
  methods: {
    // 上传的文件格式、大小限制
    beforeUpload(file) {
      var testmsg = file.name.substring(file.name.lastIndexOf('.') + 1)
      const extension0 = testmsg === 'jpeg'
      const extension1 = testmsg === 'jpg'
      const extension2 = testmsg === 'png'
      const isLt2M = file.size / 1024 / 1024 < this.fileSizeLimit
      if (!extension0 && !extension1 && !extension2) {
        this.$notify({
          message: '上传的文件只能是jpeg、jpg、png格式!',
          type: 'warning',
          duration: 2500
        })
        this.loading = false
        return
      }
      if (!isLt2M) {
        this.$message({
          message: '上传文件大小不能超过 ' + this.fileSizeLimit + 'MB!',
          type: 'warning'
        });
      }
      return (extension0 || extension1 || extension2) && isLt2M
    },
    // 文件上传
	importConfirm(item) {
      const fileObj = item.file
      const formData = new FormData()
      formData.append('file', fileObj)
      upload(formData).then(res => {
        this.$notify({
          title: '文件上传成功',
          type: 'success',
          duration: 2500
        })
      }).catch(err => {
        this.loading = false
        console.log(err.response.data.message)
      })
    },
    submitUpload(){
      this.$refs.upload.submit() // 会去执行 importConfirm
    },
    // 图片文件下载
    download(filePath) {
      download(filePath).then(response => {
        const data = response
        if (!data) {
          return
        }
        const blob = new Blob([data])
        if (window.navigator.msSaveOrOpenBlob) { // 兼容IE10
          navigator.msSaveBlob(blob, filePath)
        } else { // 其他非IE内核支持H5的浏览器
          const url = window.URL.createObjectURL(blob)
          const link = document.createElement('a')
          link.style.display = 'none'
          link.href = url
          link.setAttribute('download', filePath)
          document.body.appendChild(link)
          link.click()
        }
      }).catch(function (error) {
        console.log(error)
      })
    },
    // 图片文件显示
    show(fileName, filePath) {
      download(filePath).then(response => {
        const data = response
        if (!data) {
          return
        }
        const blob = new Blob([data])
        this.imageName = fileName
        this.imageLink = window.URL.createObjectURL(blob)
        this.isShow = true
      }).catch(function (error) {
        console.log(error)
      })
    }
  }
}
</script>

image.js

// 封装好的axios请求
import request from '@/utils/request'

export function del(id, data) {
  return request({
    url: 'api/image/' + id,
    method: 'delete',
    data
  })
}

export function upload(data) {
  return request({
    url: 'api/image/upload',
    method: 'post',
    data
  })
}

export function download(data) {
  return request({
    url: 'api/image/export',
    method: 'post',
    responseType: 'arraybuffer', // 解决下载后的文件乱码/损坏问题
    data
  })
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值