大文件上传,前端vue 做分片上传

news2024/11/24 15:03:21

html – 以弹窗的形式

<!-- 上传算法文件 -->
<el-dialog title="上传算法文件" :visible.sync="uploadPop" width="60%" :close-on-click-modal="false" :before-close="closeUploadPop" append-to-body custom-class="upload_box">
  <div class="upload_btn_box">
    <el-upload
      class="upload-demo"
      ref="upload"
      action=""
      :show-file-list="false"
      :http-request="requestFile"
      :before-upload = "checkFileType"
      v-if="isShowUploadBtn"
      >
      <el-button>上传压缩包</el-button>
    </el-upload>
    <el-button v-else :loading="true">上传压缩包</el-button>
  </div>
  <div class="tip_box">
    <span class="tips_text">
      1、请上传文件类型为zip的算法文件;
      2、上传可能需要一些时间,上传过程中建议不要关闭弹窗。
    </span>
  </div>
  <div class="file_box">
    <div class="upload_list" v-if="isShowUpload">
      <div id="progress-bar-container" class="list_box" :class="{'isSuccess': uploadAllData.isSuccess}">
        <span style="width: 14px"><i class="el-icon-folder"></i></span>
        <span style="width: 30%">{{ uploadAllData.name }}</span>
        <span style="width: 12%">{{ uploadAllData.type == 1? '算法文件': '测试集文件' }}</span>
        <span style="width: 10%">{{ uploadAllData.size }}</span>
        <span style="width: 16%">{{ uploadAllData.isSuccess?uploadAllData.msg: uploadAllData.isStop? '暂停中': uploadAllData.percent+'%' + ' ' + uploadAllData.speed + '/s' }}</span>
        <span class="action_btn" v-if="!uploadAllData.isSuccess" @click="changeStop">{{ uploadAllData.isStop?'▶': '‖' }}</span>
        <span class="action_btn" v-if="!uploadAllData.isSuccess" @click="changeClose">✖</span>
        <div id="progress-bar" class="progress-bar" :style="{ width: progressBarWidth }"></div>
      </div>
    </div>
  </div>
</el-dialog>

js – 初始化数据

// 初始化数据
initUpload() {
  this.fileData = null;
  this.chunks = [];
  this.isShowUpload = false;
  this.uploadAllData = {
    id: null,
    isSuccess: false,
    isStop: false,
    msg: '',
    name: '',
    size: '0',
    speed: "0",
    percent: 0,
    type: 1,
  };
  this.progressBarWidth = '0%';
  this.fileData = null;
  this.chunkSize = 0;
  this.fileSize = 0;
  this.isShowUploadBtn = true;
  this.uploadedChunks = 0;
},

js – 上传前判断文件类型

// 上传前判断
checkFileType(file) {
  // 先初始化,预防在上传过程中继续点击上传
  this.initUpload();
  // 禁止继续点击上传
  this.isShowUploadBtn = false;
  // 判断只能上传.zip压缩包
  const allowedExtensions = ['zip']; // 允许的文件后缀名列表
  const extension = file.name.split('.').pop().toLowerCase(); // 获取文件的后缀名
  if (allowedExtensions.indexOf(extension) === -1) {
    this.$message.error('只允许上传zip格式的文件');
    this.isShowUploadBtn = true;
    return false; // 阻止文件上传
  }
  return true; // 允许文件上传
},

js – 上传前准备、获取传输任务(旧)
!说明: 因为后台需要文件的md5校验文件的完整性,所以需要读取文件,但是文件太大的时候,导致前端读取的时候就需要很长时间了,经协调,去掉参数md5

