​Spring Boot 分片上传文件

news2024/12/24 8:25:35

在文件上传功能上,客户经常会问到文件上传大小的是否有限制或是上传大小能否设置,用户经常需要上传好几个G的资料文件,如图纸,视频等,并且需要在上传大文件过程中进行优化实时展现进度条,
进行技术评估后针对框架文件上传进行扩展升级,扩展接口支持大文件分片上传处理,减少服务器瞬时的内存压力,同一个文件上传失败后可以从成功上传分片位置进行断点续传,文件上传成功后再次上传无需等待达到秒传的效果,优化用户交互体验。

文件MD5计算
对于文件md5的计算我们使用spark-md5第三方库,大文件我们可以分片分别计算再合并节省时间。
但是经测试1G文件计算MD5需要20s左右的时间,所以经过优化我们抽取文件部分特征信息(文件第一片+文件最后一片+文件修改时间),来保证文件的相对唯一性,只需要2s左右,大大提高前端计算效率,对于前端文件内容块的读取我们需要使用html5的api中fileReader.readAsArrayBuffer方法,因为是异步触发,封装的方法提供一个回调函数进行使用。

createSimpleFileMD5(file, chunkSize, finishCaculate) {
                var fileReader = new FileReader();
                var blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
                var chunks = Math.ceil(file.size / chunkSize);
                var currentChunk = 0;
                var spark = new SparkMD5.ArrayBuffer();
                var startTime = new Date().getTime();
                loadNext();
                fileReader.onload = function() {
                    spark.append(this.result);
                    if (currentChunk == 0) {
                        currentChunk = chunks - 1;
                        loadNext();
                    } else {
                        var fileMD5 = hpMD5(spark.end() + file.lastModifiedDate);
                        finishCaculate(fileMD5)
                    }
                };

                functionloadNext() {
                    var start = currentChunk * chunkSize;
                    var end = start + chunkSize >= file.size ? file.size : start + chunkSize;
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                }
            }

文件分片切割
我们通过定义好文件分片大小,使用blob对象支持的file.slice方法切割文件,分片上传请求需要同步按顺序请求,因为使用了同步请求,前端ui会阻塞无法点击,需要开启worker线程进行操作,完成后通过postMessage方法传递消息给主页面通知ui进度条的更新,需要注意的是,worker线程方法不支持window对象,所以尽量不要使用第三方库,使用原生的XMLHttpRequest对象发起请求,需要的参数通过onmessage方法传递获取。

upload() {
                var file = document.getElementById("file").files[0];
                if (!file) {
                    alert("请选择需要上传的文件");
                    return;
                }
                if (file.size < pageData.chunkSize) {
                    alert("选择的文件请大于" + pageData.chunkSize / 1024 / 1024 + "M")
                }

                var filesize = file.size;
                var filename = file.name;
                pageData.chunkCount = Math.ceil(filesize / pageData.chunkSize);
                this.createSimpleFileMD5(file, pageData.chunkSize, function(fileMD5) {
                    console.log("计算文件MD:" + fileMD5);
                    pageData.showProgress = true;
                    var worker = new Worker('worker.js');
                    var param = {
                        token: GetTokenID(),
                        uploadUrl: uploadUrl,
                        filename: filename,
                        filesize: filesize,
                        fileMD5: fileMD5,
                        groupguid: pageData.groupguid1,
                        grouptype: pageData.grouptype1,
                        chunkCount: pageData.chunkCount,
                        chunkSize: pageData.chunkSize,
                        file: file
                    }
                    worker.onmessage = function(event) {
                        var workresult = event.data;
                        if (workresult.code == 0) {
                            pageData.percent = workresult.percent;
                            if (workresult.percent == 100) {
                                pageData.showProgress = false;
                                worker.terminate();
                            }
                        } else {
                            pageData.showProgress = false;
                            worker.terminate();
                        }

                    }
                    worker.postMessage(param);
                })
            }

worker.js执行方法如下:

functionFormAjax_Sync(token, data, url, success) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open("post", url, false);
    xmlHttp.setRequestHeader("token", token);
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.status == 200) {
            var result = JSON.parse(this.responseText);
            var status = this.status
            success(result, status);
        }
    };
    xmlHttp.send(data);

}

