项目中的文件上传与下载

继续丰富我们的项目功能。
这次是文件上传的功能,而在我们的element官网上是有文件上传的组件功能的,我们直接拿来用就行。
在这里插入图片描述
老方法,粘贴复制。但是上图的服务器接口(也就是上传下载的路由地址)我们需要自己去写,所以现在去后端服务器上写一个接口,FileController:
在这里插入图片描述
同时我们在resources文件下创建一个files文件夹,这个文件夹就是用来存放从前台上传过来的文件的。
在这里插入图片描述

因为写入文件的时候要使用IO流,但是java的原生io过于繁琐和麻烦,所以我们使用hutool这个工具,在pom中引入:

		<dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.3</version>
        </dependency>

现在写接口中的逻辑:

package com.why.demo.controller;

import cn.hutool.core.io.FileUtil;
import com.why.demo.common.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

//文件上传接口
@RestController
@RequestMapping("/files")
public class FileController {

    @PostMapping("/upload")
    public Result<?> upload(MultipartFile file) throws IOException {
//        MultipartFile用来接收前台传送过来的文件对象
//        获取文件名称
        String originalFilename = file.getOriginalFilename();

        //获取我们的文件要存储的位置(即resources下的files位置)
        String rootFilePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"+originalFilename;

        //现在要将这个文件写入进我们的文件夹中,用hutool工具实现,io过于繁琐了不用
//        file.getBytes()得到文件的字节流,rootFilePath:要写入文件的位置
        FileUtil.writeBytes(file.getBytes(),rootFilePath);
        return Result.success();
    }

}

然后打开postman测试一下:
在这里插入图片描述
千万要记得图中的红色圈圈圈起来的都要记得点的和我一模一样,其中的那个圈内没有内容的选的是“file”类型,尤其尤其要记得点哪个黑色勾勾,否则就会报空指针异常。
在这里插入图片描述
可以看到已经上传上来了。
但是现在有个问题是,我们如果上传了相同名字的文件,那么新上传的文件则会覆盖掉我们之前的同名文件,这不合理,我们需要解决。
解决的办法就是在每一个上传上来的文件前面加前缀,不加后缀是因为文件名本来就有后缀了,现在代码如下:

package com.why.demo.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.why.demo.common.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

//文件上传接口
@RestController
@RequestMapping("/files")
public class FileController {

    @PostMapping("/upload")
    public Result<?> upload(MultipartFile file) throws IOException {
//        MultipartFile用来接收前台传送过来的文件对象
//        获取文件名称
        String originalFilename = file.getOriginalFilename();

//        定义文件的唯一标识去(前缀),hutool的这个工具不会生成重复的字符串
        String flag = IdUtil.fastSimpleUUID();

        //获取我们的文件要存储的位置(即resources下的files位置)
        String rootFilePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"+flag+"_"+originalFilename;

        //现在要将这个文件写入进我们的文件夹中,用hutool工具实现,io过于繁琐了不用
//        file.getBytes()得到文件的字节流,rootFilePath:要写入文件的位置
        FileUtil.writeBytes(file.getBytes(),rootFilePath);
        return Result.success(); //返回结果url
    }

}

再次测试:
在这里插入图片描述
可以看到新上传的文件就拥有了一个只属于自己的前缀。
然后我们再实现一下文件下载的接口:

//    文件下载
    @GetMapping("/{flag}")
    public void getFiles (@PathVariable String flag, HttpServletResponse response){
        OutputStream os; //新建一个输出流对象
        String basePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"; //定义文件上传的根路径
        List<String> fileNames = FileUtil.listFileNames(basePath); //获取该根路径下的所有的文件名称
        //找到跟参数一致的文件
        String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse("");
        try{
            if(StrUtil.isNotEmpty(fileName)){
                response.addHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(fileName,"UTF-8"));
                response.setContentType("application/octet-stream");
                //通过文件路径读取文件的字节流
                byte[] bytes = FileUtil.readBytes(basePath+fileName);
                os = response.getOutputStream(); //通过输出流返回文件
                os.write(bytes);
                os.flush();
                os.close();
            }
        } catch (Exception e){
            System.out.println("文件下载失败!");
        }

    }

