SpringBoot文件上传

1.在controller文件下创建一个FileController类( 01:25 )

在这里插入图片描述

2.创建sys_file数据表,id列勾选自增( 03:30 )

在这里插入图片描述

3.接着回到FileController写代码 ( 06:19 )

在这里插入图片描述
打开application.yml,增加files以及下面的代码( 10:18 )
注意:files文件由代码自动创建
在这里插入图片描述
继续编辑FileController类文件( 11:07 )

package com.SpringBoot.demo.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * 文件上传相关接口
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Value("${files.upload.path}")  //将application.yml文件中的文件位置赋予到fileUploadPath    //注意导包:.beans.factory.annotation.Value;
    private String fileUploadPath;

    /**
     * 文件上传接口
     * @param file  前端传过来的文件
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public String upload(@RequestParam MultipartFile file) throws IOException {
        String OriginalFilename = file.getOriginalFilename(); //获取( 原始名称 )
        String type = FileUtil.extName(OriginalFilename); //获取( 文件类型 )    //注意FileUtil.extName是String
        long size = file.getSize(); //获取( 文件大小 )

        // 先存储到磁盘
        File uploadParentFile = new File(fileUploadPath);   //注意导报: File(java.io)
        // 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录
        if(!uploadParentFile.exists()) {
            uploadParentFile.mkdirs();
        }
        // 定义一个文件唯一的标识位
        String uuid = IdUtil.fastSimpleUUID();
        File uploadFile = new File(fileUploadPath + uuid);
        // 把前端获取到的文件存储到磁盘目录
        file.transferTo(uploadFile); //把文件传到磁盘上去   //注意(1)file.transferTo()是File dest  (2)transferTo报红Add一个异常数据就好了
        // 再存储到数据库
        return "";
    }
}

然后打开postman测试,由于SpringBoot拦截器没有放行files所以出现无token
在这里插入图片描述

在SpringBoot拦截器InterceptorConfig类文件中放行files,重新运行后台

在这里插入图片描述
我们可以看见电脑C盘有files文件生成,里面有我们的照片
在这里插入图片描述
通过拼接的方式获取图片的后缀,然后重新运行后台,再使用Postman测试

package com.SpringBoot.demo.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

/**
 * 文件上传相关接口
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Value("${files.upload.path}")  //将application.yml文件中的文件位置赋予到fileUploadPath    //注意导包:.beans.factory.annotation.Value;
    private String fileUploadPath;

    /**
     * 文件上传接口
     * @param file  前端传过来的文件
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public String upload(@RequestParam MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename(); //获取( 原始名称 )
        String type = FileUtil.extName(originalFilename); //获取( 文件类型 )    //注意FileUtil.extName是String
        long size = file.getSize(); //获取( 文件大小 )

        // 先存储到磁盘
        File uploadParentFile = new File(fileUploadPath);   //注意导报: File(java.io)
        // 判断配置的文件目录是否存在,若不存在则创建一个新的文件目录
        if(!uploadParentFile.exists()) {
            uploadParentFile.mkdirs();
        }
        // 定义一个文件唯一的标识位
        String uuid = IdUtil.fastSimpleUUID();
        File uploadFile = new File(fileUploadPath + uuid + StrUtil.DOT + type); //StrUtil.DOT( 文件名) + type( png )
        // 把前端获取到的文件存储到磁盘目录
        file.transferTo(uploadFile); //把文件传到磁盘上去   //注意(1)file.transferTo()是File dest  (2)transferTo报红Add一个异常数据就好了
        // 再存储到数据库
        return "";
    }
}

在这里插入图片描述
在这里插入图片描述

4.在entity包下创建File实体类(24:50)

注意:数据库中tinyint在实体类中就是Boolean类型

package com.SpringBoot.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * 数据库中 int 在实体类中就是 Integer
 * 数据库中 varchar 在实体类中就是 String
 * 数据库中 bigint 在实体类中就是 Long
 * 数据库中 tinyint 在实体类中就是 Boolean 类型
 */