onmessage = function(evt) {
    var data = evt.data;
    console.log(data)
    //传递的参数
    var token = data.token
    var uploadUrl = data.uploadUrl
    var filename = data.filename
    var fileMD5 = data.fileMD5
    var groupguid = data.groupguid
    var grouptype = data.grouptype
    var chunkCount = data.chunkCount
    var chunkSize = data.chunkSize
    var filesize = data.filesize
    var filename = data.filename
    var file = data.file


    var start = 0;
    var end;
    var index = 0;
    var startTime = new Date().getTime();
    while (start < filesize) {
        end = start + chunkSize;
        if (end > filesize) {
            end = filesize;
        }
        var chunk = file.slice(start, end); //切割文件
        var formData = new FormData();
        formData.append("file", chunk, filename);
        formData.append("fileMD5", fileMD5);
        formData.append("chunkCount", chunkCount)
        formData.append("chunkIndex", index);
        formData.append("chunkSize", end - start);
        formData.append("groupguid", groupguid);
        formData.append("grouptype", grouptype);
        //上传文件
        FormAjax_Sync(token, formData, uploadUrl, function(result, status) {
            var code = 0;
            var percent = 0;
            if (result.code == 0) {
                console.log("分片共" + chunkCount + "个" + ",已成功上传第" + index + "个")
                percent = parseInt((parseInt(formData.get("chunkIndex")) + 1) * 100 / chunkCount);
            } else {
                filesize = -1;
                code = -1
                console.log("分片第" + index + "个上传失败")
            }
            self.postMessage({ code: code, percent: percent });
        })
        start = end;
        index++;
    }
    console.log("上传分片总时间:" + (new Date().getTime() - startTime));
    console.log("分片完成");
}

文件分片接收
前端文件分片处理完毕后,接下来我们详细介绍下后端文件接受处理的方案,分片处理需要支持用户随时中断上传与文件重复上传,我们新建表f_attachchunk来记录文件分片的详细信息,表结构设计如下:

CREATE TABLE `f_attachchunk` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `ChunkGuid` varchar(50) NOT NULL,
  `FileMD5` varchar(100) DEFAULT NULL,
  `FileName` varchar(200) DEFAULT NULL,
  `ChunkSize` int(11) DEFAULT NULL,
  `ChunkCount` int(11) DEFAULT NULL,
  `ChunkIndex` int(11) DEFAULT NULL,
  `ChunkFilePath` varchar(500) DEFAULT NULL,
  `UploadUserGuid` varchar(50) DEFAULT NULL,
  `UploadUserName` varchar(100) DEFAULT NULL,
  `UploadDate` datetime DEFAULT NULL,
  `UploadOSSID` varchar(200) DEFAULT NULL,
  `UploadOSSChunkInfo` varchar(1000) DEFAULT NULL,
  `ChunkType` varchar(50) DEFAULT NULL,
  `MergeStatus` int(11) DEFAULT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=237 DEFAULT CHARSET=utf8mb4;
FileMD5:文件MD5唯一标识文件;
FileName:文件名称;
ChunkSize:分片大小;
ChunkCount:分片总数量;
ChunkIndex:分片对应序号;
ChunkFilePath:分片存储路径(本地存储文件方案使用);
UploadUserGuid:上传人主键;
UploadUserName:上传人姓名;
UploadDate:上传人日期;
UploadOSSID:分片上传批次ID(云存储方案使用);
UploadOSSChunkInfo:分片上传单片信息(云存储方案使用);
ChunkType:分片存储方式(本地存储,阿里云,华为云,Minio标识);
MergeStatus:分片合并状态(未合并,已合并)。
文件分片存储后端一共分为三步:
检查分片=》保存分片=》合并分片
我们这里先以本地文件存储为例讲解,云存储思路一致,后续会提供对应使用的api方法。

检查分片
检查分片以数据库文件分片记录的FIleMD5与ChunkIndex组合来确定分片的唯一性,因为本地分片temp文件是作为临时文件存储,可能会出现手动清除施放磁盘空间的问题,所以数据库存在记录我们还需要对应的检查实际文件情况。

boolean existChunk = false;
            AttachChunkDO dbChunk = attachChunkService.checkExistChunk(fileMD5, chunkIndex, "Local");
            if (dbChunk != null) {
                File chunkFile = new File(dbChunk.getChunkFilePath());
                if (chunkFile.exists()) {
                    if (chunkFile.length() == chunkSize) {
                        existChunk = true;
                    } else {
                        //删除数据库记录
                        attachChunkService.delete(dbChunk.getChunkGuid());
                    }

                } else {
                    //删除数据库记录
                    attachChunkService.delete(dbChunk.getChunkGuid());
                }
            }

