SpringBoot+Vue.js实现大文件分片上传、断点续传与极速秒传

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: SpringBoot+Vue.js实现大文件分片上传、断点续传与极速秒传

亲测好用,这里就直接上代码了,代码有详细的解释。

0. 建表语句

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for file_info
-- ----------------------------
DROP TABLE IF EXISTS `file_info`;
CREATE TABLE `file_info`  (
  `id` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'id',
  `file_path` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '相对路径',
  `file_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件名',
  `suffix` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '后缀',
  `file_size` int(11) NULL DEFAULT NULL COMMENT '大小|字节B',
  `file_use` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用途|枚举[FileUseEnum]:COURSE(\'C\', \'讲师\'), TEACHER(\'T\', \'课程\')',
  `created_at` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `updated_at` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  `shard_index` int(11) NULL DEFAULT NULL COMMENT '已上传分片',
  `shard_total` int(11) NULL DEFAULT NULL COMMENT '分片总数',
  `shard_size` int(11) NULL DEFAULT NULL COMMENT '分片大小|B',
  `file_key` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件标识',
  `vod` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'vod|阿里云vod',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `path_unique`(`file_path`) USING BTREE,
  UNIQUE INDEX `key_unique`(`file_key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '文件' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

1. vue代码

<template>
  <div>
    <el-card class="box-card">
      <el-row>
        <el-col :span="6">
          <el-upload
              class="upload-vhr"
              action="no"
              list-type="text"
              ref="uploadFile"
              accept="no"
              :auto-upload="false"
              :on-exceed="handleExceed"
              :http-request="customUpload"
              :on-change="handleChange"
              :on-remove="handleRemove"
              :limit="1"
              :file-list="fileList">
            <el-input placeholder="请输入内容" v-model="fileName">
              <template slot="append">
                <el-button type="primary" icon="el-icon-folder-opened">
                  选择文件
                </el-button>
              </template>
            </el-input>
            <!--<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>-->
          </el-upload>
        </el-col>
        <el-col :span="6">
          <el-button type="primary" icon="el-icon-folder-opened" @click="submitUpload">
            提交
          </el-button>
        </el-col>
      </el-row>
    </el-card>
  </div>
</template>
<script>
import {hex_md5} from "@/utils/md5.js";
export default {
  name: "EmpAdv",
  /*props: {
    afterUpload: {
      type: Function,
      default: null
    },
  },*/
  data() {
    return {
      file: "",
      fileList: [],
      fileName: "",
      url: {
        upload: "/file/upload",
        check: "/file/check"
      }
    };
  },
  methods: {
    submitUpload() {
      if (this.fileList == '') {
        this.$message.warning("请选择需要上传的文件!")
      } else {
        // 调用文件上传的钩子函数
        this.$refs.uploadFile.submit();
        this.fileList = []
      }
    },
    //自定义上传文件钩子,发送上传文件请求
    customUpload() {
      let file = this.file;
      let key = hex_md5(file.name + file.size + file.type);
      let suffix = file.name.substr(file.name.lastIndexOf(".") + 1).toLowerCase();
      // 文件分片
      let shardSize = 20 * 1024 * 1024; // 以20M为一个分片
      let shardIndex = 1; //分片索引, 1表示第一个分片
      let size = file.size;
      let shardTotal = Math.ceil(size / shardSize);
      let param = {
        "shardIndex": shardIndex,
        "shardSize": shardSize,
        "shardTotal": shardTotal,
        "fileUse": "C",
        "fileName": file.name,
        "suffix": suffix,
        "fileSize": size,
        "fileKey": key
      }
      this.check(param);
    },
    /**
     * 检查文件状态,是否已上传过?传到第几个分片?
     */
    check(param) {
      this.getRequest(this.url.check, {"fileKey": param.fileKey}).then(resp => {
        if (resp && resp.status) {
          let obj = resp.data;
          if (!obj) {
            param.shardIndex = 1;
            console.log("没有找到文件记录,从分片1开始上传");
            this.upload(param);
          } else if (obj.shardIndex === obj.shardTotal) {
            // 已上传分片 = 分片总数,说明已全部上传完,不需要再上传
            this.$message.success("文件极速秒传成功!");
          } else {
            param.shardIndex = obj.shardIndex + 1;
            console.log("找到文件记录,从分片" + param.shardIndex + "开始上传");
            this.upload(param);
          }
        } else {
          this.$message.error("文件上传失败");
        }
      })
    },
    upload(param) {
      let shardIndex = param.shardIndex;
      let shardTotal = param.shardTotal;
      let shardSize = param.shardSize;
      let fileShard = this.getFileShard(shardIndex, shardSize);
      // 将图片转为 base64 进行传输
      let fileReader = new FileReader();
      fileReader.onload = (e => {
        let base64 = e.target.result;
        param.shard = base64;
        this.postRequest(this.url.upload, param).then(resp => {
          if (resp && resp.status) {
            this.fileName = "";
            this.fileList = []
          } else {
            this.$message.error(resp.msg)
          }
          let respData = resp.data
          if (shardIndex < shardTotal) {
            // 上传下一个分片
            param.shardIndex = param.shardIndex + 1;
            this.upload(param);
          } else {
            this.$message.success("上传成功")
          }
        })
      })
      fileReader.readAsDataURL(fileShard);
    },
    getFileShard(shardIndex, shardSize) {
      let file = this.file;
      // 当前分片起始位置
      let start = (shardIndex - 1) * shardSize;
      //当前分片结束位置
      let end = Math.min(file.size, start + shardSize);
      let fileShard = file.slice(start, end);
      return fileShard;
    },
    handleRemove(file, fileList) {
      // 删除上传文件
      this.fileName = "";
      this.fileList = []
    },
    handleChange(file, fileList) {
      // 文件状态钩子,选择文件时触发
      this.fileList = fileList;
      this.fileName = file.name;
      this.file = this.fileList[0].raw;
    },
    handleExceed(files, fileList) {
      this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
    }
  }
}
</script>
<style scoped>
</style>

2. md5加密工具类

var chrsz = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
export function hex_md5(s) {
    return binl2hex(core_md5(str2binl(s), s.length * chrsz));
}
function str2binl(str) {
    var bin = Array();
    var mask = (1 << chrsz) - 1;
    for (var i = 0; i < str.length * chrsz; i += chrsz)
        bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
    return bin;
}
/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len) {
    /* append padding */
    x[len >> 5] |= 0x80 << ((len) % 32);
    x[(((len + 64) >>> 9) << 4) + 14] = len;
    var a = 1732584193;
    var b = -271733879;
    var c = -1732584194;
    var d = 271733878;
    for (var i = 0; i < x.length; i += 16) {
        var olda = a;
        var oldb = b;
        var oldc = c;
        var oldd = d;
        a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
        d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
        c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
        b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
        a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
        d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
        c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
        b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
        a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
        d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
        c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
        b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
        a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
        d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
        c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
        b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
        a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
        d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
        c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
        b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
        a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
        d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
        c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
        b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
        a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
        d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
        c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
        b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
        a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
        d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
        c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
        b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
        a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
        d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
        c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
        b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
        a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
        d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
        c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
        b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
        a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
        d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
        c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
        b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
        a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
        d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
        c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
        b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
        a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
        d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
        c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
        b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
        a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
        d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
        c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
        b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
        a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
        d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
        c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
        b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
        a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
        d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
        c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
        b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
        a = safe_add(a, olda);
        b = safe_add(b, oldb);
        c = safe_add(c, oldc);
        d = safe_add(d, oldd);
    }
    return Array(a, b, c, d);
}
/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray) {
    var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
    var str = "";
    for (var i = 0; i < binarray.length * 4; i++) {
        str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
            hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
    }
    return str;
}

