文件上传 分片上传

news2025/1/19 14:46:42

分片上传则是将一个大文件分割成多个小块分别上传,最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件,提高上传效率;同时,如果某一部分上传失败,只需要重传这一部分,不影响其他部分。

初步实现

后端代码

/**
 * 分片上传
 *
 * @param file 上传的文件
 * @param start 文件开始上传的位置
 * @param fileName 文件名称
 * @return  上传结果
 */
@PostMapping("/fragmentUpload")
@ResponseBody
public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {
    try {
        // 检查上传目录是否存在,如果不存在则创建
        File directory = new File(uploadPath);
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 设置上传文件的目标路径
        File targetFile = new File(uploadPath +File.separator+ fileName);
        // 创建 RandomAccessFile 对象以便进行文件的随机读写操作
        RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
        // 获取 RandomAccessFile 对应的 FileChannel
        FileChannel channel = randomAccessFile.getChannel();
        // 设置文件通道的位置,即从哪里开始写入文件内容
        channel.position(start);
        // 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置
        channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());

        // 关闭文件通道和 RandomAccessFile 对象
        channel.close();
        randomAccessFile.close();

        // 返回上传成功的响应
        return AjaxResult.success("上传成功");
    } catch (Exception e) {
        // 捕获异常并返回上传失败的响应
        return AjaxResult.error("上传失败");
    }
}

/**
 * 检测文件是否存在
 * 如果文件存在,则返回已经存在的文件大小。
 * 如果文件不存在,则返回 0,表示前端从头开始上传该文件。
 * @param filename
 * @return
 */
@GetMapping("/checkFile")
@ResponseBody
public AjaxResult checkFile(@RequestParam("filename") String filename) {
    File file = new File(uploadPath+File.separator + filename);
    if (file.exists()) {
        return AjaxResult.success(file.length());
    } else {
        return AjaxResult.success(0L);
    }
}

前端

var prefix = ctx + "/kuroshiro/file-upload";

// 每次上传大小
const chunkSize = 1 * 1024 * 1024;

/**
 * 开始上传
 */
function startUpload(type) {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];
    if (!file) {
        alert("请选择文件");
        return;
    }
    if(type == 1){
	    checkFile(filename).then(start => {
	         uploadFile(file, start,Math.min(start + chunkSize, file.size));
	     })
	 }
}

/**
 * 检查是否上传过
 * @param filename
 * @returns {Promise<unknown>}
 */
function checkFile(filename) {
    return $fetch(prefix+`/checkFile?filename=${filename}`);
}

/**
 * 开始分片上传
 * @param file 文件
 * @param start 开始位置
 * @param end 结束位置
 */
function uploadFile(file, start,end) {
    if(start < end){
        const chunk = file.slice(start, end);
        const formData = new FormData();
        formData.append('file', chunk);
        formData.append('start', start);
        formData.append('fileName', file.name);

        $fetch(prefix+'/fragmentUpload', {
            method: 'POST',
            body: formData
        }).then(response => {
            console.log(`分片 ${start} - ${end} 上传成功`);
            // 递归调用
            uploadFile(file,end,Math.min(end + chunkSize, file.size))
        })
    }

}

function $fetch(url,requestInit){
        return new Promise((resolve, reject) => {
            fetch(url,requestInit).then(response => {
                if (!response.ok) {
                    throw new Error('请求失败');
                }
                return response.json();
            }).then(data => {
            if (data.code === 0) {
                resolve(data.data);
            } else {
                console.error(data.msg);
                reject(data.msg)
            }
            }).catch(error => {
                console.error(error);
                reject(error)
            });
        });
    }

以上虽然实现的分片上传,但是它是某种意义上来说还是与整体上传差不多,它是一段一段的上传,某段上传失败后,后续的就不会再继续上传;不过比起整体上传来说,它会保存之前上传的内容,下一个上传时,从之前上传的位置接着上传。不用整体上传。下面进行优化。