保存分片
保存分片分为两块,文件存储到本地,成功后数据库插入对应分片信息。


//获取配置中附件上传文件夹
            String filePath = frameConfig.getAttachChunkPath() + "/" + fileMD5 + "/";
            //根据附件guid创建文件夹
            File targetFile = new File(filePath);
            if (!targetFile.exists()) {
                targetFile.mkdirs();
            }
            if (!existChunk) {
                //保存文件到文件夹
                String chunkFileName = fileMD5 + "-" + chunkIndex + ".temp";
                FileUtil.uploadFile(FileUtil.convertStreamToByte(fileContent), filePath, chunkFileName);
                //插入chunk表
                AttachChunkDO attachChunkDO = new AttachChunkDO(fileMD5, fileName, chunkSize, chunkCount, chunkIndex, filePath + chunkFileName, "Local");
                attachChunkService.insert(attachChunkDO);
            }

合并分片
在上传分片方法中,如果当前分片是最后一片,上传完毕后进行文件合并工作,同时进行数据库合并状态的更新,下一次同一个文件上传时我们可以直接拷贝之前合并过的文件作为新附件,减少合并这一步骤的I/O操作,合并文件我们采用BufferedOutputStream与BufferedInputStream两个对象,固定缓冲区大小。

if (chunkIndex == chunkCount - 1) {
                //合并文件
                String merageFileFolder = frameConfig.getAttachPath() + groupType + "/" + attachGuid;
                File attachFolder = new File(merageFileFolder);
                if (!attachFolder.exists()) {
                    attachFolder.mkdirs();
                }
                String merageFilePath = merageFileFolder + "/" + fileName;
                merageFile(fileMD5, merageFilePath);
                attachChunkService.updateMergeStatusToFinish(fileMD5);

                //插入到附件库
                //设置附件唯一guid
                attachGuid = CommonUtil.getNewGuid();
                attachmentDO.setAttguid(attachGuid);
                attachmentService.insert(attachmentDO);
            }
      //java fhadmin.cn     publicvoidmerageFile(String fileMD5, String targetFilePath)throws Exception {
        String merageFilePath = frameConfig.getAttachChunkPath()+"/"+fileMD5+"/"+fileMD5+".temp";
        File merageFile = new File(merageFilePath);
        if(!merageFile.exists()){
            BufferedOutputStream destOutputStream = new BufferedOutputStream(new FileOutputStream(merageFilePath));
            List<AttachChunkDO> attachChunkDOList = attachChunkService.selectListByFileMD5(fileMD5, "Local");
            for (AttachChunkDO attachChunkDO : attachChunkDOList) {
                File file = new File(attachChunkDO.getChunkFilePath());
                byte[] fileBuffer = new byte[1024 * 1024 * 5];//文件读写缓存
                int readBytesLength = 0; //每次读取字节数
                BufferedInputStream sourceInputStream = new BufferedInputStream(new FileInputStream(file));
                while ((readBytesLength = sourceInputStream.read(fileBuffer)) != -1) {
                    destOutputStream.write(fileBuffer, 0, readBytesLength);
                }
                sourceInputStream.close();
            }
            destOutputStream.flush();
            destOutputStream.close();
        }
        FileUtil.copyFile(merageFilePath,targetFilePath);
    }

云文件分片上传
云文件上传与本地文件上传的区别就是,分片文件直接上传到云端,再调用云存储api进行文件合并与文件拷贝,数据库相关记录与检查差异不大。

阿里云OSS
上传分片前需要生成该文件的分片上传组标识uploadid

 public String getUplaodOSSID(String key){
        key = "chunk/" + key + "/" + key;
        TenantParams.attach appConfig = getAttach();
        OSSClient ossClient = InitOSS(appConfig);
        String bucketName = appConfig.getBucketname_auth();
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
        String uploadId = upresult.getUploadId();
        ossClient.shutdown();
        return uploadId;
    }

