SpringBoot实现的大文件上传

前言

大文件分片上传和断点续传是为了解决在网络传输过程中可能遇到的问题,以提高文件传输的效率和稳定性。

  • 首先,大文件分片上传是将大文件分割成较小的片段进行上传。这样做的好处是可以减少单个文件的传输时间,因为较小的文件片段更容易快速上传到目标服务器。同时,如果在传输过程中出现错误或中断,只需要重新上传出现问题的文件片段,而不需要重新上传整个文件,从而减少了传输的时间和带宽消耗。
  • 其次,断点续传是指在文件传输过程中,如果传输被中断或者发生错误,可以从上一次中断的地方继续传输,而不是从头开始。这对于大文件的传输尤为重要,因为传输一个大文件可能需要较长的时间,而中断可能是由网络问题、电源故障、软件崩溃或其他因素引起的。断点续传功能允许用户在中断后恢复传输,而无需重新开始,节省了时间和资源。

大文件分片上传和断点续传在以下情况下尤为重要:

  1. 低带宽网络环境:在网络速度较慢或不稳定的情况下,将大文件分割为较小的片段进行上传可以降低传输的时间和失败的风险。
  2. 大文件传输:对于大文件,一次性完整上传可能需要很长时间,而且中途出现问题时需要重新传输整个文件,因此将文件分割并实现断点续传功能可以提高效率和可靠性。
  3. 网络中断或传输错误:网络中断、电源故障或软件崩溃等因素可能导致文件传输中断,断点续传功能可以从中断处恢复,避免重新传输整个文件。
  4. 多用户并发上传:在有多个用户同时上传文件的情况下,分片上传和断点续传可以减少对服务器资源的占用,提高并发传输的效率。

前端

采用百度的webuploader,在file.html中引用webuploader.js、jquery.js

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>大文件上传下载</title>
    <link rel="stylesheet" type="text/css" href="webuploader.css">
    <script src="jquery.js"></script>
    <script src="webuploader.js"></script>
    <style>
        #upload-container {
            width: 100px;
            height: 50px;
            background: #94d3e7;
            padding-bottom: 10px;
        }
    </style>
</head>
<body>

<div id="upload-container"><span>文件拖拽上传</span></div>

<button id="picker" style="margin-top: 20px">分片上传</button>

<div id="upload-list"></div>

<hr/>
<a href="/file/download" >普通下载</a>
<hr/>
<a href="/file/downloads" target="_blank">分片下载</a>

</body>
<script>
    $('#upload-container').click(function (event) {
        $("#picker").find('input').click();
    });
    // 初始化上传组件
    const uploader = WebUploader.create({
        auto: true,
        swf: 'Uploader.swf', // swf文件路径
        server: '/file/upload', // 上传接口
        dnd: '#upload-container',
        pick: '#picker',  // 内部根据当前运行创建
        multiple: true,     // 选择多个
        chunked: true,      // 开启分片
        threads: 8,        // 并发数,默认 3
        chunkRetry: 8,         // 如果遇到网络错误,重新上传次数
        method: 'POST',
        fileSizeLimit: 1024 * 1024 * 1024 * 10, // 文件总大小为10G
        fileSingleSizeLimit: 1024 * 1024 * 1024 * 1,  // 单个文件大小最大为1G
        fileVal: 'upload'
    });
    // 入队之前触发事件
    uploader.on("beforeFileQueued", function (file) {
        // 获取文件后缀
        console.log(file.name);
    });
    // 当有文件被添加进队列的时候
    uploader.on('fileQueued', function (file) {
        $('#upload-list').append( '<div id="' + file.id + '" class="item">' +
            '<h4 class="info">' + file.name + '</h4>' +
            '<p class="state">等待上传...</p>' +
            '</div>' );
    });
    // 文件上传过程中创建进度条实时显示。
    uploader.on('uploadProgress', function (file, percentage) {
        var $li = $('#' + file.id),
            $percent = $li.find('.progress .progress-bar');
        // 避免重复创建
        if (!$percent.length) {
            $percent = $('<div class="progress progress-striped active">' +
                '<div class="progress-bar" role="progressbar" style="width: 0%">' +
                '</div>' +
                '</div>').appendTo($li).find('.progress-bar');
        }
        $li.find('p.state').text('上传中');
        $percent.css('width', percentage * 100 + '%');
    });
    uploader.on( 'uploadSuccess', function( file ) {
        $( '#'+file.id ).find('p.state').text('已上传');
    });

    uploader.on( 'uploadError', function( file ) {
        $( '#'+file.id ).find('p.state').text('上传出错');
    });

    uploader.on( 'uploadComplete', function( file ) {
        $( '#'+file.id ).find('.progress').fadeOut();
    });