import SparkMD5 from 'spark-md5';
// 上传算法文件
requestFile(file) {
  this.fileData = file.file;
   const reader = new FileReader();
   console.log('开始分片任务!');
   reader.onload = (event) => {
     const fileObj = event.target.result;
     const spark = new SparkMD5.ArrayBuffer(); // 获取文件的md5 哈希值 -- 后台用于验证文件的完整性
     console.log(spark, 'md5');
     spark.append(fileObj);
     let md5Hash = spark.end();
     console.log(md5Hash, 'md5');
     spark.destroy(); //释放缓存
     // 将文件分片
     this.chunkSize = 1024 * 1024 * 20 // 每个分片的大小20M
     this.fileSize = this.fileData.size; // 文件总大小
     this.chunkCount = Math.ceil(this.fileSize / this.chunkSize) // 分片数量
     this.uploadedChunks = 0 // 重置已上传的分片数量
     this.startTime = new Date().getTime() // 记录上传开始时间
     // 逐个上传分片
     for (let index = 0; index < this.chunkCount; index++) {
       const start = index * this.chunkSize
       const end = Math.min(start + this.chunkSize, this.fileSize)
       const chunk = this.fileData.slice(start, end)
       this.chunks.push(chunk)
     }
     console.log(this.chunks);
     console.log(this.fileData);
    // 整合上传前的参数,请求获取id
     let beforeUpload = {
       belongsId: this.algorithmVersionId,
       chunkNumberCount: this.chunkCount,
       fileSavePath: "",
       fileType: 1, // 1为算法文件; 2为测试集
       md5: md5Hash,
       pauseOffset: 0,
       status: 1,
       transmitType: 1,
       uploaded: this.uploadedChunks,
       fileName: this.fileData.name
     }
     console.log(beforeUpload, '参数整合');
     httpPost('/api/v1/acctm_file_transmit_task', beforeUpload) // 文件分片传输任务表
       .then(res => {
        if(res.code == '10000') {
           this.currentUploadId = res.data;
           let num = (this.fileSize/(1024*1024)).toFixed(2); // B => MB
           let fileSizeStr = "";
           if(num >= 1024) {
             fileSizeStr = num + 'GB'
           } else if(1 <= num < 1024 ) {
             fileSizeStr = num + 'MB'
           } else {
             fileSizeStr = this.fileSize + 'KB'
           }
           this.uploadChunk(this.chunks, 0, this.chunkSize, this.fileData.name, fileSizeStr)
         } else {
           this.isShowUploadBtn = true;
           this.$message({
             type: 'error',
             message: '文件分片传输失败!'
           })
         }
       })
       .catch(() => {
         console.log('到这人了?');
       })
   };
  reader.readAsArrayBuffer(this.fileData);
  将文件分片
  this.chunkSize = 1024 * 1024 * 20 // 每个分片的大小20M
  this.fileSize = this.fileData.size; // 文件总大小
  this.chunkCount = Math.ceil(this.fileSize / this.chunkSize) // 分片数量
  this.uploadedChunks = 0 // 重置已上传的分片数量
  this.startTime = new Date().getTime() // 记录上传开始时间
  // 逐个上传分片
  for (let index = 0; index < this.chunkCount; index++) {
    const start = index * this.chunkSize
    const end = Math.min(start + this.chunkSize, this.fileSize)
    const chunk = this.fileData.slice(start, end)
    this.chunks.push(chunk)
  }
  let beforeUpload = {
    belongsId: this.algorithmVersionId,
    chunkNumberCount: this.chunkCount,
    fileSavePath: "",
    fileType: 1, // 1为算法文件; 2为测试集
    pauseOffset: 0,
    status: 1,
    transmitType: 1,
    uploaded: this.uploadedChunks,
    fileName: this.fileData.name
  }
  httpPost('/api/v1/acctm_file_transmit_task', beforeUpload) // 文件分片传输任务表
    .then(res => {
      if(res && res.code == '10000') {
        this.currentUploadId = res.data;
        let num = (this.fileSize/(1024*1024)).toFixed(2); // B => MB
        let fileSizeStr = "";
        if(num >= 1024) {
          fileSizeStr = (num/1024).toFixed(2) + 'GB'
        } else if(1 <= num && num < 1024 ) {
          fileSizeStr = num + 'MB'
        } else {
          fileSizeStr = (num*1024).toFixed(2) + 'KB'
        }
        this.uploadChunk(this.chunks, 0, this.chunkSize, this.fileData.name, fileSizeStr)
      } else {
        this.isShowUploadBtn = true;
      }
    })
    .catch(() => {
      console.log('开始分片任务失败!');
    })
},

js – 上传前准备、获取传输任务(目前采用)