上传分片时需要指定uploadid,同时我们要将返回的分片信息PartETag序列化保存数据库,用于后续的文件合并。

  public String uploadChunk(InputStream stream,String key, int chunkIndex, int chunkSize, String uploadId){
        key = "chunk/" + key + "/" + key;
        String result = "";
        try{
            TenantParams.attach appConfig = getAttach();
            OSSClient ossClient = InitOSS(appConfig);
            String bucketName = appConfig.getBucketname_auth();
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(key);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setInputStream(stream);
            // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
            uploadPartRequest.setPartSize(chunkSize);
            // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。
            uploadPartRequest.setPartNumber(chunkIndex+1);
            // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            PartETag partETag = uploadPartResult.getPartETag();
            result = JSON.toJSONString(partETag);
            ossClient.shutdown();
        }catch (Exception e){
            logger.error("OSS上传文件Chunk失败:" + e.getMessage());
        }
        return result;
    }

合并分片时通过传递保存分片的PartETag对象数组进行操作,为了附件独立唯一性我们不直接使用合并后的文件,通过api进行文件拷贝副本使用。

public boolean merageFile(String uploadId, List<PartETag> chunkInfoList,String key,AttachmentDO attachmentDO,boolean checkMerge){
        key = "chunk/" + key + "/" + key;
        boolean result = true;
       try{
           TenantParams.attach appConfig = getAttach();
           OSSClient ossClient = InitOSS(appConfig);
           String bucketName = appConfig.getBucketname_auth();
           if(!checkMerge){
               CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkInfoList);
               CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
           }
           String attachKey = getKey(attachmentDO);
           ossClient.copyObject(bucketName,key,bucketName,attachKey);
           ossClient.shutdown();
       }catch (Exception e){
           e.printStackTrace();
           logger.error("OSS合并文件失败:" + e.getMessage());
           result = false;
       }
        return result;
    }

华为云OBS
华为云api与阿里云api大致相同,只有个别参数名称不同,直接上代码:

 public String getUplaodOSSID(String key) throws Exception {
        key = "chunk/" + key + "/" + key;
        TenantParams.attach appConfig = getAttach();
        ObsClient obsClient = InitOBS(appConfig);
        String bucketName = appConfig.getBucketname_auth();
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
        String uploadId = result.getUploadId();
        obsClient.close();
        return uploadId;
    }

    public String uploadChunk(InputStream stream, String key, int chunkIndex, int chunkSize, String uploadId) {
        key = "chunk/" + key + "/" + key;
        String result = "";
        try {
            TenantParams.attach appConfig = getAttach();
            ObsClient obsClient = InitOBS(appConfig);
            String bucketName = appConfig.getBucketname_auth();
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setObjectKey(key);
            uploadPartRequest.setInput(stream);
            uploadPartRequest.setOffset(chunkIndex * chunkSize);
            // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
            uploadPartRequest.setPartSize((long) chunkSize);
            // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出此范围,OSS将返回InvalidArgument错误码。
            uploadPartRequest.setPartNumber(chunkIndex + 1);
            // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = obsClient.uploadPart(uploadPartRequest);
            PartEtag partETag = new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber());
            result = JSON.toJSONString(partETag);

            obsClient.close();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("OBS上传文件Chunk:" + e.getMessage());
        }
        return result;
    }

    public boolean merageFile(String uploadId, List<PartEtag> chunkInfoList, String key, AttachmentDO attachmentDO, boolean checkMerge) {
        key = "chunk/" + key + "/" + key;
        boolean result = true;
        try {
            TenantParams.attach appConfig = getAttach();
            ObsClient obsClient = InitOBS(appConfig);
            String bucketName = appConfig.getBucketname_auth();
            if (!checkMerge) {
                CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, key, uploadId, chunkInfoList);
                obsClient.completeMultipartUpload(request);
            }
            String attachKey = getKey(attachmentDO);
            obsClient.copyObject(bucketName, key, bucketName, attachKey);

            obsClient.close();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("OBS合并文件失败:" + e.getMessage());
            result = false;
        }
        return result;
    }

Minio
文件存储Minio应用比较广泛,框架也同时支持了自己独立部署的Minio文件存储系统,Minio没有对应的分片上传api支持,我们可以在上传完分片文件后,使用composeObject方法进行文件的合并。

