知识点21--springboot 文件上传

news2025/1/22 14:40:30

前面有一篇简单版的文件上传,是为了让大家知道文件上传是在干什么,但是在正式的开发中文件上传是一个稍微有些麻烦的东西,需要从页面层开发到数据层,如果你常常听人说文件上传会知道有一些相关的名词,比如切片、秒传、断点续传、md5、合并等名词。但其实一个完整的文件上传开发起来核心点永远只有那么几个。因此,本篇知识点给大家写了一个完整的文件上传流程,本身是用于大文件上传,不过当你看明白了代码,知晓了文件上传的核心要点,你就会发现,大、小文件的上传区别就两点,一是是否分片,二是是否并发,其他的都差不多。

当然考虑到适用性和大家的理解能力,本篇写的上传流程并没有偏业务代码,比如检查文件大小、图片像素等等这种偏业务的代码通通没有,只写了上传文件的主体核心流程,同时代码也上传到了github----》https://github.com/wangyang159/boot-jsp

看本例代码的时候有个容易产生误区的点要注意,本例代码只针对单个文件,如果你是多文件,则遍历文件调用上传就行,千万不要在单文件并发的基础上,再套一层多文件并发,先不说性能和开发难度的问题,浏览器就撑不住,浏览器对于请求的个数都是有一个上限的,而且一般不高,你可以去查一查,最高的应该是谷歌派系,也只是最高支持6个。所以我的代码中并发度是两个,给其他请求留了4个,同理你要是在单文件并发上在套一层多文件同时并发,那你的代码就成垃圾了,能不能用都成问题。不过一般在正式开发中涉及到文件上传,小文件一般传递很快,只需要把大文件的一个md5对应多个切片的js数据集合,换成小文件场景下一对一的数据集,就是说把小文件当成大文件的切片,再对应的改一下发送请求的代码,从而就可以实现小文件的并发发送,而大文件肯定会有个数和大小的限制的,不会让你很多个大文件一起上传,比如腾讯旗下的产品大多就限制单个大文件最大4G,多个挨个上传,所以遍历大文件单个并发上传就够了。

由于整个流程细节很多就不给大家分解了,完整的代码如果看不明白可以留言给我。为了方便大家理解,最好是先看一下流程图。

在这里插入图片描述
看完流程图对整个流程有个影响,就可以看代码了。

