上传和下载走的那段弯路

本文记录了作者在实现文件上传和下载功能过程中遇到的问题及解决方法。首先,简述了上传和下载的基本原理,然后分享了前端使用el-upload组件时遇到的第一段弯路,即通过截取文件名作为版本号来关联数据库,导致可能出现的错误。接着,介绍了尝试使用H5 input[type=file]标签实现文件和表单同时提交,但限制于只能上传单个文件。最后,揭示了正确的上传下载实现方式,通过前端关闭自动提交,利用http-request自定义请求,实现文件的批量上传,同时提供了前后端的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

前言

  • 上传:把文件上传到服务器,服务器ip+端口+请求路径;
  • 下载:从服务器下载文件,服务器ip+端口+请求路径;
  • springboot中配置数据库连接的用户名和密码是服务器数据库的用户名和密码;
  • 前端不要配置axios请求的baseurl,那是开发时候用的,打包项目必须注释;
  • vue的项目打包后dist下的文件放在springboot项目中的static下,package后,直接访问服务器ip+端口,就能访问项目,此时浏览器中会自动在每个请求前加上该服务器的本机ip地址;因此其他客户端也就能直接访问服务器ip+端口访问服务器上部署的项目;

一、第一段弯路(简单记录前端代码)

主要原因是不知道el-upload如何将文件和表单同时提交。功能虽然实现,但有点耍流氓

  • 先添加form表单数据;
  • 再单独上传文件(在文件上传时将文件名截取去掉后缀作为另一个表的version,为了建立两表的联系,根据version下载文件);
  • 为了能够下载文件,必须将文件命名为跟版本号相同,因为下载是根据版本号下载的; 数据库中使用了两张表;
  • 使用这种流氓方法,表中的版本号如果重复,下载的时候一定报错,sql查出来多条下载路径,因此需要在业务层判断上传的该版本是否已存在,有则先删除,否则直接插入数据。

嗯,真流氓!

   <el-button type="primary" size="mini" @click="openUpload()">
            <i class="el-icon-upload"></i>
            <span>上传apk</span>
   </el-button>
   // 打开文件上传的窗口
    openUpload() {
      this.$refs["Upload"].UploadVisible = true;
    },

el-dialog:
:action="url" ( let url = "upload/uploadInfo";)
:auto-upload="true"
:data="param"
:file-list="fileList"
multiple
accept=".apk,.png,.jpg,.xlsx"
:beforeUpload="beforeAvatarUpload"

  <el-dialog
      title="Apk上传"
      :visible.sync="UploadVisible"
      width="40%"
      size="samll"
    >
      <div>
        <el-upload
          style="margin-left: 15%"
          class="upload-demo"
          :action="url"
          :on-preview="handlePreview"
          :on-remove="handleRemove"
          :on-change="changeFile"
          :on-success="onSuccess"
          :onError="onError"
          :data="param"
          :file-list="fileList"
          :beforeUpload="beforeAvatarUpload"
          accept=".apk,.png,.jpg,.xlsx"
          :auto-upload="true"
          :drag="true"
          multiple
        >
          <el-button
            slot="trigger"
            size="small"
            type="primary"
            style="margin-top: 70px"
          >
            选取文件
          </el-button>
        </el-upload>
        <div slot="tip" class="el-upload__tip" style="margin-top: 20px">
          点击或拖拽上传apk类型的文件,<b
            >文件命名必须和列表安卓版本号相同,否则无法下载对应版本apk</b
          >
        </div>
      </div>

      <span slot="footer" class="dialog-footer">
        <el-button
          type="primary"
          @click="UploadVisible = false"
          size="small"
          style="margin-right: 20px"
          >关闭</el-button
        >
      </span>
    </el-dialog>
 beforeAvatarUpload(file) {
      this.param.version = this.splitFileName(file.name);
      // 文件名
      this.param.fileName = file.name;
      // 文件大小
      //   this.param.fileSize = Math.round(file.size / 1024) + "KB";
      this.param.fileSize = file.size;
      // 文件url
      this.param.url = this.url + "/" + this.splitFileName(file.name);
      //文件上传人
      this.param.uploadPerson = window.localStorage.getItem("WelcomeName");

      if (file.size > this.limitSize) {
        this.$message({
          message: "文件超出大小限制",
          type: "warning",
        });
        return false;
      }
      return true;
    },
    //去掉文件后缀
    splitFileName(text) { 
      var pattern = /\.{1}[a-z]{1,}$/;
      if (pattern.exec(text) !== null) {
        return text.slice(0, pattern.exec(text).index);
      } else {
        return text;
      }
    },