优化

首先,之前的分片上传,后端是直接写入了一个文件中了,所以只能顺序的上传写入,虽然可以保存上传出错之前的内容,但是整体上看来是速度也不行。
优化逻辑:把分片按顺序单独保存下来,等到所有分片都上传成功后,把所有分片合并成文件。这样上传的时候就不用等着上一个上传成功才上传下一个了。

后端代码

/**
* 分片上传
 * @param file 文件
 * @param chunkIndex 分片下标
 */
@PostMapping("/uploadChunk")
@ResponseBody
public AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {

    String uploadDirectory = chunkUploadPath+File.separator+fileName;
    File directory = new File(uploadDirectory);
    if (!directory.exists()||directory.isFile()) {
        directory.mkdirs();
    }
    String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;
    try (OutputStream os = new FileOutputStream(filePath)) {
        os.write(file.getBytes());
        return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");
    }catch (Exception e){
        // 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误
        File chunkFile = new File(filePath);
        if(chunkFile.exists()) chunkFile.delete();
        e.printStackTrace();
        return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");
    }

}


/**
 * 检测分片是否存在
 * 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传
 * 如果文件不存在,则返回空集合,表示前端从头开始上传该文件
 * @param fileName
 * @return
 */
@GetMapping("/checkChunk")
@ResponseBody
public AjaxResult checkChunk(@RequestParam("fileName") String fileName) {
    String uploadDirectory = chunkUploadPath+File.separator+fileName;
    List<Integer> list = new ArrayList<>();
    File file = new File(uploadDirectory);
    // 文件目录不存在
    if(!file.exists()||file.isFile()) return AjaxResult.success(list);

    File[] files = file.listFiles();
    // 文件目录下没有分片文件
    if(files == null) return AjaxResult.success(list);

    // 返回存在分片下标集合
    return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));
}

    // 合并文件分片
    @PostMapping("/mergeChunks")
    @ResponseBody
    public AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {
        String uploadDirectory = chunkUploadPath+File.separator+fileName;
        String mergedFilePath = uploadPath +File.separator+ fileName;

        try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {
            for (int i = 0; i < totalChunks; i++) {
                Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);
                Files.copy(chunkFilePath, os);
                Files.delete(chunkFilePath);
            }
            return AjaxResult.success();
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }

    }

前端代码

/**
 * 开始上传
 */
function startUpload(type) {
    const fileInput = document.getElementById('fileInput');
    const file = fileInput.files[0];
    if (!file) {
        alert("请选择文件");
        return;
    }

    
     const filename = file.name;
     if(type == 1){
         checkFile(filename).then(start => {
             uploadFile(file, start,Math.min(start + chunkSize, file.size));
         })
     }
     if(type == 2){
         checkChunk(filename).then(arr => {
             uploadChunk(file, arr);
         })
     }
}

 /**
* 切割文件为多个分片
* @param file
* @returns {*[]}
*/
function sliceFile(file) {
   const chunks = [];
   let offset = 0;

   while (offset < file.size) {
       const chunk = file.slice(offset, offset + chunkSize);
       chunks.push(chunk);
       offset += chunkSize;
   }

   return chunks;
}
/**
* 检查是否上传过
* @param filename
* @returns {Promise<unknown>}
*/
function checkChunk(filename) {
   return $fetch(prefix+`/checkChunk?fileName=${filename}`);
}

/**
* 开始分片上传
* @param file 文件
* @param exists 存在的分片下标
*/
function uploadChunk(file,exists) {
   const chunkArr = sliceFile(file);
   Promise.all(chunkArr.map((chunk, index) => {
       if(!exists.includes(index)){
           const formData = new FormData();
           formData.append('file', chunk);
           formData.append('fileName', file.name);
           formData.append('chunkIndex', index);

           return $fetch(prefix+'/uploadChunk', {
               method: 'POST',
               body: formData
           });
       }
   })).then(uploadRes=> {
       // 合并分片
       const formData = new FormData();
       formData.append('fileName', file.name);
       formData.append('totalChunks', chunkArr.length);
       $fetch(prefix + '/mergeChunks', {
           method: 'POST',
           body:formData,
       }).then(mergeRes=>{
           console.log("合并成功")
       });
   });
}