3. 配置文件

我这里配置了一些基础配置:druid、log4j2、mybatis等。

集成log4j2可以看这里: https://blog.csdn.net/weixin_42201180/article/details/111028263

要是不想配置log4j2可以注释掉:

server:
  port: 8090
  servlet:
    context-path: /vhr
  address:
spring:
  profiles:
    active: dev
  application:
    name: vhr
  servlet:
    multipart:
      maxFileSize: 100MB
      maxRequestSize: 100MB
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/vhr?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&serverTimezone=UTC
    data-username: root
    data-password: root
    druid:
      # 初始化时建立物理连接的个数,
      initial-size: 5
      # 最小连接池数量
      min-idle: 5
      # 最大连接池数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位毫秒
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement,也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。mysql5.5+建议开启
      pool-prepared-statements: true
      # 当值大于0时poolPreparedStatements会自动修改为true
      max-pool-prepared-statement-per-connection-size: 20
      # 通过别名的方式配置扩展插件: stat:监控统计,wall:防sql注入,log4j:日志
      filters: stat,wall,slf4j
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
mybatis:
  # 注意:一定要对应mapper映射xml文件的所在路径
  mapper-locations: classpath:/mapper/*Mapper.xml
  # 注意:对应实体类的路径
  type-aliases-package: com.javaboy.vhr.entity
  configuration:
    map-underscore-to-camel-case: true
# 日志配置
logging:
  level:
    com.javaboy.vhr.mapper: DEBUG
  config: classpath:log4j2.yml # 指定log4j配置文件的位置
localUploadFilePath: D:/vhr/localUploadFilePath/

4. 实体类

package com.javaboy.vhr.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.Date;
/**
 * @author: gaoyang
 * @date: 2021-03-23 10:46:31
 * @description: 文件(FileInfo)实体类
 */