@Data
@TableName("sys_file")
public class File {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private String type;
    private Long size;
    private String url;
    private Boolean isDelete;
    private Boolean enable;

}
5.在mapper文件中创建接口FileMapper.java(26:50)

在这里插入图片描述

在FileController中引用它
在这里插入图片描述

在FileController类的上传接口新增几行代码
在这里插入图片描述

再修改几行代码
在这里插入图片描述

5.在FileMapper.java中写一个下载download接口(),重新启动后台

在这里插入图片描述
打开postman测试一下

在这里插入图片描述
将地址复制到浏览器打开即可
在这里插入图片描述

6.重复的图片排除掉(44:02)

在FileController类文件中加上获取md5的代码在这里插入图片描述

在Navicat 15数据库图形化界面中新增md5字段,然后给is_delete、enable字段默认为0
注:暂时不给md5加索引
在这里插入图片描述

在Files实体类中加上私有变量md5
在这里插入图片描述
在FileController中写一个存一个md5的代码(如果文件中有相同的图片文件不上传图片)
在这里插入图片描述

在FileController文件接口下面写一个方法查询MD5文件getFileByMd5
声明:通过md5查询出多条记录
在这里插入图片描述

代码优化一下 //将存储到磁盘 和 //判断配置的文件下方的代码进行优化,优化后的代码

package com.SpringBoot.demo.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.SpringBoot.demo.entity.Files;
import com.SpringBoot.demo.mapper.FileMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;

/**
 * 文件上传相关接口
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Value("${files.upload.path}")  //将application.yml文件中的文件位置赋予到fileUploadPath    //注意导包:.beans.factory.annotation.Value;
    private String fileUploadPath;

    @Resource
    private FileMapper fileMapper;

    /**
     * 【1】文件上传接口
     * @param file  前端传过来的文件
     * @return
     * @throws IOException
     */
    @PostMapping("/upload")
    public String upload(@RequestParam MultipartFile file) throws IOException {
        String originalFilename = file.getOriginalFilename(); //获取( 原始名称 )
        String type = FileUtil.extName(originalFilename); //获取( 文件类型 )    //注意FileUtil.extName是String
        long size = file.getSize(); //获取( 文件大小 )

        // 1.定义一个文件唯一的标识位
        String uuid = IdUtil.fastSimpleUUID();
        String fileUUID = uuid + StrUtil.DOT + type;
        File uploadFile = new File(fileUploadPath + fileUUID); //StrUtil.DOT( 文件名) + type( png )

        // 2.判断配置的文件目录是否存在,若不存在则创建一个新的文件目录
        if(!uploadFile.getParentFile().exists()) {
            uploadFile.getParentFile().mkdirs();
        }

        // 3.获取文件的url
        String url;
        // 上传文件到磁盘
        file.transferTo(uploadFile);
        // 获取文件的md5
        String md5 = SecureUtil.md5(uploadFile);
        // 从数据库查询是否存在相同的记录
        Files dbFiles = getFileByMd5(md5);
        if (dbFiles != null) {
            url = dbFiles.getUrl();
            // 由于文件已存在,所以删除刚才上传的重复文件
            uploadFile.delete();
        } else {
            // 数据库若不存在重复文件,则不删除刚才上传的文件
            url = "http://localhost:8085/file/" + fileUUID;
        }


        // 4.再存储到数据库
        Files saveFile = new Files();
        saveFile.setName(originalFilename);
        saveFile.setType(type);
        saveFile.setSize(size/1024);//转换在数据库显示的图片大小为KB
        saveFile.setUrl(url);
        saveFile.setMd5(md5);
        fileMapper.insert(saveFile);
        return url;  //上传成功后返回url
    }

    /**
     * 【2】文件下载接口   http://localhost:8085/file/{fileUUID}
     * @param fileUUID
     * @param response
     * @throws IOException
     */
    @GetMapping("/{fileUUID}")
    public void download(@PathVariable String fileUUID, HttpServletResponse response) throws IOException{
        // 根据文件的唯一标识码获取文件
        File uploadFile = new File(fileUploadPath + fileUUID);

        // 设置输出流格式
        ServletOutputStream os = response.getOutputStream(); // 写出流
        response.addHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(fileUUID,"UTF-8"));
        response.setContentType("application/octet-stream");

        // 读取文件的字节流
        os.write(FileUtil.readBytes(uploadFile));
        os.flush();
        os.close();
    }

    /**
     * 【3】通过文件的MD5查询文件
     */
    private Files getFileByMd5(String md5) {
        // 查询文件的md5是否存在
        QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("md5",md5);
        List<Files> filesList = fileMapper.selectList(queryWrapper);
        return filesList.size() == 0 ? null : filesList.get(0);
    }
}