以上优化后所有分片可以同时上传,所有分片上传都成功后进行合并。

最后是完整代码

@Controller()
@RequestMapping("/kuroshiro/file-upload")
public class FileUploadController {
    private String prefix = "kuroshiro/fragmentUpload";
    // 文件保存目录
    private final String uploadPath = RuoYiConfig.getUploadPath();
    // 分片保存目录
    private final String chunkUploadPath = uploadPath+File.separator+"chunks";

    /**
     * demo
     * @return
     */
    @GetMapping("/demo")
    public String demo() {
        return prefix+"/demo";
    }

    /**
     * 分片上传
     *
     * @param file 上传的文件
     * @param start 文件开始上传的位置
     * @param fileName 文件名称
     * @return  上传结果
     */
    @PostMapping("/fragmentUpload")
    @ResponseBody
    public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {
        try {
            // 检查上传目录是否存在,如果不存在则创建
            File directory = new File(uploadPath);
            if (!directory.exists()) {
                directory.mkdirs();
            }

            // 设置上传文件的目标路径
            File targetFile = new File(uploadPath +File.separator+ fileName);
            // 创建 RandomAccessFile 对象以便进行文件的随机读写操作
            RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
            // 获取 RandomAccessFile 对应的 FileChannel
            FileChannel channel = randomAccessFile.getChannel();
            // 设置文件通道的位置,即从哪里开始写入文件内容
            channel.position(start);
            // 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置
            channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());

            // 关闭文件通道和 RandomAccessFile 对象
            channel.close();
            randomAccessFile.close();

            // 返回上传成功的响应
            return AjaxResult.success("上传成功");
        } catch (Exception e) {
            // 捕获异常并返回上传失败的响应
            return AjaxResult.error("上传失败");
        }
    }

    /**
     * 检测文件是否存在
     * 如果文件存在,则返回已经存在的文件大小。
     * 如果文件不存在,则返回 0,表示前端从头开始上传该文件。
     * @param filename
     * @return
     */
    @GetMapping("/checkFile")
    @ResponseBody
    public AjaxResult checkFile(@RequestParam("filename") String filename) {
        File file = new File(uploadPath+File.separator + filename);
        if (file.exists()) {
            return AjaxResult.success(file.length());
        } else {
            return AjaxResult.success(0L);
        }
    }




    /**
     * 分片上传
     * @param file 文件
     * @param chunkIndex 分片下标
     */
    @PostMapping("/uploadChunk")
    @ResponseBody
    public AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {

        String uploadDirectory = chunkUploadPath+File.separator+fileName;
        File directory = new File(uploadDirectory);
        if (!directory.exists()||directory.isFile()) {
            directory.mkdirs();
        }
        String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;
        try (OutputStream os = new FileOutputStream(filePath)) {
            os.write(file.getBytes());
            return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");
        }catch (Exception e){
            // 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误
            File chunkFile = new File(filePath);
            if(chunkFile.exists()) chunkFile.delete();
            e.printStackTrace();
            return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");
        }

    }


    /**
     * 检测分片是否存在
     * 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传
     * 如果文件不存在,则返回空集合,表示前端从头开始上传该文件
     * @param fileName
     * @return
     */
    @GetMapping("/checkChunk")
    @ResponseBody
    public AjaxResult checkChunk(@RequestParam("fileName") String fileName) {
        String uploadDirectory = chunkUploadPath+File.separator+fileName;
        List<Integer> list = new ArrayList<>();
        File file = new File(uploadDirectory);
        // 文件目录不存在
        if(!file.exists()||file.isFile()) return AjaxResult.success(list);

        File[] files = file.listFiles();
        // 文件目录下没有分片文件
        if(files == null) return AjaxResult.success(list);

        // 返回存在分片下标集合
        return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));
    }

    // 合并文件分片
    @PostMapping("/mergeChunks")
    @ResponseBody
    public AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {
        String uploadDirectory = chunkUploadPath+File.separator+fileName;
        String mergedFilePath = uploadPath +File.separator+ fileName;

        try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {
            for (int i = 0; i < totalChunks; i++) {
                Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);
                Files.copy(chunkFilePath, os);
                Files.delete(chunkFilePath);
            }
            File chunkDir = new File(uploadDirectory);
            if (chunkDir.exists()) chunkDir.delete();
            return AjaxResult.success();
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }

    }

}

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <th:block th:include="include :: header('分片上传')" />
</head>
<body class="gray-bg">
<div class="container-div" id="chunk-div">
    <div class="row">
        <div class="col-sm-12 search-collapse">
            <form id="formId">
                <div class="select-list">
                    <ul>
                        <li>
                            <label>选择文件:</label>
                            <input type="file" id="fileInput"/>
                        </li>

                        <li>
                            <a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(1)"><i class="fa fa-upload"></i>&nbsp;开始上传1</a>
                            <a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(2)"><i class="fa fa-upload"></i>&nbsp;开始上传2</a>
                        </li>
                    </ul>
                </div>
            </form>
        </div>
        <div class="col-sm-12" style="padding-left: 0;">
            <div class="ibox">
                <div class="ibox-content">
                    <h3>上传进度</h3>
                    <ul class="sortable-list connectList agile-list" v-if="uploadMsg">
                        <li v-for="item in uploadMsg" :class="item.status+'-element'">
                            {{item.title}}
                            <div class="agile-detail">
                                {{item.result}}
                            </div>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
    var prefix = ctx + "/kuroshiro/file-upload";

    new Vue({
        el: '#chunk-div',
        data: {
            // 每次上传大小
            chunkSize: 100 * 1024 * 1024,
            uploadMsg:{},
            startTime:0,
        },

        methods: {
            /**
             * 开始上传
             */
            startUpload: function(type){
                const fileInput = document.getElementById('fileInput');
                const file = fileInput.files[0];
                if (!file) {
                    alert("请选择文件");
                    return;
                }

                const filename = file.name;
                this.uploadMsg = {};
                this.startTime = (new Date()).getTime();
                Vue.set(this.uploadMsg, 'checkMsg', {
                    title:`文件检测`,
                    result: "检测中... ...",
                    status:"info"
                });
                if(type == 1){
                    this.checkFile(filename).then(start => {
                        this.uploadMsg['checkMsg'].result = `检测成功:已存在文件,大小为 ${start}`
                        this.uploadFile(file, start,Math.min(start + this.chunkSize, file.size));
                    },err => {
                        this.uploadMsg['checkMsg'].result = `检测失败:${err}`
                    })
                }
                if(type == 2){
                    this.checkChunk(filename).then(arr => {
                        this.uploadMsg['checkMsg'].result = `检测成功:已存在文件分片 ${arr.length}`
                        this.uploadChunk(file, arr);
                    },err => {
                        this.uploadMsg['checkMsg'].result = `检测失败:${err}`
                        this.uploadMsg['checkMsg'].status = `info`
                    })
                }
            },
            /**
             * 检查是否上传过
             * @param filename
             * @returns {Promise<unknown>}
             */
            checkFile: function(filename) {
                return this.$fetch(prefix+`/checkFile?filename=${filename}`);
            },
            /**
             * 开始分片上传
             * @param file 文件
             * @param start 开始位置
             * @param end 结束位置
             */
            uploadFile: function(file, start,end) {
                if(start < end){
                    const chunk = file.slice(start, end);
                    const formData = new FormData();
                    formData.append('file', chunk);
                    formData.append('start', start);
                    formData.append('fileName', file.name);

                    Vue.set(this.uploadMsg, 'uploadMsg_'+start, {
                        title:`分片 ${start} - ${end} 上传`,
                        result: "上传中... ...",
                        status:"info"
                    });

                    this.$fetch(prefix+'/fragmentUpload', {
                        method: 'POST',
                        body: formData
                    }).then(response => {
                        this.uploadMsg['uploadMsg_'+start].result = `上传成功`;
                        // 递归调用
                        this.uploadFile(file,end,Math.min(end + this.chunkSize, file.size))
                    },err=>{
                        this.uploadMsg['uploadMsg_'+start].result = `上传失败:${err}`;
                        this.uploadMsg['uploadMsg_'+start].status = `danger`;
                    })
                }else{
                    this.uploadMsg['uploadSuccess'] = {
                        title:`文件已上传`,
                        result:`耗时:`+((new Date()).getTime()-this.startTime),
                        status:"info"
                    };
                }

            },
            /**
             * 切割文件为多个分片
             * @param file
             * @returns {*[]}
             */
            sliceFile: function(file) {
                const chunks = [];
                let offset = 0;

                while (offset < file.size) {
                    const chunk = file.slice(offset, offset + this.chunkSize);
                    chunks.push(chunk);
                    offset += this.chunkSize;
                }

                return chunks;
            },
            /**
             * 检查是否上传过
             * @param filename
             * @returns {Promise<unknown>}
             */
            checkChunk: function(filename) {
                return this.$fetch(prefix+`/checkChunk?fileName=${filename}`);
            },

            /**
             * 开始分片上传
             * @param file 文件
             * @param exists 存在的分片下标
             */
            uploadChunk: function(file,exists) {
                const chunkArr = this.sliceFile(file);
                Promise.all(chunkArr.map(async (chunk, index) => {
                    if (!exists.includes(index)) {
                        const formData = new FormData();
                        formData.append('file', chunk);
                        formData.append('fileName', file.name);
                        formData.append('chunkIndex', index);

                        Vue.set(this.uploadMsg, "upload_" + index, {
                            title: `分片 ${index + 1} 上传`,
                            result: "上传中... ...",
                            status: "info"
                        });
                        return new Promise((resolve, reject) => {
                            this.$fetch(prefix+'/uploadChunk', {
                                method: 'POST',
                                body: formData
                            }).then(res => {
                                resolve(res)
                                this.uploadMsg["upload_"+index].result = "上传成功";
                            },err => {
                                reject(err)
                                this.uploadMsg["upload_"+index].result = err;
                                this.uploadMsg["upload_"+index].status = "danger";
                            });
                        })

                    }
                })).then(uploadRes=> {
                    this.uploadMsg["uploadSuccess"] = {
                        title:`上传成功`,
                        result: "耗时:"+((new Date()).getTime()-this.startTime),
                        status:"info"
                    };
                    // 合并分片
                    const formData = new FormData();
                    formData.append('fileName', file.name);
                    formData.append('totalChunks', chunkArr.length);

                    Vue.set(this.uploadMsg, 'mergeChunks', {
                        title:`合并分片`,
                        result: "合并中... ...",
                        status:"info"
                    });
                    this.$fetch(prefix + '/mergeChunks', {
                        method: 'POST',
                        body:formData,
                    }).then(mergeRes=>{
                        this.uploadMsg["mergeChunks"].result = "合并成功";
                    },err => {
                        this.uploadMsg["mergeChunks"].result = `合并失败:${err}`;
                        this.uploadMsg["mergeChunks"].status = "danger";
                    });
                });

            },

            $fetch: function(url,requestInit){
                return new Promise((resolve, reject) => {
                    fetch(url,requestInit).then(response => {
                        if (!response.ok) {
                            throw new Error('请求失败');
                        }
                        return response.json();
                    }).then(data => {
                        if (data.code === 0) {
                            resolve(data.data);
                        } else {
                            reject(data.msg)
                        }
                    }).catch(error => {
                        reject(error)
                    });
                });
            },
        }
    });

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

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

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