这个控制器还有些部分要完善,所以我直接贴源码:

package com.why.demo.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.why.demo.common.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.List;

//文件上传接口
@RestController
@RequestMapping("/files")
public class FileController {

    @Value("${server.port}")
    private String port;

    private static final String ip = "http://localhost";

//    文件上传
    @PostMapping("/upload")
    public Result<?> upload(MultipartFile file) throws IOException {
//        MultipartFile用来接收前台传送过来的文件对象
//        获取文件名称
        String originalFilename = file.getOriginalFilename();

//        定义文件的唯一标识去(前缀),hutool的这个工具不会生成重复的字符串
        String flag = IdUtil.fastSimpleUUID();

        //获取我们的文件要存储的位置(即resources下的files位置)
        String rootFilePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"+flag+"_"+originalFilename;

        //现在要将这个文件写入进我们的文件夹中,用hutool工具实现,io过于繁琐了不用
//        file.getBytes()得到文件的字节流,rootFilePath:要写入文件的位置
        FileUtil.writeBytes(file.getBytes(),rootFilePath);
        return Result.success(ip+":"+port+"/files/"+flag); //返回结果url
    }


//    文件下载
    @GetMapping("/{flag}")
    public void getFiles (@PathVariable String flag, HttpServletResponse response){
        OutputStream os; //新建一个输出流对象
        String basePath = System.getProperty("user.dir")+"/springboot/src/main/resources/files/"; //定义文件上传的根路径
        List<String> fileNames = FileUtil.listFileNames(basePath); //获取该根路径下的所有的文件名称
        //找到跟参数一致的文件
        String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse("");
        try{
            if(StrUtil.isNotEmpty(fileName)){
                response.addHeader("Content-Disposition","attachment;filename="+URLEncoder.encode(fileName,"UTF-8"));
                response.setContentType("application/octet-stream");
                //通过文件路径读取文件的字节流
                byte[] bytes = FileUtil.readBytes(basePath+fileName);
                os = response.getOutputStream(); //通过输出流返回文件
                os.write(bytes);
                os.flush();
                os.close();
            }
        } catch (Exception e){
            System.out.println("文件下载失败!");
        }

    }

}

现在用过postman测试上传文件功能:
在这里插入图片描述
可以看见已经上传上去了:
在这里插入图片描述
此时会上传文件的方法会返回一个url,而这个url正是我们下载文件的路径;复制postman上返回的那串url,在浏览器地址栏输入即可下载我们刚刚上传的文件:
在这里插入图片描述
可以看见左下角已经在下载了。
然后我们去写前端,前面已经提到了,element已经提供了组件,我们需要写的只是接口路由,所以很简单。假如我们现在想给我们的Book菜单加一个上传或者下载图片封面的功能,那么我们现在就先去给数据库中的book表加一个封面的字段:
在这里插入图片描述

把下面这一段放入Book.vue组件内的封面中:

在这里插入图片描述

在这里插入图片描述
有些东西我们不需要,可以删掉,如下:
在这里插入图片描述
现在访问点击编辑:
在这里插入图片描述
可以上传东西了,但是我们此时缺少东西去接收从后台返回回来的文件下载的路由,但是element依然给我们提供好了这样的组件。
在这里插入图片描述
文件上传时有一个“on-success”的钩子,所以我们可以在上传按钮中加一个方法:
在这里插入图片描述
methods中定义一下,先让它输出打印一下:
在这里插入图片描述
然后访问点击上传会报跨域错误,我也不懂为啥,但是up主说后台再加个跨域就可以解决了:
在这里插入图片描述
后台解决跨域就在common配置包下加个配置类CorsConfig:

package com.why.demo.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    //当前跨域请求最大有效时长,这里默认一天
    private static final long MX_AGE = 24 * 60 * 60;

    private CorsConfiguration buildConfig(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*"); //1、设置访问源地址
        corsConfiguration.addAllowedHeader("*"); //2、设置访问源请求头
        corsConfiguration.addAllowedMethod("*"); //3、设置访问源请求方法
        corsConfiguration.setMaxAge(MX_AGE);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",buildConfig()); //4、对接口配置跨域设置
        return new CorsFilter(source);
    }
}

不知道为啥配置了这一段之后就可以访问了:
在这里插入图片描述
可以看到现在访问就没有问题了。
可以看见我们可以拿到路由的值了,那么现在我们只需要将这段路由值传到后台去就可以了。
在这里插入图片描述

不要忘了去给实体类加上我们刚刚在数据库中加的字段:
在这里插入图片描述
现在我们访问上传文件:
在这里插入图片描述
可以看见数据库中已经出现了下载的链接。要注意的一点是,在idea中上传的文件不能大于1M,否则上传会失败。
怎么显示出来呢让这图片,element依然给我们提供了一个展示图片的组件:
在这里插入图片描述
copy下来在Book.vue中进行操作,直接贴源码了:

<template>
    <!--padding设置一点内边距-->
    <div style="padding: 10px">
        <!--功能区域-->
        <div style="margin: 10px 0">
            <el-button type="primary" @click="add">新增</el-button>
            <el-button type="primary">导入</el-button>
            <el-button type="primary">导出</el-button>
        </div>

        <!--搜索区域-->
        <div style="margin: 10px 0">
            <el-input v-model="search" placeholder="请输入关键字" style="width: 20%" clearable/>
            <el-button type="primary" style="margin-left: 5px" @click="load">搜索</el-button>
        </div>


        <!--后面的width可以不写,浏览器会自适应
        stripe是斑马纹效果-->
        <el-table :data="tableData" border stripe style="width: 100%">
            <!--sortable让日期排序-->
            <el-table-column prop="id" label="ID" sortable/>
            <el-table-column prop="name" label="书名"/>
            <el-table-column prop="price" label="单价" />
            <el-table-column prop="author" label="作者"/>
            <el-table-column prop="createTime" label="出版日期"/>
            <el-table-column label="封面">
                    <template #default="scope">
                        <el-image
                                style="width: 100px; height: 100px"
                                :src="scope.row.cover"
                                :preview-src-list="[scope.row.cover]">
                        </el-image>
                    </template>
            </el-table-column>

            <el-table-column fixed="right" label="操作">
                <template #default="scope">
                    <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>

                    <!--弹出消息确认框-->
                    <el-popconfirm
                            confirm-button-text="是的,没错"
                            cancel-button-text="妈的,我再想想"
                            icon="el-icon-info"
                            icon-color="red"
                            title="确定删除吗?" @confirm="handleDelete(scope.row.id)">
                        <template #reference>
                            <el-button size="mini" type="danger">删除</el-button>
                        </template>
                    </el-popconfirm>
                </template>
            </el-table-column>
        </el-table>

        <!--分页功能区域-->
        <div style="margin: 10px 0">
            <el-pagination
                    v-model:currentPage="currentPage4"
                    :page-sizes="[5,10,20]"
                    :page-size="pageSize"
                    layout="total, sizes, prev, pager, next, jumper"
                    :total="total"
                    @size-change="handleSizeChange"
                    @current-change="handleCurrentChange">
            </el-pagination>
        </div>

        <!--弹窗输入用户信息-->
        <el-dialog v-model="dialogVisible" title="提示" width="30%">
            <!--定义表单在这个弹出框内以收集新增的用户信息-->
            <el-form :model="form" label="用户名">
                <el-form-item label="书名">
                    <el-input v-model="form.name" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="价格">
                    <el-input v-model="form.price" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="作者">
                    <el-input v-model="form.author" style="width: 80%"></el-input>
                </el-form-item>
                <el-form-item label="出版时间">
                    <el-date-picker v-model="form.createTime" value-format="YYYY-MM-DD" time="date" style="width: 80%" clearable></el-date-picker>
                </el-form-item>
                <el-form-item label="封面">
                    <!--文件上传器-->
                    <el-upload ref="upload" action="http://localhost:9090/files/upload" :on-success="fileUploadSuccess">
                        <el-button type="primary">点击上传</el-button>
                    </el-upload>
                </el-form-item>

            </el-form>
            <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="save">确 定</el-button>
      </span>
            </template>
        </el-dialog>
    </div>