二、第二段弯路(简单记录前端代码)

后来在csdn里面看到一段关于文件和表单同时提交的案例,只不过前端用的是H5的input标签,设置类型type=file。后来使用这种方法确实解放了用户,不限制文件名,也使用了一张数据库表,但是这种原生的标签,缺点在于每次只能上传一个文件,如果想同时上传多个文件,必须自己写代码处理。
关键:let formData = new FormData();

  <input
            id="upLoad"
            v-if="this.option === 'add'"
            accept=".xls,.xlsx,.apk,.png,.PNG,.jpg,.JPG,.doc,.docx,.pdf,.txt"
            type="file"
            name="file"
            @change="getFile($event)"
            style="margin-left: 90px; margin-bottom: 10px"
   />
 //获取上传文件
    getFile(event) {
      this.file = event.target.files[0];
    },
  // 添加版本信息
          event.preventDefault(); //取消默认行为
          let formData = new FormData();
          formData.append("apkName", this.versionForm.apkName);
          formData.append("version", this.versionForm.version);
          formData.append("update_type", this.versionForm.update_type);
          formData.append("update_info", this.versionForm.update_info);
          formData.append("update_person", this.versionForm.update_person);
          formData.append("fit_mainBoard", this.versionForm.fit_mainBoard);
          formData.append("fw_version", this.versionForm.fw_version);
          formData.append("file", this.file);

          let res = await this.$http.post("version/addVersion", formData);
          if (res.status === 200 && res.data.result === "success") {
            this.$message.success("添加成功");
            //调用父组件函数,刷新列表
            this.$parent.getVersionList();
            this.$parent.getTotal(); //新增后更新total
            this.VersionFormVisible = false;
            var obj = document.getElementById("upLoad");
            obj.value = "";
          } else {
            this.$message.error("添加失败,请稍后重试");
          }

三、正确打开方式(全部代码)

来了,它来了,它真的来了,它带着帅气走来了,他就是vue
前端关闭自动提交,不要绑定action到url;
通过:http-request自定义请求。

    ref="upload"
    action="string"
    :data="param"
    :file-list="fileList"
    :beforeUpload="beforeAvatarUpload"
    :http-request="handleUpload"
    accept=".apk,.png,.jpg,.xlsx"
    :auto-upload="false"

在提交的时候

   // 添加版本信息
          this.$refs.upload.submit();

submit方法就会调用http-request绑定的自定义方法handleUpload(param),注意这里的参数一定是param,上传的文件就是param.file

值得一提的是:绑定到一个数组:file-list="fileList",就可以提交一个或多个文件,不需要做任何处理。

好了,下面正式开始:

上传前端

<template>
  <div>
    <el-dialog :title="title" width="40%" :visible.sync="VersionFormVisible">
      <el-form
        :model="versionForm"
        ref="versionFormRef"
        :rules="versionFormRules"
        label-width="90px"
      >
        <el-form-item label="软件名称" prop="apkName">
          <el-input v-model="versionForm.apkName"></el-input>
        </el-form-item>

        <el-form-item label="版本号" prop="version">
          <el-input v-model="versionForm.version"></el-input>
        </el-form-item>

        <el-form-item label="更新人" prop="update_person">
          <el-input v-model="versionForm.update_person"></el-input>
        </el-form-item>

        <el-form-item label="适配主板" prop="fit_mainBoard">
          <el-input v-model="versionForm.fit_mainBoard"></el-input>
        </el-form-item>

        <el-form-item label="固件版本" prop="fw_version">
          <el-input v-model="versionForm.fw_version"></el-input>
        </el-form-item>

        <el-form-item label="更新类型" prop="update_type">
          <el-select
            v-model="versionForm.update_type"
            placeholder="请选择更新类型"
          >
            <el-option label="一般更新" value="一般更新"></el-option>
            <el-option label="中级更新" value="中级更新"></el-option>
            <el-option label="严重BUG更新" value="严重BUG更新"></el-option>
          </el-select>
        </el-form-item>

        <el-form-item label="文件上传" v-if="this.option === 'add'">
          <!--v-if="this.option==='add'" 只在添加版本信息的时候出现文件上传按钮 -->
          <el-upload
            ref="upload"
            action="string"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :on-change="changeFile"
            :on-success="onSuccess"
            :onError="onError"
            :data="param"
            :file-list="fileList"
            :beforeUpload="beforeAvatarUpload"
            :http-request="handleUpload"
            accept=".apk,.png,.jpg,.xlsx"
            :auto-upload="false"
          >
            <el-button slot="trigger" size="small" type="primary"
              >选取文件</el-button
            >
            <span slot="tip" class="el-upload__tip" style="margin-left: 10px"
              ><i>请上传文件(支持单文件和多文件上传),否则无法提交!</i></span
            >
          </el-upload>
        </el-form-item>

        <el-form-item label="更新描述" prop="update_info">
          <textarea
            v-model="versionForm.update_info"
            style="
              min-height: 180px;
              font-size: 14px;
              min-width: 410px;
              margin-top:10px;
            "
          ></textarea>
        </el-form-item>
      </el-form>

      <div
        slot="footer"
        class="dialog-footer"
        style="margin-top: -50px; margin-right: 25px"
      >
        <el-button @click="reset()" size="mini">取 消</el-button>
        <el-button type="primary" size="mini" @click="submit()"
          >确 定</el-button
        >
      </div>
    </el-dialog>
  </div>