相关文章

数字图像处理:实验二

任务一&#xff1a; 将不同像素&#xff08;32、64和256&#xff09;的原图像放大为像素大 小为1024*1024的图像&#xff08;图像自选&#xff09; 要求&#xff1a;1&#xff09;输出一幅图&#xff0c;该图包含六幅子图&#xff0c;第一排是原图&#xff0c;第 二排是对应放大…

WEB渗透技术研究与安全防御

目录 作品简介I IntroductionII 1 网络面临的主要威胁1 1.1 技术安全1 2 分析Web渗透技术2 2.1 Web渗透技术的概念2 2.2 Web漏洞产生的原因2 2.3 注入测试3 2.3.1 注入测试的攻击流程3 2.3.2 进行一次完整的Sql注入测试4 2.3.3 Cookie注入攻击11 3 安全防御方案设计…

使用 Thermal Desktop 进行航天器热分析

介绍 将航天器保持在运行温度下的轨道上是一个具有挑战性的问题。航天器需要处理太空非常寒冷的背景温度&#xff0c;同时还要管理来自内部组件、地球反照率和太阳辐射的高热负荷。航天器在轨道上可以进行的各种轨道机动使解决这个问题变得更加复杂。 Thermal Desktop 是一款…

乘联会:1月汽车零售预计175万辆 环比暴跌33.6%