publicbooleanuploadChunk(InputStream stream, String key, int chunkIndex){
        boolean result = true;
        try {
            MinioClient minioClient = InitMinio();
            String bucketName = frameConfig.getMinio_bucknetname();
            PutObjectOptions option = new PutObjectOptions(stream.available(), -1);
            key = "chunk/" + key + "/" + key;
            minioClient.putObject(bucketName, key + "-" + chunkIndex, stream, option);
        } catch (Exception e) {
            logger.error("Minio上传Chunk文件失败:" + e.getMessage());
            result = false;
        }
        return result;
    }

    publicbooleanmerageFile(String key, int chunkCount, AttachmentDO attachmentDO, boolean checkMerge){
        boolean result = true;
        try {
            MinioClient minioClient = InitMinio();
            String bucketName = frameConfig.getMinio_bucknetname();
            key = "chunk/" + key + "/" + key;
            if (!checkMerge) {
                List<ComposeSource> sourceObjectList = new ArrayList<ComposeSource>();
                for (int i = 0; i < chunkCount; i++) {
                    ComposeSource composeSource = ComposeSource.builder().bucket(bucketName).object(key + "-" + i).build();
                    sourceObjectList.add(composeSource);
                }
                minioClient.composeObject(ComposeObjectArgs.builder().bucket(bucketName).object(key).sources(sourceObjectList).build());
            }
            String attachKey = getKey(attachmentDO);
            minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .bucket(bucketName)
                            .object(attachKey)
                            .source(
                                    CopySource.builder()
                                            .bucket(bucketName)
                                            .object(key)
                                            .build())
                            .build());
        } catch (Exception e) {
            logger.error("Minio合并文件失败:" + e.getMessage());
            result = false;
        }
        return result;
    }

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

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

相关文章

Jquery ajax 进行网络请求,同步阻塞引起的UI线程阻塞 (loading图片不显示 )

jax重新获取数据刷新页面功能&#xff0c;因为ajax属于耗时操作&#xff0c;想在获取数据且加载页面时显示加载遮罩层&#xff0c;结果发现了ajax的好多坑。 ajax 执行http网络请示时时&#xff0c;让遮罩层显示&#xff0c;ajax加载完毕后遮罩层消失。 因为我想让loadChart()…

机器学习第13天:模型性能评估指标

☁️主页 Nowl &#x1f525;专栏《机器学习实战》 《机器学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 文章目录 交叉验证 保留交叉验证 k-折交叉验证 留一交叉验证 混淆矩阵 精度与召回率 介绍 精度 召回率 区别 使用代码 偏差与方差 介绍 区…

【阿里云】图像识别 智能分类识别 项目开发(一)

语音模块和阿里云图像识别结合 环境准备 代码实现 编译运行 写个shell脚本用于杀死运行的进程 语音模块和阿里云图像识别结合 使用语音模块和摄像头在香橙派上做垃圾智能分类识别 语音控制摄像下载上传阿里云解析功能点实现 环境准备 将语音模块接在UART5的位置 在orange…

AMEYA360:村田首款1608M尺寸/100V静电容量1µF的MLCC实现商品化