重新启动后台,然后打开postman测试一下,上传三次相同文件(不同重命名),我们可以看见files文件里面只有一张图片,数据库有三条记录
说明:三个不同命名,文件一致的图片在上传时,只保留一个
在这里插入图片描述

在这里插入图片描述

7.前端页面展示上传、下载功能(1:20:40)

新建File.vue组件

<template>
  <div>
    <!-- 1.2.2.(2)新增、删除、导入、导出按钮 -->
    <div style="margin:10px 0">
      <el-upload action="http://localhost:8085/file/upload" :show-file-list="false" :on-success="handleFileUploadSuccess" style="display:inline-block">
        <el-button type="primary">                                                      
          上传文件
          <i class="el-icon-top"></i>
        </el-button>
      </el-upload>
      <el-button type="danger" @click="delBatch" class="ml-5">
        批量删除
        <i class="el-icon-remove-outline"></i>
      </el-button>
      
    </div>

    <!-- 1.2.2.(3)表格 -->
    <el-table
      :data="tableData"
      border
      stripe
      :header-cell-class-name="headerBg"
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55"></el-table-column>
      <el-table-column prop="id" label="ID" width="50"></el-table-column>
      <el-table-column prop="name" label="文件名称"></el-table-column>
      <el-table-column prop="type" label="文件类型" width="120"></el-table-column>
      <el-table-column prop="size" label="文件大小KB" width="190"></el-table-column>
      <el-table-column label="下载" width="120">
        <template slot-scope="scope">
            <el-button type="primary" @click="download(scope.row.url)">下载</el-button>
        </template>
      </el-table-column>
      <el-table-column label="启用">
        <template slot-scope="scope">
            <el-switch v-model="scope.row.enable" active-color="#13ce66" inactive-color="#ccc" @change="changeEnable(scope.row)"></el-switch>
        </template>
      </el-table-column>
      <el-table-column prop="address" label="操作">
        <template slot-scope="scope">
          <el-popconfirm
            class="ml-10"
            confirm-button-text="确定"
            cancel-button-text="我再想想"
            icon="el-icon-info"
            icon-color="red"
            title="这是一段内容确定删除吗?"
            @confirm="del(scope.row.id)"
          >
            <el-button type="danger" slot="reference">
              删除
              <i class="el-icon-remove-outline"></i>
            </el-button>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>

    <!-- 1.2.2(4)分页查询 -->
    <div class="block ml-5">
      <!-- 
            current-page(当前页面)=pageNum(前端下面传过来的) 
            page-size(每页条数)=pagesize(前端下面传过来的)
            :total=total(后台传过来的)
            @size-change="handleSizeChange"
      -->   
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="pageNum"
        :page-sizes="[2, 5, 10, 20]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
      ></el-pagination>

    </div>
  </div>
</template>