快科技1月18日消息&#xff0c;据乘联会的初步推算&#xff0c;2025年1月狭义乘用车零售总市场规模预计将达到约175万辆左右。与去年同期相比&#xff0c;这一数据呈现了-14.6%的同比下降态势&#xff1b;而相较于上个月&#xff0c;则出现了-33.6%的环比暴跌情况。 为了更清晰…

SQL 递归 ---- WITH RECURSIVE 的用法

SQL 递归 ---- WITH RECURSIVE 的用法 开发中遇到了一个需求&#xff0c;传递一个父类id&#xff0c;获取父类的信息&#xff0c;同时获取其所有子类的信息。 首先想到的是通过程序中去递归查&#xff0c;但这种方法着实孬了一点&#xff0c;于是想&#xff0c;sql能不能递归查…

【机器学习实战入门项目】使用深度学习创建您自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…

windows 搭建flutter环境,开发windows程序

环境安装配置&#xff1a; 下载flutter sdk https://docs.flutter.dev/get-started/install/windows 下载到本地后&#xff0c;随便找个地方解压&#xff0c;然后配置下系统环境变量 编译windows程序本地需要安装vs2019或更新的开发环境 主要就这2步安装后就可以了&#xff0…

【Linux】15.Linux进程概念(4)

文章目录 程序地址空间前景回顾C语言空间布局图&#xff1a;代码1代码2代码3代码4代码5代码6代码7 程序地址空间前景回顾 历史核心问题&#xff1a; pid_t id fork(); if(id 0) else if(id>0) 为什么一个id可以放两个值呢&#xff1f;之前没有仔细讲。 C语言空间布局图&am…

