IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 知识点21--springboot 文件切片上传 -> 正文阅读

[Java知识库]知识点21--springboot 文件切片上传

前面有一篇简单版的文件上传,是为了让大家知道文件上传是在干什么,但是在正式的开发中文件上传是一个稍微有些麻烦的东西,需要从页面层开发到数据层,如果你常常听人说文件上传会知道有一些相关的名词,比如切片、秒传、断点续传、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配置文件那些,大家自己把代码拉下来自己看就行

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:52:39  更:2022-12-25 10:56:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/26 23:53:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码