1、首先是前端的表单页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>切片上传</title>
</head>
<body>

    <form id="addFrm" enctype="multipart/form-data" method="post">
        文件上传:<input type="file" name="file" id="file"><br/>
        <input type="button" onclick="saveObject()" value="文件上传"/>
    </form>


    <script type="text/javascript" src="/sy.js"></script>
    <script type="text/javascript" src="spark-md5.min.js"></script>
    <script type="text/javascript" src="jquery-3.2.1.js"></script>
    <script type="text/javascript">
        /**
         * 1、分片获取文件的md5 并 顺序保存分片fileblock
         * 给单文件标签添加一个修改事件,修改时将文件分为sliceLength个片
         * 文件总大小除以sliceLength获得每个块的大小chunkSize
         * FileReader每读取一块文件的字节数组就追加到SparkMD5中
         * onload回调函数递归闭包的读取方法直到读取结束
         * file_index是原文件的字节数组指针,fileblock_index是保存块文件的数组指针
         * 最终将结果封装在一个总的结果集readyfilemeg中
         *
         * 注意结果中块数一定是sliceLength块,不要担心除法有可能除不尽文件的总大小
         * 递归读取的条件是file_index小于文件总字节当运行到最后一块不够的的时候任然会切片
         */
        var readyfilemeg = []
        document.querySelector('#file').addEventListener('change', e => {
            //准备需要的变量
            const fileblock = [];
            let fileblock_index = 0;
            //document获取文件
            const file = e.target.files[0];
            const sliceLength = 5;
            const chunkSize = Math.ceil(file.size / sliceLength);
            const fileReader = new FileReader();
            const md5 = new SparkMD5();
            let file_index = 0;

            //运行方法
            const loadFile = () => {
                //slice是一个左闭右开的方法
                const slice = file.slice(file_index, file_index + chunkSize);
                fileblock[fileblock_index]=slice;
                fileblock_index++;
                file_index += chunkSize;
                fileReader.readAsBinaryString(slice);
            }

            //第一次需要手动调一下,才能触发onload
            loadFile();
            fileReader.onload = e1 => {
                md5.appendBinary(e1.target.result);
                if ( file_index < file.size ) {
                    loadFile();
                } else {
                    //封装结果:文件的md5、文件块集合、文件块总数、文件的名称、文件总大小
                    readyfilemeg["filemd5"]=md5.end();
                    readyfilemeg["fileblocks"]=fileblock;
                    readyfilemeg["fileblocksize"]=fileblock_index;
                    readyfilemeg["filename"]=file.name;
                    readyfilemeg["filesize"]=file.size;
                    console.log(readyfilemeg)
                }
            };
        });

        /**
         * 2、当要点击上传的时候,使用并发API-Promise将切片信息并发传递给后台
         */
        function saveObject() {
            //秒传
            $.ajax({
                method:"post",
                dataType:"json",
                url:"/minupload",
                data:{"fileMd5":readyfilemeg["filemd5"]},
                success:function (result) {
                    if(result){
                        alert("上传成功")
                    }else {
                        concurRequst(readyfilemeg,2).then(resps=>{
                            let reduce = 0;
                            for (let i = 0; i < resps.length; i++) {
                                reduce+=parseInt(resps[i]);
                            }

                            /**
                             * 3、根据后台传递回来的信息全部为上传成功,那么就要触发后台合并
                             */
                            if(reduce != 0){
                                alert("上传失败请重新上传文件")
                            }else{
                                $.ajax({
                                    method:"post",
                                    dataType:"json",
                                    url:"/allhb",
                                    data:{"fileMd5":readyfilemeg["filemd5"],"fileSize":readyfilemeg["filesize"],"fileName":readyfilemeg["filename"]},
                                    success:function (result) {
                                        if(result){
                                            alert("上传成功")
                                        }else{
                                            alert("上传失败")
                                        }
                                    }
                                })
                            }
                        });

                    }
                }
            })

        }

    </script>
</body>
</html>

2、页面上发送单独写了一个js,用来写上传时的方法

/**
 * 用并发请求的方式发出所有文件,本质上是Promise提供的异步且并发发送请求功能,运行上就是用n个并发,将请求体数组中的请求分摊开来发送
 * @param readyfilemeg 请求体数组
 * @param maxNum 最大并发数
 * @returns
 */
function concurRequst(datas,maxNum) {

    //并发发送请求,resolve是Promise用来回调的结果集
    return new Promise(resolve => {

        //文件数据不能为空
        if(datas == null){
            //Promise类的方法,调用时表示Resolved已完成,又称Fulfilled,参数是结束后可供回调的数据
            resolve([])
            return
        }

        //定义一个保存结果的临时数组、一个取文件信息时的临时下标、并且将文件块信息取出来
        const result = [];
        let index = 0;
        let fileblocks = datas["fileblocks"];

        //并发发出请求的方法
        async function request() {
            //当文件块数组中的内容全部发完,结束任务,返回回调结果
            if(index === fileblocks.length){
                resolve(result);
                return;
            }

            //每次取出一个文件块,index后移,并另存一份下标后面要按下标保存结果
            const fileblock = fileblocks[index];
            const i = index;
            index++;

            //封装该文件块的信息
            let f = new FormData();
            f.append("fileBlock",fileblock);//文件块本身
            f.append("fileMd5",datas["filemd5"]);//总文件的md5
            f.append("fileBlockSize",fileblocks.length);//总文件一共被分了几块
            f.append("blockIndex",i+1);//该快文件的序号
            f.append("fileName",datas["filename"]);//文件的名称

            try {
                //把文件块的信息发到后台并保存返回结果
                const resp = await fetch("http://localhost:91/upload",{
                    method : "post",
                    body : f
                })

                //后台将结果信息保存在响应头中,拿出来按顺序放到临时的结果集中
                result[i]=resp.headers.get("meg")
            }catch (err){
                //出现意外 也要把错误信息加入进去
                result[i] = err
            }finally {
                //递归发送下一个请求
                request()
            }
        }

        //用并发数和请求体数组元素个数取最小值控制发送数量
        const t = Math.min(maxNum,fileblocks.length)
        for (let i = 0 ; i < t ; i++){
            request()
        }
    })
}