// 上传算法文件
requestFile(file) {
  this.fileData = file.file;
  // 将文件分片
  this.chunkSize = 1024 * 1024 * 20 // 每个分片的大小20M
  this.fileSize = this.fileData.size; // 文件总大小
  this.chunkCount = Math.ceil(this.fileSize / this.chunkSize) // 分片数量
  this.uploadedChunks = 0 // 重置已上传的分片数量
  this.startTime = new Date().getTime() // 记录上传开始时间
  // 逐个上传分片
  for (let index = 0; index < this.chunkCount; index++) {
    const start = index * this.chunkSize
    const end = Math.min(start + this.chunkSize, this.fileSize)
    const chunk = this.fileData.slice(start, end)
    this.chunks.push(chunk)
  }
  let beforeUpload = {
    belongsId: this.activeRouter.id,
    chunkNumberCount: this.chunkCount,
    fileSavePath: "",
    fileType: 2, // 1为算法文件; 2为测试集
    pauseOffset: 0,
    status: 1,
    transmitType: 1,
    uploaded: this.uploadedChunks,
    fileName: this.fileData.name
  }
  httpPost('/api/v1/acctm_file_transmit_task', beforeUpload) // 文件分片传输任务表
    .then(res => {
      if(res && res.code == '10000') {
        this.currentUploadId = res.data;
        let num = (this.fileSize/(1024*1024)).toFixed(2); // B => MB
        let fileSizeStr = "";
        if(num >= 1024) {
          fileSizeStr = (num/1024).toFixed(2) + 'GB'
        } else if(1 <= num && num < 1024 ) {
          fileSizeStr = num + 'MB'
        } else {
          fileSizeStr = (num*1024).toFixed(2) + 'KB'
        }
        this.uploadChunk(this.chunks, 0, this.chunkSize, this.fileData.name, fileSizeStr)
      } else {
        this.isShowUploadBtn = true;
      }
    })
    .catch(() => {
      console.log('开始分片任务失败!');
    })
},

js – 开始分片上传
!注意:自己封装的axios 请求触发不了onUploadProgress事件

//  上传分片
uploadChunk(chunks, index, chunkSize, name, fileSize) {
  console.log(chunks, index);
  this.isShowUpload = true;
  this.isShowUploadBtn = true; // 显示加载文件后放开上传的按钮
  this.progressBar = document.getElementById('progress-bar');
  if (index >= chunks.length) {
    // 全部分片上传完成
    this.uploadAllData = {
      id: this.currentUploadId,
      isStop: false,
      isSuccess: true,
      msg: '上传完成',
      name: name,
      size: fileSize,
      speed: '0',
      percent: 0,
      type: 1
    }
    // 重置数据
    this.chunks = [];
    this.progressBarWidth = '0%';
    this.fileData = null;
    this.chunkSize = 0;
    this.fileSize = 0;
    this.uploadedChunks = 0;
    return
  }
  let chunk = chunks[index]; // 当前文件的分片数据
  // 整合参数
  const formData = new FormData()
  formData.append('chunkSize', chunkSize)
  formData.append('file', chunk, index+1) // 文件的分片,从1开始 -- 与后台协商,文件的命名
  formData.append('chunkNumber', index+1); // 分片序号,从1开始 -- 与后台协商
  formData.append('chunkNumberCount', this.chunkCount)
  formData.append('transmitTaskId', this.currentUploadId)
  // 发送分片请求
  let url = process.env.VUE_APP_BASE_API;
  axios.post(url + '/api/v1/acctm_file_transmit_task/chunkUpload', formData, { // 文件分片信息表
      headers: {
        'Content-Type': 'multipart/form-data',
        'Authorization': sessionStorage.getItem('token') || ""
      },
      onUploadProgress: (progressEvent) => {
        const uploaded = progressEvent.loaded
        // const total = progressEvent.total
        // 更新上传进度
        this.progress = Math.round(((index + 1) / this.chunkCount) * 100)
        // this.progress = Math.round((uploaded / total) * 100)
        // 计算上传速度
        const currentTime = new Date().getTime()
        const elapsedTime = (currentTime - this.startTime) / 1000 // 经过的时间(秒)
        this.speed = Math.round((uploaded - this.uploadedSize) / elapsedTime / 1024) // 上传速度(KB/s)
        // console.log(this.speed);
        // 更新已上传大小
        this.uploadedSize = uploaded;
        // 转换单位,超过1M,显示M,超过G,显示G
        let number = (this.speed/1024).toFixed(2); // KB => MB
        // console.log(1 <= number < 1024);
        let speedStr = ""
        if(number >= 1024) {
          speedStr = (number/1024).toFixed(2) + 'GB'
        } else if(1 <= number && number < 1024) {
          speedStr = number + 'MB'
        } else if(number < 0 || this.speed < 0) {
          speedStr = 0 + 'KB';
        } else {
          speedStr = (this.speed).toFixed(2) + 'KB'
        }
        this.uploadAllData = {
          id: this.currentUploadId,
          isStop: false,
          isSuccess: false,
          msg: '正在下载',
          name: name,
          size: fileSize,
          speed: speedStr,
          percent: this.progress == 100? 99: this.progress,
          type: 1
        };
        this.progressBarWidth = `${this.progress}%`
      }
    })
    .then((res) => {
      // console.log(res);
      if(res && res.data && res.data.code == '10000' && res.data.data) {
        if(this.timer) {
          clearTimeout(this.timer)
        }
        this.uploadedChunks++
        // 上传下一个分片
        this.uploadChunk(chunks, this.uploadedChunks, chunkSize, name, fileSize);
      } else if(res && res.data && res.data.code == '10000' && !res.data.data) { // 重新上传当前分片
        this.uploadChunk(chunks, this.uploadedChunks, chunkSize, name, fileSize);
      } else if(res && res.data && res.data.code == '40004') {
        this.uploadAllData.isStop = !this.uploadAllData.isStop;
        return
      } else if(res && res.data && res.data.code == '50010') {
        this.uploadAllData.isStop = !this.uploadAllData.isStop;
        return
      } else {
        // 一般是网络问题,隔几秒继续上传
        this.timer = setTimeout(() => {
          this.uploadChunk(chunks, this.uploadedChunks, chunkSize, name, fileSize);
        }, 2000);
      }
    })
    .catch((error) => {
      // 处理上传错误
      console.log('上传错误', error)
    })
},