@Getter
@Setter
@ApiModel("文件实体类")
public class FileInfo implements Serializable {
    private static final long serialVersionUID = 694649584012557460L;
    @ApiModelProperty("id")
    private String id;
    @ApiModelProperty("相对路径")
    private String filePath;
    @ApiModelProperty("文件名")
    private String fileName;
    @ApiModelProperty("后缀")
    private String suffix;
    @ApiModelProperty("大小|字节B")
    private Integer fileSize;
    @ApiModelProperty("用途|枚举[FileUseEnum]:COURSE('C', '讲师'), TEACHER('T', '课程')")
    private String fileUse;
    @ApiModelProperty("创建时间")
    private Date createdAt;
    @ApiModelProperty("修改时间")
    private Date updatedAt;
    @ApiModelProperty("已上传分片")
    private Integer shardIndex;
    @ApiModelProperty("分片大小|B")
    private Integer shardSize;
    @ApiModelProperty("分片总数")
    private Integer shardTotal;
    @ApiModelProperty("文件标识")
    private String fileKey;
    @ApiModelProperty("base64")
    private String shard;
    @ApiModelProperty("vod|阿里云vod")
    private String vod;
}

5. 后端接口-controller

package com.javaboy.vhr.controller;
import com.github.pagehelper.PageInfo;
import com.javaboy.vhr.entity.FileInfo;
import com.javaboy.vhr.enums.FileUseEnum;
import com.javaboy.vhr.service.FileInfoService;
import com.javaboy.vhr.utils.Base64ToMultipartFile;
import com.javaboy.vhr.utils.result.ResultDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.*;
/**
 * @author: gaoyang
 * @date: 2021-03-23 10:46:33
 * @description: 文件(FileInfo)表控制层
 */