3、前端用了两个框架,第一个是jquery,大家应该都有,第二个是spark-md5,是一个计算文件md5的js框架,它可以在git上下载到https://github.com/satazor/js-spark-md5
在这里插入图片描述
代码中用它配合文件切分,提高转换大文件md5效率的同时,也完成了切片的需求,如果大家自己写代码的时候如果上传的是一般打下的文件,不需要切片的话,可以直接整个文件读取md5,就像下面这样

document.querySelector('#file').addEventListener('change', e => {
  const file = e.target.files[0];
  const fileReader = new FileReader()
  fileReader.readAsBinaryString(file);
  fileReader.onload = e => {
    const md5 = SparkMD5.hashBinary(e.target.result);
    console.log(md5);
  }
});

4、前端就上面这些了,下面就是后端,首先是后端的数据Bean,需要对应数据库中的切片信息表和文件信息表

package com.wy.bootjsp.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/20 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;FileBlock 文件块数据的信息类
 */
public class FileBlock {
    /**
     * 数据id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 总文件的md5信息
     */
    private String fileMd5;

    /**
     * 总文件一共被分了几块
     */
    private Integer fileBlockSize;

    /**
     * 该块文件的顺序号
     */
    private Integer blockIndex;

    /**
     * 总文件的名字
     */
    private String fileName;

    /**
     * 该文件块的全路径名
     */
    private String blockPathName;

    public FileBlock() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFileMd5() {
        return fileMd5;
    }

    public void setFileMd5(String fileMd5) {
        this.fileMd5 = fileMd5;
    }

    public Integer getFileBlockSize() {
        return fileBlockSize;
    }

    public void setFileBlockSize(Integer fileBlockSize) {
        this.fileBlockSize = fileBlockSize;
    }

    public Integer getBlockIndex() {
        return blockIndex;
    }

    public void setBlockIndex(Integer blockIndex) {
        this.blockIndex = blockIndex;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getBlockPathName() {
        return blockPathName;
    }

    public void setBlockPathName(String blockPathName) {
        this.blockPathName = blockPathName;
    }

    @Override
    public String toString() {
        return "FileBlock{" +
                "id=" + id +
                ", fileMd5='" + fileMd5 + '\'' +
                ", fileBlockSize=" + fileBlockSize +
                ", blockIndex=" + blockIndex +
                ", fileName='" + fileName + '\'' +
                ", blockPathName='" + blockPathName + '\'' +
                '}';
    }
}
package com.wy.bootjsp.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/20 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;FileMeg 结果文件信息
 */
public class FileMeg {
    /**
     * 数据id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 文件的md5信息
     */
    private String fileMd5;

    /**
     * 总文件一共被分了几块
     */
    private Integer fileBlockSize;

    /**
     * 总文件的名字
     */
    private String fileName;

    /**
     * 文件的全路径名
     */
    private String pathName;

    /**
     * 文件总大小
     */
    private Long fileSize;

    public FileMeg() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFileMd5() {
        return fileMd5;
    }

    public void setFileMd5(String fileMd5) {
        this.fileMd5 = fileMd5;
    }

    public Integer getFileBlockSize() {
        return fileBlockSize;
    }

    public void setFileBlockSize(Integer fileBlockSize) {
        this.fileBlockSize = fileBlockSize;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public Long getFileSize() {
        return fileSize;
    }

    public void setFileSize(Long fileSize) {
        this.fileSize = fileSize;
    }

    public String getPathName() {
        return pathName;
    }

    public void setPathName(String pathName) {
        this.pathName = pathName;
    }

    @Override
    public String toString() {
        return "FileMeg{" +
                "id=" + id +
                ", fileMd5='" + fileMd5 + '\'' +
                ", fileBlockSize=" + fileBlockSize +
                ", fileName='" + fileName + '\'' +
                ", pathName='" + pathName + '\'' +
                ", fileSize=" + fileSize +
                '}';
    }
}

并且你要准备一个数据库建表

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50725
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 50725
 File Encoding         : 65001