js – 暂停\启动
!说明:因为前端不能做到真正的把请求暂停,只能调用接口,然后后台去实际暂停上传的接口

// 暂停、启动
changeStop() {
  if(this.timer) {
    clearTimeout(this.timer)
  }
  this.uploadAllData.isStop = !this.uploadAllData.isStop;
  if(this.uploadAllData.isStop) { // 点击了启动图标,触发暂停
    httpGet("/api/v1/acctm_file_transmit_task/pauseStart/" + this.currentUploadId)
      .then(res => {
        if(res && res.code == '10000' && !res.data) {
          // console.log('暂停成功!');
        }
      })
  } else { // 点击了暂停图标,触发启动
    httpGet("/api/v1/acctm_file_transmit_task/pauseStart/" + this.currentUploadId)
      .then(res => {
        if(res && res.code == '10000') {
          let num = (this.fileSize/(1024*1024)).toFixed(2); // B => MB
          let fileSizeStr = "";
          if(num >= 1024) {
            fileSizeStr = (num/1024).toFixed(2) + 'GB'
          } else if(1 <= num && num < 1024 ) {
            fileSizeStr = num + 'MB'
          } else {
            fileSizeStr = (num*1024).toFixed(2) + 'KB'
          }
          // 从之前的分片
          this.uploadChunk(this.chunks, this.uploadedChunks+1, this.chunkSize, this.fileData.name, fileSizeStr);
        }
      })
  }
},

js – 删除任务

changeClose() {
  if(this.timer) {
    clearTimeout(this.timer)
  }
  httpDeleter('/api/v1/acctm_file_transmit_task/' + this.currentUploadId)
    .then(res => {
      if(res && res.code == '10000') {
        this.$message({
          type: 'success',
          message: '删除成功!'
        });
        this.initUpload();
      }
    })
},

js – 关闭弹窗
!说明: 由于上传的文件比较大,本地文件存储的位置获取不到,所以如果半路退出,则不能再继续获取到上一次的文件