</template>

<script>


    import request from "../utils/request";

    export default {
        name: 'Book',
        components: {

        },
        data() {
            return {
                form: {},
                dialogVisible: false,
                search: '',
                //当前页
                currentPage4: 1,
                pageSize: 10,
                total: 0,
                tableData: [

                ],
            }
        },
        //表示在页面被加载的时候,在这个方法里面的方法就全部调用
        created() {
            this.load()
        },
        methods: {
            fileUploadSuccess(res){
                console.log(res)
                this.form.cover = res.data
            },
            //查询方法
            load(){
                request.get("/book",{
                    params: {
                        pageNum: this.currentPage4,
                        pageSize: this.pageSize,
                        search: this.search
                    }
                }).then(res => {
                    console.log(res)
                    this.tableData = res.data.records
                    this.total = res.data.total
                })
            },
            add(){
                this.dialogVisible = true;
                //清空表单域
                this.form = {};
                this.$refs['upload'].clearFiles() //清楚历史文件列表
            },
            save(){
                if(this.form.id){  //更新操作
                    request.put("/book",this.form).then(res => {
                        console.log(res)
                        if(res.code == '0'){
                            //element给我们提供了一个可以供显示的结果的东西,如下
                            this.$messageBox({
                                type: "success",
                                message: "更新成功"
                            })
                        }else{
                            //element给我们提供了一个可以供显示的结果的东西,如下
                            this.$messageBox({
                                type: "error",
                                message: res.msg
                            })
                        }
                    })
                    this.load() //每一次更新后都自动刷新表格
                    this.dialogVisible = false //关闭弹窗
                }else{ //新增操作
                    request.post("/book",this.form).then(res => {
                            console.log(res)
                            if (res.code == '0') {
                                //element给我们提供了一个可以供显示的结果的东西,如下
                                this.$messageBox({
                                    type: "success",
                                    message: "新增成功"
                                })
                            } else {
                                //element给我们提供了一个可以供显示的结果的东西,如下
                                this.$messageBox({
                                    type: "error",
                                    message: res.msg
                                })
                            }
                            this.load() //每次新增完都自动刷新表格
                            this.dialogVisible = false //关闭弹窗
                        }
                    )}
            },
            handleEdit(row){
                this.form = JSON.parse(JSON.stringify(row))
                this.dialogVisible = true
                this.$nextTick(()=>{
                    this.$refs['upload'].clearFiles() //清楚历史上传记录
                })
            },
            handleDelete(id){
                console.log(id)
                request.delete("/book/"+id).then(res =>{

                    if (res.code == '0') {
                        //element给我们提供了一个可以供显示的结果的东西,如下
                        this.$messageBox({
                            type: "success",
                            message: "删除成功"
                        })
                    } else {
                        //element给我们提供了一个可以供显示的结果的东西,如下
                        this.$messageBox({
                            type: "error",
                            message: res.msg
                        })
                    }
                    this.load() //删除之后重新加载表格的数据
                })
            },
            handleSizeChange(pageSize){
                //    改变当前每页的数据条数时触发
                this.pageSize = pageSize
                this.load()
            },
            handleCurrentChange(pageNum){
                //  改变当前页码时触发
                this.currentPage4 = pageNum
                this.load()
            }
        }
    }
</script>

访问可以看见图片已经出现:
在这里插入图片描述
至此文件的上传下载也结束啦!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在地球迷路的怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值