一文读懂服务器的HBA卡

什么是 HBA 卡 HBA 卡&#xff0c;全称主机总线适配器&#xff08;Host Bus Adapter&#xff09; &#xff0c;是服务器与存储装置间的关键纽带&#xff0c;承担着输入 / 输出&#xff08;I/O&#xff09;处理及物理连接的重任。作为一种电路板或集成电路适配器&#xff0c;HBA…

oracle使用case when报错ORA-12704字符集不匹配原因分析及解决方法

问题概述 使用oracle的case when函数时&#xff0c;报错提示ORA-12704字符集不匹配&#xff0c;如下图&#xff0c;接下来分析报错原因并提出解决方法。 样例演示 现在有一个TESTTABLE表&#xff0c;本表包含的字段如下图所示&#xff0c;COL01字段是NVARCHAR2类型&#xff0…

Linux-----线程同步(条件变量)

目录 相关API restrict关键字 线程间条件切换函数 条件变量pthread_cond_t 案例 在前面的锁的基础上进一步提高线程同步效率&#xff0c;也就是两个线程只用锁去执行的话依然会存在资源竞争的情况&#xff0c;也就是抢锁&#xff0c;这里就需要在锁的这边加上限制&#xf…

每日进步一点点(网安)

今日练习题目是PHP反序列化&#xff0c;也学习一下说明是序列化和反序列化 1.PHP序列化 序列化是指将数据结构或对象转换为可传输或可储存的格式的过程。这通常需要将数据转换为字节流或者其他编码格式&#xff0c;以便在不同系统和应用程序之间进行传输或存储 在PHP中&…