closeUploadPop() {
  if(this.timer) {
    clearTimeout(this.timer)
  }
  // 判断是否上传完成
  if(this.fileData && !this.uploadAllData.isSuccess) {
    this.$confirm('文件上传未完成,关闭弹窗则上传无效,是否确定关闭?', '温馨提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
      .then(async() => {
        let res = await httpDeleter('/api/v1/acctm_file_transmit_task/' + this.currentUploadId)
        if(res && res.code == '10000') {
          this.uploadPop = false;
          this.initUpload();
          if(this.activeName == "1"){
            this.getCloudData(1,10,1,this.algorithmId);
          } else {
            this.getCloudData(1,10,2,this.algorithmId);
          }
        }
      })
      .catch(() => {
        
      })
  } else {
    this.uploadPop = false;
    // 格式化数据
    this.initUpload();
    if(this.activeName == "1"){
      this.getCloudData(1,10,1,this.algorithmId);
    } else {
      this.getCloudData(1,10,2,this.algorithmId);
    }
  }
},

完成、效果展示
正在上传
在这里插入图片描述
暂停
在这里插入图片描述
退出
在这里插入图片描述
完成
在这里插入图片描述

中秋国庆的八天假 迎来了七天的搬砖!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1069158.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

完美收官丨深圳信驰达科技IOTE 2023第二十届国际物联网展参展回顾

►►►展会风采 2023年9月22日&#xff0c;为期三天的IOTE 2023第二十届国际物联网展 • 深圳站在深圳国际会展中心&#xff08;宝安馆&#xff09;9、10、11号馆圆满落幕。本届展会以“IoT构建数字经济底座”为主题&#xff0c;吸引覆盖IoT全栈生态的参展商&#xff0c;展出超…

【数据结构】快排的详细讲解

目录&#xff1a; 介绍 一&#xff0c;递归快排确定基准值 二&#xff0c;递归遍历 三&#xff0c;非递归的快排 四&#xff0c;快排的效率 介绍 快排是排序算法中效率是比较高的&#xff0c;快排的基本思想是运用二分思想&#xff0c;与二叉树的前序遍历类似&#xff0c;…

C++ 之如何将数组传递给函数?

在本文中&#xff0c;您将学习将数组传递给C 中的函数。您将学习如何传递一维和多维数组。 数组可以作为参数传递给函数。也可以从函数返回数组。考虑以下示例&#xff0c;将一维数组传递给函数&#xff1a; 示例1&#xff1a;将一维数组传递给函数 C 程序通过将一维数组传递…

从事嵌入式工作有哪些优势?

随着物联网和人工智能的发展&#xff0c;嵌入式技术越来越值钱&#xff0c;学嵌入式的人也越来越多&#xff0c;现在开始入行嵌入式。根据一些权威部门统计&#xff0c;我国目前嵌入式软件人才缺口每年为40万人左右&#xff0c;嵌入式人才供给一直处于供不应求的状态。 那么从…

Docker私有仓库打开2375端口(linux)

前言 在我们开发测试过程中&#xff0c;需要频繁的更新docker镜像&#xff0c;然而默认情况下&#xff0c;docker的2375端口是关闭的&#xff0c;下面介绍如何打开端口。 1、打开步骤 1.1、修改配置 登录docker所在服务器&#xff0c;修改docker.service文件 vi /usr/lib/sys…

Mac解压缩软件BetterZip免费版注册码下载

软件介绍 BetterZip免费版是一款适用于Mac系统的解压缩软件&#xff0c;软件具备了专业、实用、简单等特点&#xff0c;它可以让用户更快捷的向压缩文件中添加和删除文件&#xff0c;同时兼容性也十分优秀&#xff0c;支持ZIP &#xff0c; SIT &#xff0c; TAR、BZIP2 &…

Linux 系统日常运维经典技能

【微|信|公|众|号&#xff1a;厦门微思网络】 【微思网络www.xmws.cn&#xff0c;成立于2002年&#xff0c;专业培训21年&#xff0c;思科、华为、红帽、ORACLE、VMware等厂商认证及考试&#xff0c;以及其他认证PMP、CISP、ITIL等】 linux运维必备认证-RHCE&#xff0c;学RHC…

mysql 物理备份及恢复

一、物理复制的基本概念 物理备份:直接复制数据库文件&#xff0c;适用于大型的数据库环境&#xff0c;不受存储引擎的限制&#xff0c;但不能恢复到不同的mysql版本 完整备份&#xff1a;也叫完全备份&#xff0c;每次将所有数据&#xff08;不管自第一次备份有没有修改过&…

盘点日本IT行业到底怎么样?赴日IT分析

相比其它行业&#xff0c;日本的IT行业对于人才需求旺盛、就业前景比较光明。一方面&#xff0c;日本IT企业在创新和开发方面寻求领先&#xff0c;需要大量的IT人才为其所用。另一方面&#xff0c;随着全球数字化趋势的加速和政府信息化的不断推进&#xff0c;各个领域都在不断…

力扣第257题 二叉树的所有路径 c++ 树 深度优先搜索 字符串 回溯 二叉树

题目 257. 二叉树的所有路径 简单 给你一个二叉树的根节点 root &#xff0c;按 任意顺序 &#xff0c;返回所有从根节点到叶子节点的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,null,5] 输出&#xff1a;["1->2-&g…

Win11右键恢复Win10老版本

Win11右键恢复Win10老版本 最近自己更新了windows11的OS,整体感觉都是不错的,但是就是每次右键菜单我都要再次点击下展开更多选项,这对追求极简主义的我,就是不爽, 手动恢复win10操作吧! 第一种:创建文件(简单快速) 1.新建一个resoreRightKey.reg文件,并在里面填入如下代码 W…

阿里云服务器乌兰察布带宽收费价格表

阿里云服务器华北6&#xff08;乌兰察布&#xff09;地域公网带宽价格表&#xff0c;1M带宽价格是23元/月&#xff0c;按使用流量价格是0.8元每GB&#xff0c;阿里云服务器网来详细说下1M带宽、5M带宽、6M带宽、10M带宽、20M带宽、50M带宽和100M等带宽收费价格表&#xff1a; …

课题学习(四)----四元数解法

一、四元数解法 为了求解惯性导航的力学方程&#xff0c;姿态矩阵 R b b R^b_{b} Rbb​可以有姿态微分方程得到。其中&#xff0c;四元数是常用的方法&#xff0c;如下图所示&#xff0c;假设刚体在原点旋转&#xff0c;根据欧拉定理&#xff0c;运动坐标系(b系列)相对于导航坐…

计算机竞赛 题目:基于深度学习的人脸表情识别 - 卷积神经网络 竞赛项目 代码

文章目录 0 简介1 项目说明2 数据集介绍&#xff1a;3 思路分析及代码实现3.1 数据可视化3.2 数据分离3.3 数据可视化3.4 在pytorch下创建数据集3.4.1 创建data-label对照表3.4.2 重写Dataset类3.4.3 数据集的使用 4 网络模型搭建4.1 训练模型4.2 模型的保存与加载 5 相关源码6…

vue3 vscode no tsconfig与找不到名称“ref”。ts(2304)

如题&#xff0c;这两个问题都与tsconfig的配置有关&#xff0c;先看下问题表现&#xff1a; 解决方法&#xff0c;应当正确配置如下&#xff0c;之后保存或重启vscode&#xff1a;

Stm32_标准库_7_光敏传感器

AO端口&#xff1a;通俗的讲大概是根据环境亮度的不同导致电阻的阻值不同&#xff0c;最后AO口输出的模拟量也不同&#xff0c;这个端口是用来测量环境光照的具体强度 DO端口&#xff1a;光敏电阻默认设置了一个阈值&#xff0c;当光照强度高于这个阈值本端口输出低电平&#…

C#使用ICSharpCode.TextEditor制作代码编辑器

效果 类似于vs里的代码风格 准备工作 1 创建Winform项目 2 NuGet下载ICSharpCode.TextEditor 显示控件ICSharpCode.TextEditor 将Debug目录下的ICSharpCode.TextEditor.dll,通过工具栏显示控件,并拖拽到Form窗口代码 1 Form 代码 using System; using S

Midjourney第四篇:9大风格头像

获取图像生成提示词&#xff08;咒语&#xff09;&#xff0c;公众号&#xff1a;科技探幽&#xff0c;回复“mid”&#xff0c;获取详细教程 迪士尼风格 关键词&#xff1a;art by disney ,Disney style,3d character from Disney 皮克斯风格 关键词&#xff1a;art by pix…

跟着播客学英语-Why I use vim ? part one.

why-use-vim-01.png 最近这段时间在学英语&#xff0c;在网上看到有网友推荐可以听英文播客提高听力水平。 正好我自己也有听播客的习惯&#xff0c;只不过几乎都是中文&#xff0c;但现在我已经尝试听了一段时间的英文播客&#xff0c;觉得效果还不错。 大部分都是和 IT 相关的…

MyBatis(JavaEE进阶系列4)

目录 前言&#xff1a; 1.MyBatis是什么 2.为什么要学习MyBatis框架 3.MyBatis框架的搭建 3.1添加MyBatis框架 3.2设置MyBatis配置 4.根据MyBatis写法完成数据库的操作 5.MyBatis里面的增删改查操作 5.1插入语句 5.2修改语句 5.3delete语句 5.4查询语句 5.5like查…