 Date: 20/12/2022 20:48:14
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for blockmeg
-- ----------------------------
DROP TABLE IF EXISTS `blockmeg`;
CREATE TABLE `blockmeg`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `filemd5` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '原文件的md5',
  `fileblocksize` int(11) NULL DEFAULT NULL COMMENT '原文件被切分的总块数',
  `blockindex` int(11) NULL DEFAULT NULL COMMENT '该块数对比原文件分块的顺序',
  `filename` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '原文件姓名',
  `blockpathname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '块数据保存路径',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 79 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '切片信息表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for filemeg
-- ----------------------------
DROP TABLE IF EXISTS `filemeg`;
CREATE TABLE `filemeg`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `filemd5` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件的md5',
  `fileblocksize` int(11) NULL DEFAULT NULL COMMENT '文件被切分的总块数',
  `filename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件名',
  `pathname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件存储路径',
  `filesize` bigint(20) NULL DEFAULT NULL COMMENT '文件总大小',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '切片信息表对应的原文件信息表' ROW_FORMAT = Dynamic;

5、随后是控制器层

package com.wy.bootjsp.controller;

import com.wy.bootjsp.bean.FileBlock;
import com.wy.bootjsp.bean.FileMeg;
import com.wy.bootjsp.service.FileBlockService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

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

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/18 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;处理文件上传的控制器
 */
@Controller
public class FileUploadContrller {

    @Resource
    private FileBlockService fileBlockService;

    /**
     * 接收数据块的控制器,并支持断点续传
     * @param response
     * @param fileBlock
     * @param fileBlockMeg
     */
    @RequestMapping("/upload")
    @ResponseBody
    public void test(HttpServletResponse response, @RequestParam("fileBlock") MultipartFile fileBlock, FileBlock fileBlockMeg){
        //断点续传:用总文件的md5和文件块序号去数据库中查,如果有则跳过存储该数据块
        Integer blockId = fileBlockService.getFileBlockByMd5AndIndex(fileBlockMeg);
        if(blockId != null){
            response.addHeader("meg","0");
            return;
        }

        //如果秒传没有发现已有数据块就保存
        String path = null;
        try {
            path = fileBlockService.saveFile(fileBlock, fileBlockMeg);
        } catch (IOException e) {
            response.addHeader("meg","1");
            e.printStackTrace();
            return;
        }
        fileBlockMeg.setBlockPathName(path);
        //把块文件信息写在数据库里面
        fileBlockService.insertFileBlockMeg(fileBlockMeg);

        response.addHeader("meg","0");
    }

    /**
     * 通过文件的md5查询文件信息表是否有记录,如果有则视为本次上传为秒传
     * @param fileMd5
     * @return
     */
    @RequestMapping("/minupload")
    @ResponseBody
    public Boolean minonload(String fileMd5){
        Integer id = fileBlockService.getFileMegByMd5(fileMd5);
        if(id != null){
            return true;
        }else{
            return false;
        }
    }

    /**
     * 文件合并,并把结果文件的信息保存在数据库中
     * @param fileMd5
     * @param fileSize
     * @param fileName
     * @return
     */
    @RequestMapping("/allhb")
    @ResponseBody
    public Boolean allhb(String fileMd5,long fileSize,String fileName) throws InterruptedException {
        /*
        等待一秒防止前面切面并行上传的结果没有完全落库完成
        如果不等,则由于前面的上传是promise异步+并行,虽然使用了await,但仍然很可能少查询到n条数据,往往是最后一条
        这一点应该是MybatisPlus本身的问题
         */
        Thread.sleep(1000);

        //先查出所有文件片路径信息
        List<FileBlock> fileBlocksPath = fileBlockService.getFileBlocksPath(fileMd5);

        //获取到这些片文件,按顺序放到数组中
        File[] fileBlocks = new File[fileBlocksPath.size()];
        for (int i = 0 ; i < fileBlocksPath.size() ; i++){
            FileBlock f = fileBlocksPath.get(i);
            //下标要减一,因为片文件保存的时候是从1开始的
            fileBlocks[f.getBlockIndex()-1] = new File(f.getBlockPathName());
        }
        //合并这些文件,注意保存文件的时候文件名主体使用md5
        String savePath = fileBlockService.allhb(fileBlocks, fileMd5 + "." + fileName.split("\\.")[1]);

        //如果合并成功返回的应该是结果文件的路径,需要和其他关键信息一起保存到数据库中
        if(!savePath.equals("error")){
            FileMeg fileMeg = new FileMeg();
            fileMeg.setFileMd5(fileMd5);
            fileMeg.setFileName(fileName);//保存文件信息时保存的是文件的本来名字
            fileMeg.setFileSize(fileSize);
            fileMeg.setFileBlockSize(fileBlocks.length);
            fileMeg.setPathName(savePath);
            fileBlockService.insertFileMeg(fileMeg);
            return true;
        }else{
            return false;
        }

    }
}

6、随后是service业务层

package com.wy.bootjsp.service;

import com.wy.bootjsp.bean.FileBlock;
import com.wy.bootjsp.bean.FileMeg;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/20 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;FileBlockService
 */
public interface FileBlockService {

    /**
     * 通过md5和文件序号查询一条分片信息
     * @param fileBlock
     * @return
     */
    Integer getFileBlockByMd5AndIndex(FileBlock fileBlock);

    /**
     * 文件存储
     * @param file
     * @param fileBlock
     * @return
     */
    String saveFile (MultipartFile file,FileBlock fileBlock) throws IOException;

    /**
     * 通过md5获得一个文件信息
     * @param fileMd5
     * @return
     */
    Integer getFileMegByMd5(String fileMd5);

    /**
     * 保存文件块信息
     * @param fileBlock
     * @return
     */
    void insertFileBlockMeg(FileBlock fileBlock);

    /**
     * 获取所有文件的路径信息
     * @param fileMd5
     * @return
     */
    List<FileBlock> getFileBlocksPath(String fileMd5);

    /**
     * 合并所有文件的方法
     * @param files
     * @param savaFileName 合并结果的文件名
     * @return
     */
    String allhb(File[] files,String savaFileName);

    /**
     * 保存完整文件信息
     * @param fileMeg
     * @return
     */
    void insertFileMeg(FileMeg fileMeg);
}
package com.wy.bootjsp.service.impl;

import com.wy.bootjsp.bean.FileBlock;
import com.wy.bootjsp.bean.FileMeg;
import com.wy.bootjsp.mapper.FileBlockMapper;
import com.wy.bootjsp.service.FileBlockService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.util.List;

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/20 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;FileBlockSerivceImpl
 */
@Service
public class FileBlockSerivceImpl implements FileBlockService {

    @Resource
    private FileBlockMapper fileBlockMapper;

    @Override
    public Integer getFileBlockByMd5AndIndex(FileBlock fileBlock) {
        return fileBlockMapper.getFileBlockByMd5AndIndex(fileBlock);
    }

    @Override
    public String saveFile(MultipartFile file,FileBlock fileBlock) throws IOException {
        //结果文件名称
        String fileName = fileBlock.getFileMd5() + fileBlock.getBlockIndex() + ".ext";
        //创建新文件对象
        File destFile = new File("D:\\pic", fileName);

        //确保目标的父文件目录存在
        if (!destFile.getParentFile().exists()) {
            destFile.mkdirs();
        }
        //执行拷贝过程
        file.transferTo(destFile);
        //返回文件的全路径名
        return destFile.getPath();
    }

    
    @Override
    public Integer getFileMegByMd5(String fileMd5) {
        return fileBlockMapper.getFileMegByMd5(fileMd5);
    }
    
    @Override
    public void insertFileBlockMeg(FileBlock fileBlock) {
        fileBlockMapper.insertFileBlockMeg(fileBlock);
    }

    @Override
    public List<FileBlock> getFileBlocksPath(String fileMd5) {
        return fileBlockMapper.getFileBlocksPath(fileMd5);
    }

    @Override
    public String allhb(File[] files,String savaFileName) {
        String resultPath = "D:\\pic\\"+savaFileName;
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            //新建一个目标文件对象
            File target = new File(resultPath);
            //打开文件流输出对象
            out = new FileOutputStream(target);

            //循环读取要合并的文件集合
            for(File f : files) {
                byte[] buf = new byte[1024];
                int len = 0;
                in = new FileInputStream(f);
                while ((len = in.read(buf)) != -1) {
                    //写出数据
                    out.write(buf,0,len);
                }
                //写完之后把片文件的输入流关掉
                if (in != null) {
                    in.close();
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return "error";
        } catch (IOException e) {
            e.printStackTrace();
            return "error";
        }finally {
            //把结果文件的输出流关掉
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return resultPath;
    }

    @Override
    public void insertFileMeg(FileMeg fileMeg) {
        fileBlockMapper.insertFileMeg(fileMeg);
    }
}

7、最后是数据层

package com.wy.bootjsp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wy.bootjsp.bean.FileBlock;
import com.wy.bootjsp.bean.FileMeg;

import java.util.List;

/**
 * 作者: wangyang <br/>
 * 创建时间: 2022/12/20 <br/>
 * 描述: <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;FileBlockDao
 */
public interface FileBlockMapper extends BaseMapper<FileBlock> {

    /**
     * 通过md5和文件序号查询一条分片信息
     * @param fileBlock
     * @return
     */
    Integer getFileBlockByMd5AndIndex(FileBlock fileBlock);

    /**
     * 通过md5获得一个文件信息
     * @param fileMd5
     * @return
     */
    Integer getFileMegByMd5(String fileMd5);

    /**
     * 保存文件块信息
     * @param fileBlock
     * @return
     */
    void insertFileBlockMeg(FileBlock fileBlock);

    /**
     * 获取所有文件的路径信息
     * @param fileMd5
     * @return
     */
    List<FileBlock> getFileBlocksPath(String fileMd5);

    /**
     * 保存完整文件信息
     * @param fileMeg
     * @return
     */
    void insertFileMeg(FileMeg fileMeg);

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wy.bootjsp.mapper.FileBlockMapper">

    <select id="getFileBlockByMd5AndIndex" resultType="Integer">
        select id from blockmeg where filemd5=#{fileMd5} and blockindex=#{blockIndex}
    </select>

    <insert id="insertFileBlockMeg">
        insert into blockmeg(filemd5,fileblocksize,blockindex,filename,blockpathname) values(
            #{fileMd5},#{fileBlockSize},#{blockIndex},#{fileName},#{blockPathName}
        )
    </insert>

    <select id="getFileMegByMd5" resultType="Integer">
        select id from filemeg where filemd5=#{fileMd5}
    </select>

    <resultMap id="fileBlockMap" type="com.wy.bootjsp.bean.FileBlock">
        <id column="id" property="id"></id>
        <result column="blockpathname" property="blockPathName" />
        <result column="blockindex" property="blockIndex" />
    </resultMap>

    <select id="getFileBlocksPath" resultMap="fileBlockMap">
        select id,blockpathname,blockindex from blockmeg where filemd5=#{fileMd5}
    </select>

    <insert id="insertFileMeg">
        insert into filemeg(id,filemd5,fileblocksize,filename,pathname,filesize) values(
            #{id},#{fileMd5},#{fileBlockSize},#{fileName},#{pathName},#{fileSize}
        )
    </insert>

</mapper>

其他pom、springboot配置文件那些,大家自己把代码拉下来自己看就行

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

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

相关文章

编程算法集锦

编程算法集锦一、分治法1.分治法介绍2.归并排序3.快速排序4.中值问题二、贪心法1.贪心法2.最小生成树Kruskal算法3.Huffman编码4.单源点最短路径三、回溯法1.回溯法-n皇后问题2.子集和数四、动态规划1.数塔问题2.最长公共子序列3.求序列-2 11 -4 13 -5 -2的最大字段和4.求最长的…

Linux内核工作队列(workqueue)详解

1、为什么需要工作队列&#xff1f; 在内核代码中&#xff0c;经常会遇到不能或不合适去马上调用某个处理过程&#xff0c;此时希望将该工作推送给某个内核线程执行&#xff0c;这样做的原因有很多&#xff0c;比如&#xff1a; 中断触发了某个过程的执行条件&#xff0c;而该过…

电表485通讯抄表软件

电表485通讯主要是有线抄表&#xff0c;电表485通讯抄表软件选用485线传送数据&#xff0c;适宜集中化安装电表&#xff0c;下列给您具体说说电表485通讯抄表原理、应用领域等。 电表485通讯抄表原理 RS485抄表适用电表集中化安装场合&#xff0c;为节省RS485通讯线成本&…

VR渲染之Stereo Rendering解析

VR渲染的独特和最明显的方面之一是需要生成两个视图&#xff0c;左右眼睛各一个。我们需要这两个视图来为观众创建立体3D效果。 Multi Camera 传统上&#xff0c;VR应用程序必须绘制两次几何体--一次是左眼&#xff0c;一次是右眼。这基本上使非VR应用程序所需的处理翻了一番。…

揭秘百度智能测试在测试定位领域实践

作者 | intelligents 前几篇&#xff0c;分别介绍了测试活动测试输入、测试执行、测试分析、测试定位和测试评估五个步骤中测试输入、执行、分析、评估的智能化研究和实践&#xff0c;本章节重点介绍测试定位环节的智能化实践。 测试定位的主要作用是在构建失败或问题发生后&…

傻白探索Chiplet,国内外研究现状(六)

目录 一、概述 二、国外Chiplet历史与现状 2.1 AMD 2.1.1 EPYC&#xff08;Naples&#xff09; 2.1.2 EPYC&#xff08;Rome&#xff09; 2.1.3 EPYC&#xff08;Milan-X &#xff09; 2.1.4 Ryzen&#xff08;Matisse&#xff09; 2.2 苹果 2.3 Intel 2.3.1 Alter…

【大数据技术】Spark+Flume+Kafka实现商品实时交易数据统计分析实战(附源码)

需要源码请点赞关注收藏后评论区留言私信~~~ Flume、Kafka区别和侧重点 1&#xff09;Kafka 是一个非常通用的系统&#xff0c;你可以有许多生产者和消费者共享多个主题Topics。相比之下&#xff0c;Flume是一个专用工具被设计为旨在往HDFS&#xff0c;HBase等发送数据。它对H…

2022年我国江蓠行业现状:养殖面积、产量不断增长 进口量仍大于出口

根据观研报告网发布的《中国江蓠市场现状深度研究与发展前景预测报告&#xff08;2022-2029年&#xff09;》显示&#xff0c;江蓠属于“海藻”产业&#xff0c;为暖水性藻类&#xff0c;我国俗称 “龙须菜”、 “海菜”、 “蚝菜”。藻体紫褐色或紫黄色、绿色。 江蓠在热带、 …

Opencv(C++)笔记--霍夫变换检测直线、霍夫变换检测圆

目录 1--原理 2--Opencv API 3--实例代码 4--霍夫变换检测圆 1--原理 具体原理可参考 博客1 和 视频讲解1&#xff1b; 霍夫变换检测直线的核心思想是&#xff1a;在笛卡尔坐标系下&#xff0c;一条直线&#xff08;两个点&#xff08;x1, y1&#xff09;和&#xff08;x2,…

行业权威来揭秘,商用PC为什么首选12代酷睿

第12代酷睿处理器可以提供更卓越的性能&#xff0c;凭借架构先进性让商用台式机和笔记本电脑为用户带来更好的体验&#xff0c;帮助企业和员工效率倍增。 作者|九月 来源| PConline 想要让办公效率进一步提升&#xff0c;一台强大的PC设备是必不可少的生产力和内容创作工…

有什么适合零基础的人做的副业兼职

互联网上有很多套路。这是不可预防的。只要你敢贪婪&#xff0c;你就会陷入别人设计的陷阱。在业余时间做兼职应该是很多人的梦想&#xff0c;因为他们可以在有限的时间内赚更多的钱。很多人不知道的是&#xff0c;其实我们赚钱的渠道很多:比如网上发文章.短视频直播.我们媒体、…

基于SpringBoot+Mybatis框架的私人影院预约系统(附源码,包含数据库文件)

基于SpringBootMybatis框架的私人影院预约系统&#xff0c;附源码&#xff0c;包含数据库文件。 非常完整的一个项目&#xff0c;希望能对大家有帮助哈。 本系统的完整源码以及数据库文件都在文章结尾处&#xff0c;大家自行获取即可。 项目简介 该项目设计了基于SpringBoo…

Spring MVC—Spring MVC概述

文章目录Java web的发展历史一.Model I和Model II1.Model I开发模式2.Model II开发模式二. MVC模式SpringMVC 的工作原理和流程springmvc 的拦截器Spring和SpringMVC的区别————————————————————————————————Java web的发展历史 一.Model I和M…

VS Code debug调试时无法查看变量内容【已解决】

问题场景&#xff1a;新换成的vscode编译软件&#xff0c;但是在debug调试时发现与QtCreator不同&#xff0c;无法直接查看变量&#xff0c;显示的都是地址或其他。 比如&#xff1a;QString或QStringList无法查看具体的内容&#xff0c;正常是这样显示的&#xff0c;反正我不…

Linux神器——vim

目录 一、vim基本概念 二、vim基本操作 三、vim正常模式命令集 四、vim末行模式命令集 五、vim操作总结 六、vim界面配置 vi/vim的区别简单点来说&#xff0c;它们都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本&#xff0c;它不仅兼容vi的所有指令&#xff0c;而…

上班15年后,普通程序员能实现财富自由吗?

对于职业生涯还没有开挂的普通程序员来说&#xff0c;有可能实现财务自由吗&#xff1f; 先来说下财务自由的最低标准 北上广深&#xff1a;身价3000万&#xff0c;含房产1000万、现金2000万。 杭州、南京、成都等二线城市&#xff1a;身价1500万&#xff0c;含500万房产、现…

集成底座双K8S集群扩展升级方案

集成底座方案是应用于企业信息化建设的集成整合阶段&#xff0c;通过建立统一、标准、柔性、可复用、可扩展的IT架构&#xff0c;解决企业信息化建设过程中缺乏整体规划、集成整合难度大、安全管控不到位等问题&#xff0c;强化企业信息化的架构建设、集成整合、数据治理、安全…

某鱼兼职并不是那么好做,钱也不是漫天要价

文章目录一、背景二、雇主的期望2.1、jinja2代码三、题主的期望3.1、删除功能3.2、前端体现3.3、留言列表实现降序3.4、效果显示四、总结一、背景 上周某鱼推送过来的单子多到题主应接不暇&#xff0c;不得已拒绝了几单&#xff0c;但是接下来的单子呢又不那么顺利&#xff0c;…

提速3.7倍!何恺明团队再发新作,更快更高效的FLIP模型:通过Masking扩展语言-图像预训练(附论文原文下载)

原创/文 BFT机器人 研究论文地址&#xff1a;https://arxiv.org/abs/2212.00794 计算机视觉和深度学习领域大神何恺明携团队再发新作&#xff01;论文围绕近来火热的CLIP&#xff08;Contrastive Language-Image Pre-Training&#xff09;模型展开研究&#xff0c;并提出了一种…

Python怎么进行时区的转换

pytz 是一个用于处理时区的 Python 库,它为 Python 提供了对时区的支持。 它提供了大量的时区信息&#xff0c;包括时区名称、偏移量、是否使用夏令时等。你可以使用 pytz 库来处理本地时间、UTC 时间和其他时区之间的转换。 它提供了许多函数来帮助你处理时区相关的信息。 …