</script>
</html>

后端

1.Pom文件添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/>
    </parent>

    <groupId>com.liyh</groupId>
    <artifactId>springboot-file</artifactId>
    <version>0.0.1</version>
    <name>springboot-file</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

        <!-- 做断点下载使用 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
2.yml配置文件
# 配置服务端口
server:
  port: 8018

spring:
  servlet:
    multipart:
      # Spring Boot中有默认的文件上传组件,在使用ServletFileUpload时需要关闭Spring Boot的默认配置
      enabled: false
      # 设置单个文件大小
      max-file-size: 1GB
      # 设置单次请求文件的总大小
      max-request-size: 10GB

3..编写测试接口controller

package com.liyh.controller;

import com.liyh.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 文件上传测试接口
 *
 * @author liyh
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private FileService fileService;

    /**
     * 单个文件上传,支持断点续传
     */
    @PostMapping("/upload")
    public void upload(HttpServletRequest request, HttpServletResponse response) {
        try {
            fileService.upload(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 普通文件下载
     */
    @GetMapping("/download")
    public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
        fileService.download(request, response);
    }

    /**
     * 分片文件下载
     */
    @GetMapping("/downloads")
    public String downloads() throws IOException {
        fileService.downloads();
        return "下载成功";
    }

}

4.编写service 

package com.liyh.service;

import com.liyh.entity.DownloadFileInfo;
import com.liyh.entity.FileInfo;
import com.liyh.entity.UploadFileInfo;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class FileService {

    /**
     * 编码
     */
    private static final String UTF_8 = "UTF-8";

    /**
     * 文件上传路径(当前项目路径下,也可配置固定路径)
     */
    private String uploadPath = System.getProperty("user.dir") + "/springboot-file/upload/";

    /**
     * 下载指定文件
     */
    private String downloadFile = "D:\\Download\\git.exe";

    /**
     * 文件下载地址(当前项目路径下,也可配置固定路径)
     */
    private String downloadPath = System.getProperty("user.dir") + "/springboot-file/download/";

    /**
     * 分片下载每一片大小为50M
     */
    private static final Long PER_SLICE = 1024 * 1024 * 50L;

    /**
     * 定义分片下载线程池
     */
    private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    /**
     * final string
     */
    private static final String RANGE = "Range";

    /**
     * 上传文件
     */
    public void upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 获取ServletFileUpload
        ServletFileUpload servletFileUpload = getServletFileUpload();
        List<FileItem> items = servletFileUpload.parseRequest(request);
        // 获取文件信息
        UploadFileInfo uploadFileInfo = getFileInfo(items);
        // 写入临时文件
        writeTempFile(items, uploadFileInfo);
        // 判断是否合并
        mergeFile(uploadFileInfo);
        // 返回结果
        response.setCharacterEncoding(UTF_8);
        response.getWriter().write("上传成功");
    }

    /**
     * 获取ServletFileUpload
     */
    private ServletFileUpload getServletFileUpload() {
        // 设置缓冲区大小,先读到内存里在从内存写
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(1024);
        File file = new File(uploadPath);
        // 如果文件夹不存在则创建
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
        factory.setRepository(file);
        // 解析
        ServletFileUpload upload = new ServletFileUpload(factory);
        // 设置单个大小与最大大小
        upload.setFileSizeMax(1 * 1024 * 1024 * 1024L);
        upload.setSizeMax(10 * 1024 * 1024 * 1024L);
        return upload;
    }

    /**
     * 获取文件信息
     *
     * @param items
     * @return
     * @throws UnsupportedEncodingException
     */
    private UploadFileInfo getFileInfo(List<FileItem> items) throws UnsupportedEncodingException {
        UploadFileInfo uploadFileInfo = new UploadFileInfo();
        for (FileItem item : items) {
            if (item.isFormField()) {
                // 获取分片数据
                if ("chunk".equals(item.getFieldName())) {
                    uploadFileInfo.setCurrentChunk(Integer.parseInt(item.getString(UTF_8)));
                }
                if ("chunks".equals(item.getFieldName())) {
                    uploadFileInfo.setChunks(Integer.parseInt(item.getString(UTF_8)));
                }
                if ("name".equals(item.getFieldName())) {
                    uploadFileInfo.setFileName(item.getString(UTF_8));
                }
            }
        }
        return uploadFileInfo;
    }

    /**
     * 写入临时文件
     *
     * @param items
     * @param uploadFileInfo
     * @throws Exception
     */
    private void writeTempFile(List<FileItem> items, UploadFileInfo uploadFileInfo) throws Exception {
        // 获取文件基本信息后
        for (FileItem item : items) {
            if (!item.isFormField()) {
                // 有分片需要临时目录
                String tempFileName = uploadFileInfo.getFileName();
                if (StringUtils.isNotBlank(tempFileName)) {
                    if (uploadFileInfo.getCurrentChunk() != null) {
                        tempFileName = uploadFileInfo.getCurrentChunk() + "_" + uploadFileInfo.getFileName();
                    }
                    // 判断文件是否存在
                    File tempFile = new File(uploadPath, tempFileName);
                    // 断点续传,判断文件是否存在,若存在则不传
                    if (!tempFile.exists()) {
                        item.write(tempFile);
                    }
                }
            }
        }
    }

    /**
     * 判断是否合并
     *
     * @param uploadFileInfo
     * @throws IOException
     * @throws InterruptedException
     */
    private void mergeFile(UploadFileInfo uploadFileInfo) throws IOException, InterruptedException {
        Integer currentChunk = uploadFileInfo.getCurrentChunk();
        Integer chunks = uploadFileInfo.getChunks();
        String fileName = uploadFileInfo.getFileName();
        // 如果当前分片等于总分片那么合并文件
        if (currentChunk != null && chunks != null && currentChunk.equals(chunks - 1)) {
            File tempFile = new File(uploadPath, fileName);
            try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(tempFile))) {
                // 根据之前命名规则找到所有分片
                for (int i = 0; i < chunks; i++) {
                    File file = new File(uploadPath, i + "_" + fileName);
                    // 并发情况,需要判断所有,因为可能最后一个分片传完,之前有的还没传完
                    while (!file.exists()) {
                        // 不存在休眠100毫秒后在重新判断
                        Thread.sleep(100);
                    }
                    // 分片存在,读入数组中
                    byte[] bytes = FileUtils.readFileToByteArray(file);
                    os.write(bytes);
                    os.flush();
                    file.delete();
                }
                os.flush();
            }
        }
    }

    /**
     * 文件下载
     *
     * @param request
     * @param response
     * @throws IOException
     */
    public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 获取文件
        File file = new File(downloadFile);
        // 获取下载文件信息
        DownloadFileInfo downloadFileInfo = getDownloadFileInfo(file.length(), request, response);
        // 设置响应头
        setResponse(response, file.getName(), downloadFileInfo);
        // 下载文件
        try (InputStream is = new BufferedInputStream(new FileInputStream(file));
             OutputStream os = new BufferedOutputStream(response.getOutputStream())) {
            // 跳过已经读取文件
            is.skip(downloadFileInfo.getPos());
            byte[] buffer = new byte[1024];
            long sum = 0;
            // 读取
            while (sum < downloadFileInfo.getRangeLength()) {
                int length = is.read(buffer, 0, (downloadFileInfo.getRangeLength() - sum) <= buffer.length ? (int) (downloadFileInfo.getRangeLength() - sum) : buffer.length);
                sum = sum + length;
                os.write(buffer, 0, length);
            }
        }

    }

    /**
     * 有两个map,我要去判断里面相同键的值一致不一致,除了双重for循环,有没有别的好办法
     */
    private DownloadFileInfo getDownloadFileInfo(long fSize, HttpServletRequest request, HttpServletResponse response) {
        long pos = 0;
        long last = fSize - 1;
        // 判断前端是否需要分片下载
        if (request.getHeader(RANGE) != null) {
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String numRange = request.getHeader(RANGE).replace("bytes=", "");
            String[] strRange = numRange.split("-");
            if (strRange.length == 2) {
                pos = Long.parseLong(strRange[0].trim());
                last = Long.parseLong(strRange[1].trim());
                // 若结束字节超出文件大小,取文件大小
                if (last > fSize - 1) {
                    last = fSize - 1;
                }
            } else {
                // 若只给一个长度,开始位置一直到结束
                pos = Long.parseLong(numRange.replace("-", "").trim());
            }
        }
        long rangeLength = last - pos + 1;
        String contentRange = "bytes " + pos + "-" + last + "/" + fSize;
        return new DownloadFileInfo(fSize, pos, last, rangeLength, contentRange);
    }

    /**
     * 分片下载
     *
     * @throws IOException
     */
    public void downloads() throws IOException {
        File file = new File(downloadPath);
        // 如果文件夹不存在则创建
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
        // 探测下载,获取文件相关信息
        FileInfo fileInfoDto = sliceDownload(1, 10, -1, null);
        // 如果不为空,执行分片下载
        if (fileInfoDto != null) {
            // 计算有多少分片
            long pages = fileInfoDto.getFileSize() / PER_SLICE;
            // 适配最后一个分片
            for (long i = 0; i <= pages; i++) {
                long start = i * PER_SLICE;
                long end = (i + 1) * PER_SLICE - 1;
                executorService.execute(new SliceDownloadRunnable(start, end, i, fileInfoDto.getFileName()));
            }
        }
    }

    /**
     * 分片下载
     *
     * @param start 分片起始位置
     * @param end   分片结束位置
     * @param page  第几个分片, page=-1时是探测下载
     */
    private FileInfo sliceDownload(long start, long end, long page, String fName) throws IOException {
        // 断点下载
        File file = new File(downloadPath, page + "-" + fName);
        // 如果当前文件已经存在,并且不是探测任务,并且文件的长度等于分片的大小,那么不用下载当前文件
        if (file.exists() && page != -1 && file.length() == PER_SLICE) {
            return null;
        }
        // 创建HttpClient
        HttpClient client = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://localhost:8018/file/download");
        httpGet.setHeader(RANGE, "bytes=" + start + "-" + end);
        HttpResponse httpResponse = client.execute(httpGet);
        String fSize = httpResponse.getFirstHeader("fSize").getValue();
        fName = URLDecoder.decode(httpResponse.getFirstHeader("fName").getValue(), UTF_8);
        HttpEntity entity = httpResponse.getEntity();
        // 下载
        try (InputStream is = entity.getContent();
             FileOutputStream fos = new FileOutputStream(file)) {
            byte[] buffer = new byte[1024];
            int ch;
            while ((ch = is.read(buffer)) != -1) {
                fos.write(buffer, 0, ch);
            }
            fos.flush();
        }
        // 判断是否是最后一个分片,如果是那么合并
        if (end - Long.parseLong(fSize) > 0) {
            mergeFile(fName, page);
        }
        return new FileInfo(Long.parseLong(fSize), fName);
    }

    private void mergeFile(String fName, long page) throws IOException {
        File file = new File(downloadPath, fName);

        try (BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
            for (int i = 0; i <= page; i++) {
                File tempFile = new File(downloadPath, i + "-" + fName);
                // 文件不存在或文件没写完
                while (!tempFile.exists() || (i != page && tempFile.length() < PER_SLICE)) {
                    Thread.sleep(100);
                }
                byte[] bytes = FileUtils.readFileToByteArray(tempFile);
                os.write(bytes);
                os.flush();
                tempFile.delete();
            }
            // 删除文件
            File f = new File(downloadPath, "-1" + "-null");
            if (f.exists()) {
                f.delete();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class SliceDownloadRunnable implements Runnable {
        private final long start;
        private final long end;
        private final long page;
        private final String fName;

        private SliceDownloadRunnable(long start, long end, long page, String fName) {
            this.start = start;
            this.end = end;
            this.page = page;
            this.fName = fName;
        }

        @Override
        public void run() {
            try {
                sliceDownload(start, end, page, fName);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 设置响应头
     */
    private void setResponse(HttpServletResponse response, String fileName, DownloadFileInfo downloadFileInfo) throws UnsupportedEncodingException {
        response.setCharacterEncoding(UTF_8);
        response.setContentType("application/x-download");
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, UTF_8));
        // 支持分片下载
        response.setHeader("Accept-Range", "bytes");
        response.setHeader("fSize", String.valueOf(downloadFileInfo.getFSize()));
        response.setHeader("fName", URLEncoder.encode(fileName, UTF_8));
        // range响应头
        response.setHeader("Content-Range", downloadFileInfo.getContentRange());
        response.setHeader("Content-Length", String.valueOf(downloadFileInfo.getRangeLength()));
    }

}

5..编写上传文件实体类、文件信息实体类和下载文件实体类

package com.liyh.entity;

import lombok.Data;

@Data
public class UploadFileInfo {

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 上传文件会有多个分片,记录当前为那个分片
     */
    private Integer currentChunk;

    /**
     * 总分片数
     */
    private Integer chunks;

}
package com.liyh.entity;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileInfo {

    private long fileSize;

    private String fileName;

}
package com.liyh.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class DownloadFileInfo {

    /**
     * 文件总大小
     */
    private long fSize;

    /**
     * 断点起始位置
     */
    private long pos;

    /**
     * 断点结束位置
     */
    private long last;

    /**
     * rang响应
     */
    private long rangeLength;

    /**
     * range响应
     */
    private String contentRange;

}

运行效果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【秋招突围】2024届秋招笔试-小红书笔试题-第二套-三语言题解(Java/Cpp/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系计划跟新各公司春秋招的笔试题 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f4e7; 清隆这边…

高频小信号放大器的分类与质量指标

目录 分类 质量指标 增益 通频带 选择性 稳定性 噪声系数 分类 质量指标 增益 电压与功率的放大倍数。 通频带 放大效果比较好的频率范围。 选择性 放大目标信号以滤除其他信号的综合能力。 稳定性 噪声系数

chatglm4本地部署详解

下载地址 模型下载地址&#xff1a;GitHub - THUDM/GLM-4: GLM-4 series: Open Multilingual Multimodal Chat LMs | 开源多语言多模态对话模型 已经训练好的数据下载地址&#xff1a; https://huggingface.co/THUDM/glm-4-9b-chat-1m/tree/main 测试主机配置 cpu&#xff1a;E…

pdf转图片,pdf转图片在线转

pdf转图片的方法&#xff0c;对于许多人来说可能是一个稍显陌生的操作。然而&#xff0c;在日常生活和工作中&#xff0c;我们有时确实需要将pdf文件转换为图片格式&#xff0c;以便于在特定的场合或平台上进行分享、展示或编辑。以下&#xff0c;我们将详细介绍一个pdf转成图片…

【网络安全的神秘世界】AppScan安装及使用指南

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 https://www.hcl-software.com/appscan AppScan是一种综合型漏洞扫描工具&#xff0c;采用SaaS解决方案&#xff0c;它将所以测试功能整合到一个服务中&a…

Java基础——网络编程(一)

初识网络编程 网络编程&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件…… BS架构的优缺点&#xff1a; 1、不需要开发客户端&#xff0c;只需要页面服务端 2、…

Redis 键空间迭代 Scan

引言 在平时线上Redis维护工作中&#xff0c;有时候需要从Redis实例成千上万的key中找出特定前缀的key列表来手动处理数据&#xff0c;可能是修改他的值&#xff0c;也可能是删除key。 Redis提供了一个简单暴力的指令keys用来列出所有满足特定正则字符串规则的key。 127.0.0…

26.1 WEB框架介绍

1. Web应用程序 1.1 应用程序有两种模式 应用程序的架构模式主要分为两种: C/S (客户端/服务器端)和B/S(浏览器/服务器端). * 1. C/S模式, 即客户端/服务器模式(Client/Server Model): 是一种分布式计算模式.它将应用程序的功能划分为客户端和服务器端两部分.在这种模式下, 客…

几种经典排序算法

几种经典排序算法 插入排序折半插入排序法 选择排序冒泡排序希尔排序堆排序二路归并排序快速排序 在介绍排序之前&#xff0c;先来说说&#xff0c;研究不同的排序主要是要研究他们的哪些不同&#xff1a; 时间性能。即排序过程中元素之间的比较次数与元素移动次数。我们此次讨…

【最新鸿蒙应用开发】——鸿蒙中的“Slot插槽”?@BuilderParam

构建函数-BuilderParam 传递 UI 1. 引言 BuilderParam 该装饰器用于声明任意UI描述的一个元素&#xff0c;类似slot占位符。 简而言之&#xff1a;就是自定义组件允许外部传递 UI Entry Component struct Index {build() {Column({ space: 15 }) {SonCom() {// 直接传递进来…

IPv6 ND 协议功能概述

ND 协议功能概述 ND&#xff08;Neighbor Discovery&#xff0c;邻居发现&#xff09;协议是 IPv6 的一个关键协议&#xff0c;它综合了 IPv4 中的 ARP&#xff0c;ICMP 路由发现和 ICMP 重定向等协议&#xff0c;并对它们做了改进。 作为 IPv6 的基础性协议&#xff0c;ND 协…

ppt添加圆角矩形,并调整圆角弧度方法

一、背景 我们看的论文&#xff0c;许多好看的图都是用PPT做的&#xff0c;下面介绍用ppt添加圆角矩形&#xff0c;并调整圆角弧度方法。 二、ppt添加圆角矩形&#xff0c;并调整圆角弧度 添加矩形&#xff1a; 在顶部工具栏中&#xff0c;点击“插入”选项卡。 在“插图”…

冒泡排序知识点

排序的基本概念 排序是计算机内经常进行的一种操作&#xff0c;其目的是将一组“无序”的记录调整为“有序”的记录序列。 常用的排序例子 8 7 1 5 4 2 6 3 9 把上面的这个无序序列变为有序&#xff08;升序或者降序&#xff09;序列的过程。 1 2 3 4 5 6 7 8 9&#xff0…

Spring运维之boo项目表现层测试加载测试的专用配置属性以及在JUnit中启动web服务器发送虚拟请求

测试表现层的代码如何测试 加载测试的专用属性 首先写一个测试 假定我们进行测试的时候要加一些属性 要去修改一些属性 我们可以写一个只在本测试有效的测试 写在配置里 测试 打印输出 我们把配置文件里面的配置注释掉后 我们同样可以启动 package com.example.demo;impo…

代码随想录——组合总和Ⅱ(Leetcode 40)需要回顾

题目链接 回溯 本题的难点在于&#xff1a;集合&#xff08;数组candidates&#xff09;有重复元素&#xff0c;但还不能有重复的组合。 思想&#xff1a;元素在同一个组合内是可以重复的&#xff0c;怎么重复都没事&#xff0c;但两个组合不能相同。所以要去重的是同一树…

购物车店铺列表查询流程

购物车店铺列表查询流程 购物车结算流程图

嵌入式门槛高不高,工资怎么样?

一般来说&#xff0c;嵌入式岗位的准入门槛其实并不是特别高。通常情况下&#xff0c;只要能够熟练掌握 C 语言编程以及单片机相关知识&#xff0c;就能够去制作一些较为简单的电子产品&#xff0c;由此可见其门槛相对而言是比较低的&#xff0c;相应的薪水可能也不会特别高。 …

I2C 总线通信技术基础

1.0 I2C 技术基础 使用总线的目的&#xff1a;采用串行总线技术可以使系统的硬件设计大大简化、系统的体积减小、可靠性提高&#xff0c;同时&#xff0c;系统的更改和扩充变的极为容易。 通信中常用的串行拓展总线 I2C&#xff08;Inter-Integrated Circuit &#xff09;总线…

C语言程序设计-6 循环控制

C语言程序设计-6 循环控制 循环结构是程序中一种很重要的结构。其特点是&#xff0c;在给定条件成立时&#xff0c;反复执行某程序 段&#xff0c;直到条件不成立为止。给定的条件称为循环条件&#xff0c;反复执行的程序段称为循环体。&#xff23;语 言提供了多种循环语句&a…

计算机网络知识点全面总结回顾

物理层 OSI模型&#xff1a;数据链路层&#xff08;流量控制&#xff09;&#xff0c;从传输层开始端到端&#xff1b;每一层的元素都称为实体&#xff0c;同一层的是对等实体&#xff1b;三个重要概念&#xff1a;服务&#xff08;下层为上层提供调用&#xff09;&#xff0c…