</template>

<script>
export default {
  name: "VersionForm",
  data() {
    return {
      title: "",
      VersionFormVisible: false,
      versionForm: {},
      versionFormRules: {
        apkName: [
          { required: true, message: "请输入软件名称", trigger: "blur" },
        ],
        version: [{ required: true, message: "请输入版本号", trigger: "blur" }],
        update_type: [
          { required: true, message: "请选择更新类型", trigger: "blur" },
        ],
        update_info: [
          { required: true, message: "请输入更新描述信息", trigger: "blur" },
        ],
        update_person: [
          { required: true, message: "请输入更新人名称", trigger: "blur" },
        ],
        fit_mainBoard: [
          { required: true, message: "请输入适配主板", trigger: "blur" },
        ],
        fw_version: [
          { required: true, message: "请输入固件版本", trigger: "blur" },
        ],
      },
      option: "",
      fileList: [],
      limitSize: 1024 * 1 * 1024000,
      param: {},
    };
  },
  methods: {
    reset() {
      this.VersionFormVisible = false;
      this.$refs.versionFormRef.resetFields();
      this.fileList=[];
    },
    editInitData(row) {
      this.title = "编辑版本信息";
      this.option = "edit";
      this.versionForm = {
        id: row.id,
        apkName: row.apkName,
        version: row.version,
        update_type: row.update_type,
        update_info: row.update_info,
        update_person: row.update_person,
        fit_mainBoard: row.fit_mainBoard,
        fw_version: row.fw_version,
      };
    },
    addInitData() {
      this.title = "添加版本信息";
      this.option = "add";
      this.versionForm = {
        apkName: "",
        version: "",
        update_type: "",
        update_info: "",
        update_person: "",
      };
    },

    //发起编辑和添加请求
    submit() {
      this.$refs.versionFormRef.validate(async (valid) => {
        if (!valid) return;
        if (this.option === "edit") {
          // 编辑版本信息
          let param = {
            id: this.versionForm.id,
            apkName: this.versionForm.apkName,
            version: this.versionForm.version,
            update_type: this.versionForm.update_type,
            update_info: this.versionForm.update_info,
            update_person: this.versionForm.update_person,
            fit_mainBoard: this.versionForm.fit_mainBoard,
            fw_version: this.versionForm.fw_version,
          };
          let res = await this.$http.post(
            "version/editVersion",
            this.$qs.stringify(param)
          );
          if (res.status === 200 && res.data.result === "success") {
            this.$message.success("编辑成功");
            //调用父组件函数,刷新列表
            this.$parent.getVersionList();
            this.VersionFormVisible = false;
          } else {
            this.$message.error("编辑失败,请稍后重试");
          }
        } else {
          // 添加版本信息
          this.$refs.upload.submit();
        }
      });
    },
    handleRemove() {},

    handlePreview() {},

    changeFile() {},

    onSuccess(response, file) {
      this.$message({
        message: `${file.name} 上传成功`,
        type: "success",
        duration:1500
      });
    },

    onError(response, file) {
      this.$message({
        message: `${file.name} 上传失败`,
        type: "warning"
      });
    },
    beforeAvatarUpload(file) {
      if (file.size > this.limitSize) {
        this.$message({
          message: "文件超出大小限制",
          type: "warning",
        });
        return false;
      }
      return true;
    },
    // 注意:param
    async handleUpload(param) {
      this.VersionFormVisible = false;
      this.$message({
        type: "success",
        message: `【${param.file.name}】`+"已经开始上传",
        duration: 1500,
      });
      let formData = new FormData();
      formData.append("apkName", this.versionForm.apkName);
      formData.append("version", this.versionForm.version);
      formData.append("update_type", this.versionForm.update_type);
      formData.append("update_info", this.versionForm.update_info);
      formData.append("update_person", this.versionForm.update_person);
      formData.append("fit_mainBoard", this.versionForm.fit_mainBoard);
      formData.append("fw_version", this.versionForm.fw_version);
      formData.append("file", param.file);
      let res = await this.$http.post("version/addVersion", formData);
      if (res.status === 200 && res.data.result === "success") {
        this.$message({
          type: "success",
          message: `【${this.versionForm.apkName}】`+"版本信息添加成功",
          duration: 3000,
        });
        //调用父组件函数,刷新列表
        this.$parent.getVersionList();
        this.$parent.getTotal(); //新增后更新total
        this.fileList = [];
      } else {
        this.$message.error("版本信息添加失败,请稍后重试");
      }
    },
  },
};
</script>