@Slf4j
@Api(tags = "文件API")
@RestController
@RequestMapping("/file")
public class FileInfoController {
    @Value("${localUploadFilePath}")
    private String FILE_PATH;
    @Resource
    private FileInfoService fileInfoService;
    @ApiOperation(value = "文件上传")
    @PostMapping("/upload")
    public ResultDTO<FileInfo> upload(@RequestBody FileInfo fileInfo) throws InterruptedException {
        String use = fileInfo.getFileUse();
        String key = fileInfo.getFileKey();
        String suffix = fileInfo.getSuffix();
        String shardBase64 = fileInfo.getShard();
        MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);
        // 保存文件到本地
        FileUseEnum useEnum = FileUseEnum.getByCode(use);
        // 如果目录不存在则创建
        String dir = useEnum.name().toLowerCase();
        File fullDir = new File(FILE_PATH + dir);
        if (!fullDir.exists()) {
            fullDir.mkdirs();
        }
        // course\6sfSqfOwzmik4A4icMYuUe.mp4
        String path = new StringBuffer(dir)
                .append(File.separator)
                .append(key)
                .append(".")
                .append(suffix)
                .toString();
        // course\6sfSqfOwzmik4A4icMYuUe.mp4.1
        String localPath = new StringBuffer(path)
                .append(".")
                .append(fileInfo.getShardIndex())
                .toString();
        String fullPath = FILE_PATH + localPath;
        File dest = new File(fullPath);
        try {
            // 保存文件
            shard.transferTo(dest);
        } catch (IOException e) {
            log.error(e.getMessage());
            return ResultDTO.error("上传失败-" + e.getMessage(), null);
        }
        // 保存文件记录
        fileInfo.setFilePath(path);
        FileInfo model = this.fileInfoService.queryByKey(fileInfo.getFileKey());
        if (model == null) {
            this.fileInfoService.insert(fileInfo);
        } else {
            model.setShardIndex(fileInfo.getShardIndex());
            this.fileInfoService.update(model);
        }
        if (fileInfo.getShardIndex().equals(fileInfo.getShardTotal())) {
            this.merge(fileInfo);
        }
        return ResultDTO.success("上传成功", fileInfo);
    }
    /**
     * 文件合并
     */
    public void merge(FileInfo fileInfo) throws InterruptedException {
        log.info("合并分片开始");
        // course\6sfSqfOwzmik4A4icMYuUe.mp4
        String path = fileInfo.getFilePath();
        Integer shardTotal = fileInfo.getShardTotal();
        File newFile = new File(FILE_PATH, path);
        // 文件追加写入
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(newFile, true);
        } catch (FileNotFoundException e) {
            log.error(e.getMessage());
        }
        // 分片文件
        FileInputStream fileInputStream = null;
        byte[] bytes = new byte[10 * 1024 * 1024];
        int len;
        try {
            for (Integer i = 0; i < shardTotal; i++) {
                // 读取第 i 个分片
                fileInputStream = new FileInputStream(new File(FILE_PATH + path + "." + (i + 1)));
                while ((len = fileInputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, len);
                }
            }
        } catch (IOException e) {
            log.error("合并分片异常-" + e.getMessage());
        } finally {
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                outputStream.close();
                log.info("IO流关闭");
            } catch (IOException e) {
                log.error("IO流关闭失败-", e.getMessage());
            }
        }
        log.info("合并分片结束");
        // 释放虚拟机对文件的占用
        System.gc();
        Thread.sleep(100);
        log.info("删除分片开始");
        for (Integer i = 0; i < shardTotal; i++) {
            String filePath = FILE_PATH + path + "." + (i + 1);
            File file = new File(filePath);
            boolean result = file.delete();
            log.info("删除{},{}", filePath, result ? "成功" : "失败");
        }
        log.info("删除分片结束");
    }
    @ApiOperation(value = "文件分片检查")
    @GetMapping("/check")
    public ResultDTO<FileInfo> check(@RequestParam(name = "fileKey") String fileKey) {
        FileInfo fileInfo = this.fileInfoService.queryByKey(fileKey);
        return ResultDTO.success(fileInfo);
    }
}

6. 接口实现类serviceImpl

这里就不贴service代码了,大家自动生成即可。