<script>
export default {
    name: "File",
    data() {
        return {
            tableData:[],
            name:'',
            multipleSelection: [],
            pageNum: 1,
            pageSize: 10,
            total: 0,
            headerBg: "headerBg"
        }
    },
    created() {
      //执行create生命周期函数之前,我们已经拿到了data数据,和methods方法了
      this.load();
    },
    methods: {
      load() {
        this.$http.get("/file/page", {
            params: {
              pageNum: this.pageNum,
              pageSize: this.pageSize,
              name: this.name
            }
          
          }).then(res => {
              // console.log(res.data);
              this.tableData = res.data.records;
              this.total = res.data.total;
          })
      },
      changeEnable(row) {
        this.$http.post("/file/update",row).then(res =>{
          if(res.code === '200'){
            this.$message.success("操作成功")
          }
        })
      },
      // 【重置】
      reset() {
        this.username = "";
        this.email = "";
        this.address = "";
        this.load();
      },
      // 【删除】
      del(id) {
        this.$http.delete("/file/" + id).then(res =>{
          if (res.code === '200') {
            console.log(res.code)
            this.$message.success("删除成功!");
            // if (id) {
            //   //如果当前页面没有数据,跳转到上一页 (暂时写不到)
            // }
            this.load(); //重新加载页面
          } else {
            this.$message.error("删除失败!");
          }
        })
      },
      // 【表格绑定好的】
      handleSelectionChange(val) {
        console.log(val);
        this.multipleSelection = val;
      },
      // 【批量删除】 
      delBatch() {
        let ids = this.multipleSelection.map(v => v.id); // [{}, {}, {}] => [1, 2, 3]     对象数组 变为 纯id数组
        this.$http.post("/file/del/batch", ids).then(res => {
          if (ids.length !== 0) {
            console.log(ids);
            this.$confirm("是否确认删除这" + ids.length + "条数据?", {
              confirmButtonText: "确定",
              cancelButtonText: "取消",
              type: "warning"
            })
              .then(() => {
                return batchDelete(rows);
              })
              .then(() => {
                this.created();
                this.msgSuccess("删除成功");
              })
          }
          if (res.code === '200') {
            this.$message.success("批量删除成功!");
            this.load(); //重新加载页面
          } else {
            this.$message.error("未选择数据!");
          }
        });
      },
      // 【分页】 - val当前页码 
      handleSizeChange(val) {
        console.log(val);
        this.pageSize = val;
        this.load();
      },
      // 【分页】 - 当前为val条数据每页
      handleCurrentChange(val) {
        console.log(val);
        this.pageNum = val;
        this.load();
      },
      // 【上传成功】 回调函数  
      handleFileUploadSuccess(res) {
          console.log(res)
          this.load()
      },
      // 【下载】
      download(url) {
        window.open(url)
      }
    }

}
</script>

<style>

</style>
8.SpringBoot写接口(1:30:20)

在FileController中,写一个删除接口

    /**
     * 【7】分页查询接口
     */
    //(5.2)第二种方式( mybatis-plus 调用userService中接口的方法)
    @GetMapping("/page")    //接口路径,/file/page  (defaultValue = "")//这里是默认为空查询不会报错
    //三个queryWrapper.like,表示三个条件同时查询(而且默认为空查询不会失败)
    public Result findPage(@RequestParam Integer pageNum,
                           @RequestParam Integer pageSize,
                           @RequestParam(defaultValue = "") String name) {
        QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
        // 查询未删除的记录
        queryWrapper.eq("is_delete",false);//0代表:false  1代表:true
        queryWrapper.orderByDesc("id");
        if(!"".equals(name)) {
            queryWrapper.like("name",name);
        }
        return Result.success(fileMapper.selectPage(new Page<>(pageNum,pageSize),queryWrapper));

    }

再写一个更新接口

	/**
     * 【4】新增或更新接口
     */
    @PostMapping("/update")
    public Result update(@RequestBody Files files) {
        return Result.success(fileMapper.updateById(files));
    }

再写一个删除接口

	/**
     * 【5】删除接口
     */
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
        Files files = fileMapper.selectById(id);
        files.setIsDelete(true);
        fileMapper.updateById(files);
        return Result.success();
    }

写一个批量删除接口

	/**
     * 【6】批量删除接口
     */
    @PostMapping("/del/batch")
    public Result deleteBatch(@RequestBody List<Integer> ids) {
        // select * from sys_file where id in(id,id,id...)
        QueryWrapper<Files> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id",ids);
        List<Files> files = fileMapper.selectList(queryWrapper);
        for (Files file : files) {
            file.setIsDelete(true);
            fileMapper.updateById(file);
        }
        return Result.success();
    }