<style lang="less" scoped>
.el-form-item {
  margin-right: 20px;
}
</style>

上传后端

   //增加版本信息
    @RequestMapping(value = "/addVersion",method = RequestMethod.POST)
    @ResponseBody
    public Message addVersion(AddVersionParam addVersionParam,@RequestParam("file") MultipartFile file){

        String originalFilename=file.getOriginalFilename();

        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

        String filePath=System.getProperty("user.dir")+"\\upload\\";
        File Dir = new File(filePath);
        if (!Dir.exists()) {
            Dir.mkdir();
        }

        filePath += sdf.format(date) + "\\";
        Dir = new File(filePath);
        if (!Dir.exists()) {
            Dir.mkdir();
        }

        File dest=new File(filePath+originalFilename);
        try {
            file.transferTo(dest); //使用transferTo保存文件
            addVersionParam.setFileName(originalFilename);
            addVersionParam.setFilePath("upload\\"+sdf.format(date)+"\\"+originalFilename);
            versionService.addVersion(addVersionParam);
            return onSuccess("上传成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
            return onError("上传失败");
    }

下载前端

 <el-button size="mini" type="warning" @click="downLoad(scope.row)"
              >apk下载</el-button
            >
  //下载apk
    downLoad(row) { 
      const loading = this.$loading({
        lock: true,
        text: "下载中,时间较长,完成后会弹出提示,请注意查看",
        spinner: "el-icon-loading",
        background: "rgba(0, 0, 0, 0.7)",
      });
      setTimeout(function () {
        loading.close();
      }, 3000);

      window.open("version/downLoad/" + row.id, "_self");
    },

下载后端

  //apk下载
    @RequestMapping(value = "/downLoad/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Message downLoad(@PathVariable Integer id , HttpServletResponse response) throws UnsupportedEncodingException {
        File file = new File(System.getProperty("user.dir")+"\\"+versionService.getFilePath(id));
        if(file.exists()){
            // 设置response的Header请求头
            response.addHeader("Content-Disposition", "attachment;filename=" + new String(versionService.getFileName(id).getBytes("UTF-8"), "ISO-8859-1"));
            response.addHeader("Content-Length", "" +file.length());
            byte[] buffer = new byte[1024];
            FileInputStream fis = null; //文件输入流
            BufferedInputStream bis = null;
            OutputStream os = null; //输出流
            try {
                os = response.getOutputStream();
                fis = new FileInputStream(file);
                bis = new BufferedInputStream(fis);
                int i = bis.read(buffer);
                while(i != -1){
                    os.write(buffer);
                    i = bis.read(buffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                bis.close();
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return onError("下载失败");
    }

上传和下载,对于路径:System.getProperty("user.dir")这点需要特别注意。

完!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值