package com.javaboy.vhr.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.javaboy.vhr.entity.FileInfo;
import com.javaboy.vhr.mapper.FileInfoMapper;
import com.javaboy.vhr.service.FileInfoService;
import com.javaboy.vhr.utils.UuidUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
 * @author: gaoyang
 * @date: 2021-03-23 10:46:35
 * @description: 文件(FileInfo)表服务实现类
 */
@Service("fileInfoService")
public class FileInfoServiceImpl implements FileInfoService {
    @Resource
    private FileInfoMapper fileInfoMapper;
    /**
     * 通过ID查询单条数据
     *
     * @param id 主键
     * @return 实例对象
     */
    @Override
    public FileInfo queryById(String id) {
        return this.fileInfoMapper.queryById(id);
    }
    /**
     * 新增数据
     *
     * @param fileInfo 实例对象
     * @return 实例对象
     */
    @Override
    public FileInfo insert(FileInfo fileInfo) {
        fileInfo.setId(UuidUtil.getShortUuid());
        fileInfo.setCreatedAt(new Date());
        fileInfo.setUpdatedAt(new Date());
        this.fileInfoMapper.insert(fileInfo);
        return fileInfo;
    }
    /**
     * 修改数据
     *
     * @param fileInfo 实例对象
     * @return 实例对象
     */
    @Override
    public FileInfo update(FileInfo fileInfo) {
        fileInfo.setUpdatedAt(new Date());
        this.fileInfoMapper.update(fileInfo);
        return this.queryById(fileInfo.getId());
    }
    /**
     * 通过文件标识查询
     * @param fileKey
     * @return
     */
    @Override
    public FileInfo queryByKey(String fileKey) {
        return this.fileInfoMapper.queryByKey(fileKey);
    }
}

mybatis语句这里也不贴了,就是简单的增删改查。

源码地址:https://gitee.com/king-high/vhr-master.git

技术交流+微:JavaBoy_1024

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
JavaScript 前端开发 Java
springboot从控制器请求至页面时js失效的解决方法
springboot从控制器请求至页面时js失效的解决方法
16 0
springboot从控制器请求至页面时js失效的解决方法
|
2月前
|
JSON JavaScript 前端开发
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
解决js中Long类型数据在请求与响应过程精度丢失问题(springboot项目中)
42 0
|
2月前
|
JavaScript 前端开发
springboot+layui从控制器请求至页面时js失效的解决方法
springboot+layui从控制器请求至页面时js失效的解决方法
16 0
|
3月前
|
JavaScript 前端开发 Java
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
springboot整合minio+vue实现大文件分片上传,断点续传(复制可用,包含minio工具类)
472 0
|
2月前
|
JavaScript 安全 Java
Spring Boot 和 Vue.js 实现的前后端分离的用户权限管理系统
Spring Boot 和 Vue.js 实现的前后端分离的用户权限管理系统
56 0
|
存储 前端开发 Java
SpringBoot文件上传和下载
SpringBoot文件上传和下载
SpringBoot文件上传和下载
|
前端开发 Java Spring
SpringBoot文件上传下载
SpringBoot文件上传下载
224 0
SpringBoot文件上传下载
|
Java
SpringBoot文件上传下载
项目中经常会有上传和下载的需求,这篇文章简述一下springboot项目中实现简单的上传和下载。 新建springboot项目,前台页面使用的thymeleaf模板,其余的没有特别的配置,pom代码如下: 4.
4117 0
|
Java Apache 数据库连接
SpringBoot 文件上传/下载
有啥不懂的或者出错的可以在下面留言。 1. 文件上传 //上传路径 String folder = "C:\\code\\springboot-springsecurity\\security-demo\\src\\main\\java...
2409 0
|
23天前
|
Java Linux
Springboot 解决linux服务器下获取不到项目Resources下资源
Springboot 解决linux服务器下获取不到项目Resources下资源