Java-数据结构-二叉树习题(1)

对于二叉树的学习&#xff0c;主要的还是得多多练习~毕竟二叉树属于新的知识&#xff0c;并且也并不是线性结构&#xff0c;再加上经常使用递归的方法解决二叉树的问题&#xff0c;所以代码的具体流程还是无法看到的&#xff0c;只能通过画图想象&#xff0c;所以还是必须多加练…

(二)afsim第三方库编译(qt编译)

注意&#xff1a;源码编译的路径不能有中文否则报错&#xff0c;压缩包必须用官网下载的xz格式解压的才可以&#xff0c;否则sudo ./configure命令找不到 先编译openssl3.1.1软件包&#xff0c;否则编译的qt库将不支持network&#xff0c;相关库的编译(上文&#xff08;一&…

【QT用户登录与界面跳转】

【QT用户登录与界面跳转】 1.前言2. 项目设置3.设计登录界面3.1 login.pro参数3.2 界面设置3.2.1 登录界面3.2.2 串口主界面 4. 实现登录逻辑5.串口界面6.测试功能7.总结 1.前言 在Qt应用程序开发中&#xff0c;实现用户登录及界面跳转功能是构建交互式应用的重要步骤之一。下…

【docker踩坑记录】

docker踩坑记录 踩坑记录(持续更新中.......)docker images 权限问题 踩坑记录(持续更新中…) docker images 权限问题 permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head "http://%2Fvar%2Frun%2Fdocker.s…

搜维尔科技:Xsens人形机器人解决方案的优势

Xsens 致力于推动人形机器人技术的发展&#xff0c;塑造机器人与人类环境无缝融合的未来&#xff0c;通过创新精确和协作&#xff0c;协助生产和服务&#xff0c;改善人类生活和产业。 Xsens通过人形跟随捕捉详细的人体运动数据&#xff0c;使机器人能够学习类人的动作&#x…

国内微电子(集成电路)领域重点高校的特色与优势

本文旨在梳理国内微电子&#xff08;集成电路&#xff09;领域重点高校的特色与优势&#xff0c;为有志于从事相关领域的学生提供参考。文章将从学科特色、科研实力&#xff08;以ISSCC论文为参考之一&#xff09;、行业认可度等方面进行分析&#xff0c;并强调实验室、导师、研…

leetcode707-设计链表

leetcode 707 思路 本题也是用了虚拟头节点来进行解答&#xff0c;这样的好处是&#xff0c;不管是头节点还是中间的节点都可以当成是中间节点来处理&#xff0c;用同一套方法就可以进行处理&#xff0c;而不用考虑太多的边界条件。 下面题目中最主要的实现就是添加操作addA…

数据结构-栈队列OJ题

文章目录 一、有效的括号二、用队列实现栈三、用栈实现队列四、设计循环队列 一、有效的括号 (链接&#xff1a;ValidParentheses) 这道题用栈这种数据结构解决最好&#xff0c;因为栈有后进先出的性质。简单分析一下这道题&#xff1a;所给字符串不是空的也就是一定至少存在一…