株式会社村田制作所成功开发了用于基站、服务器和数据中心48V线路的多层陶瓷电容器“GRM188D72A105KE01”并已量产。该产品在1608M(1.60.8mm)尺寸、100V的额定电压下可实现1μF的超大静电容量(村田调查数据&#xff0c;截至2023年11月20日)。目前可向村田申请免费样品。 随着5G…

【PyQt】(自定义类)阴影遮罩-升级版

这是之前发的代码(自定义类)阴影遮罩的升级版。 升级就升级在&#xff0c;优化了对非矩形控件的遮盖效果&#xff0c;例如圆角按钮&#xff0c;以及默认方法不满足时可以传入其他的遮盖方法。 自定义阴影遮罩Mask&#xff1a; class Mask(QWidget):__excludeNone__colorNonecl…

UVA437 巴比伦塔 The Tower of Babylon

UVA437 巴比伦塔 The Tower of Babylon 题面翻译 题目描述 你可能已经听说过巴比伦塔的传说。现在这个传说的许多细节已经被遗忘。所以本着本场比赛的教育性质&#xff0c;我们现在会告诉你整个传说&#xff1a; 巴比伦人有 n n n 种长方形方块&#xff0c;每种有无限个&a…

加载minio中存储的静态文件html,不显示样式与js

问题描述:点击链接获取的就是纯静态文件,但是通过浏览器可以看到明明加载了css文件与js文件 原因:仔细看你会发现加载css文件显示的contentType:text/html文件,原来是minio上传文件时将所有文件的contentType设置成了text/html 要在上传时指定文件,根据文章的类型指定的Conten…

Unity 讯飞 之 讯飞星火大模型的简单封装和使用(补充讯飞大模型识图功能)

Unity 讯飞 之 讯飞星火大模型的简单封装和使用&#xff08;补充讯飞大模型识图功能&#xff09; 目录 Unity 讯飞 之 讯飞星火大模型的简单封装和使用&#xff08;补充讯飞大模型识图功能&#xff09; 一、简单介绍 二、实现原理 三、注意事项 四、效果预览 五、案例简单…

【Element】el-switch开关 点击弹窗确认框时状态先改变----点击弹窗取消框失效

一、背景 需求&#xff1a;在列表中添加定期出账的开关按钮&#xff0c;点击开关时&#xff0c;原来的状态不改变&#xff0c;弹出弹窗&#xff1b;点击弹窗取消按钮&#xff1a;状态不改变&#xff0c;点击弹窗确定按钮&#xff1a;状态改变&#xff0c;并调取列表数据刷新页…

python 如何利用everything的能力快速搜索兴趣文件夹

演示代码 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/23 17:10 file: python 如何通过everything搜索兴趣文档.py desc: xxxxxx """# region 引入必要的依赖 import os模块名 DebugInfo try:from Debu…

使用 Python 和 NLTK 进行文本摘要

一、说明 文本摘要是一种自然语言处理技术&#xff0c;允许用户将大量文本总结为小块&#xff0c;而不会丢失任何重要信息。本文介绍NLP中使用Gensim和Sumy实现文本摘要的步骤。 二、为什么要总结文本&#xff1f; 互联网包含大量信息&#xff0c;而且每秒都在增加。文本摘要可…

Java实现求最大值

1 问题 接收用户输入的3个整数&#xff0c;如何将最大值作为结果输出。 2 方法 采用“截图文字代码”的方式描述。 引入输入包调用main()函数&#xff0c;提示并接收用户输入的3个整数&#xff0c;并交由变量a b c来保存。对接收的3个数据进行比较&#xff0c;先比较a和b&#…

『许战海战略文库』打造技术品牌:企业的新成长引擎

引言&#xff1a;随着全球化和技术的快速发展,企业面临的竞争压力也越来越大。在这种环境下,仅仅拥有技术优势是不够的,如何将技术转化为品牌的核心竞争力&#xff0c;从而实现企业的长期和持续发展,成为许多企业面临的核心问题。 产业技术品牌不仅代表企业技术实力&#xff0c…

Vue项目实战之一----实现分类弹框效果

效果图 实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script src"js/vue.js"></script><!-- 引入样式 --><link rel"stylesheet&qu…

p12 63.删除无头结点无头指针的循环链表中所有值为x的结点 桂林电子科技大学2015年 (c语言代码实现)注释详解

本题代码如下 void delete(linklist* L, int x) {lnode* p *L, * q *L;while (p->next ! q)// 从第一个结点开始遍历链表&#xff0c;直到尾结点的前一个结点{if (p->next->data x)//判断是否等于x{lnode* r p->next;//将r指向x的位置p->next r->next;…

热门话题解析:pytest测试用例顺序问题解决方案!

前言 上一篇文章我们讲了在pytest中测试用例的命名规则&#xff0c;那么在pytest中又是以怎样的顺序执行测试用例的呢&#xff1f; 在unittest框架中&#xff0c;默认按照ACSII码的顺序加载测试用例并执行&#xff0c;顺序为&#xff1a;09、AZ、a~z&#xff0c;测试目录、测…

Redis key的类型以及命令

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

常见树种(贵州省):016杜鹃、含笑、桃金娘、金丝桃、珍珠花、观光木

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、杜鹃 …

C语言——指针(二)

&#x1f4dd;前言 上篇文章C语言——指针&#xff08;一&#xff09;初步讲解了&#xff1a; 1&#xff0c;指针与指针变量 2&#xff0c;指针变量的基本使用&#xff08;如何定义&#xff0c;初始化&#xff0c;引用&#xff09; 这篇文章我们进一步探讨&#xff0c;使用指针…

【漏洞复现】金蝶云星空管理中心 ScpSupRegHandler接口存在任意文件上传漏洞 附POC

漏洞描述 金蝶云星空是一款云端企业资源管理(ERP)软件,为企业提供财务管理、供应链管理以及业务流程管理等一体化解决方案。金蝶云星空聚焦多组织,多利润中心的大中型企业,以 “开放、标准、社交”三大特性为数字经济时代的企业提供开放的 ERP 云平台。服务涵盖:财务、供…