9.在Aside.vue中,新增一个文件管理
在这里插入图片描述
10.在vue界面测试上传文件的接口
在这里插入图片描述
11.个人用户头像上传
在Person.vue组件中写头像的标签

<template>
  <el-card style="width: 400px; padding: 20px; ">
    <el-form label-width="80px" size="big">
      <el-upload
        class="avatar-uploader"
        action="http://localhost:8085/file/upload"
        :show-file-list="false"
        :on-success="handleAvatarSuccess"
      >
        <img v-if="form.avatarUrl" :src="form.avatarUrl" class="avatar" />
        <i v-else class="el-icon-plus avatar-uploader-icon"></i>
      </el-upload>

      <el-form-item label="用户名" :label-width="formLabelWidth">
        <el-input v-model="form.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="昵称" :label-width="formLabelWidth">
        <el-input v-model="form.nickname" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="邮箱" :label-width="formLabelWidth">
        <el-input v-model="form.email" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="电话" :label-width="formLabelWidth">
        <el-input v-model="form.phone" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="地址" :label-width="formLabelWidth">
        <el-input v-model="form.address" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="save">确定</el-button>
      </el-form-item>
    </el-form>
  </el-card>
</template>
<script>
export default {
  name: "Person",
  data() {
    return {
      formLabelWidth: "",
      form: {},
      user: localStorage.getItem("user")
        ? JSON.parse(localStorage.getItem("user"))
        : {}
    };
  },
  created() {
    this.$http.get("/user/username/" + this.user.username).then(res => {
      if (res.code === "200") {
        this.form = res.data;
      }
    });
  },
  methods: {
    save() {
      this.$http.post("/user", this.form).then(res => {
        if (res.data) {
          this.$message.success("保存成功!");
          this.dialogFormVisible = false; //添加成功后,关闭对话框
          this.load; //重新加载页面,始终抱持前端页面能看见( 新增 和 修改后 )
        } else {
          this.$message.error("保存失败!");
        }
      });
    },
    handleAvatarSuccess(res) {
      this.form.avatarUrl = res
    }
  }
};
</script>

<style>
  .avatar-uploader {
    text-align: center;
    padding-bottom: 10px;
  }
  .avatar-uploader .el-upload {
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
  }
  .avatar-uploader .el-upload:hover {
    border-color: #409EFF;
  }
  .avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 138px;
    height: 138px;
    line-height: 178px;
    text-align: center;
  }
  .avatar {
    width: 138px;
    height: 138px;
    display: block;
  }
</style>
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中实现文件上传非常简单。首先,你需要在项目的依赖中添加spring-boot-starter-web。然后,你可以通过配置文件来对文件上传进行一些基本的配置。例如,你可以设置是否开启文件上传支持、文件写入磁盘的阈值、上传文件的临时保存位置、上传的单个文件的最大大小以及多文件上传时文件的总大小等。\[1\] 对于单文件上传,你可以创建一个HTML表单,使用enctype="multipart/form-data"来指定表单的编码类型,并使用<input type="file">来选择文件。然后,你可以在后端编写一个处理文件上传的接口,通过@RequestParam注解来获取上传的文件。\[2\] 对于多文件上传,你可以创建一个HTML表单,使用相同的方式来选择多个文件。然后,你可以在后端编写一个处理多文件上传的接口,通过@RequestParam注解来获取上传的文件列表。\[2\] 在Spring Boot中,如果你没有提供MultipartResolver,那么默认采用的MultipartResolver就是StandardServletMultipartResolver。因此,你甚至可以实现零配置的文件上传。\[3\] 总结起来,Spring Boot提供了简单而强大的功能来实现文件上传,你只需要添加依赖、进行一些基本的配置,然后在后端编写相应的接口即可实现文件上传功能。 #### 引用[.reference_title] - *1* *2* *3* [SpringBoot文件上传](https://blog.csdn.net/qq_43581790/article/details/123811775)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值