学成在线笔记+踩坑(5)——【媒资模块】上传视频,断点续传

news2024/11/24 3:27:44

目录

5 上传视频 

5.1 媒资管理页面上传视频流程预览

5.2 断点续传技术

5.2.1 什么是断点续传

5.2.2 测试分块与合并,RandomAccessFile随机流

5.2.3 视频上传流程

5.2.4 测试minio合并文件

5.3 接口定义,检查文件/分块、上传分块、合并分块

5.4 上传分块Service

5.4.1 检查文件和分块

5.4.2 上传分块

5.4.3 完善接口层

报错、Tomcat默认上传文件大小限制为1M,yml配置文件上传限制 

5.5 合并分块开发

5.5.1 service开发

5.5.2 接口层完善

5.5.2 合并分块测试


5 上传视频 

5.1 媒资管理页面上传视频流程预览

1、教学机构人员进入媒资管理列表查询自己上传的媒资文件。

点击“媒资管理”

进入媒资管理列表页面查询本机构上传的媒资文件。

2、教育机构用户在"媒资管理"页面中点击 "上传视频" 按钮。

点击“上传视频”打开上传页面

3、选择要上传的文件,自动执行文件上传。

4、视频上传成功会自动处理,处理完成可以预览视频。

5.2 断点续传技术

5.2.1 什么是断点续传

如果一个大文件快上传完了网断了没有上传完成,需要客户重新上传,用户体验非常差。

断点续传:

在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载,断点续传可以节省操作时间

流程如下:

1、前端上传前先把文件分成块

2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传

3、各分块上传完成最后在服务端合并文件

5.2.2 测试分块与合并,RandomAccessFile随机流

文件分块的流程如下:

  • 1、获取源文件长度
  • 2、根据设定的分块文件的大小计算出块数
  • 3、从源文件读数据依次向每一个块文件写数据。

测试代码如下:

随机流RandomAccessFile:

是Java 输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,它既可以读取文件内容,也可以向文件输出数据。与普通的输入/输出流不同的是,RandomAccessFile支持"随机访问"的方式,程序可以直接跳转到文件的任意地方来读写数据。


package com.xuecheng.media;
/**
 * @description 大文件处理测试
 */
public class BigFileTest {

    //分块测试,将视频按每块5m进行分块
    @Test
    public void testChunk() throws IOException {
        //源文件
        File sourceFile = new File("D:\\develop\\upload\\1.项目背景.mp4");
        //分块文件存储路径。这个路径得是真实存在的,否则会报错找不到路径
        String chunkFilePath = "D:\\develop\\upload\\chunk\\";
        //分块文件大小。这里设置成5M
        int chunkSize = 1024 * 1024 * 5;
        //分块文件个数。Math.ceil是向上取整
        int chunkNum = (int) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
        //使用随机流从源文件读数据,向分块文件中写数据
        RandomAccessFile raf_r = new RandomAccessFile(sourceFile, "r");
        //缓存区
        byte[] bytes = new byte[1024];
        //遍历所有块
        for (int i = 0; i < chunkNum; i++) {
            //“D:\develop\upload\chunk\1”、“D:\develop\upload\chunk\2”...
            File chunkFile = new File(chunkFilePath + i);
            //分块文件写入流
            RandomAccessFile raf_rw = new RandomAccessFile(chunkFile, "rw");
            int len = -1;
            //每次写满一个字节数组
            while ((len=raf_r.read(bytes))!=-1){
                raf_rw.write(bytes,0,len);
                //当分块大小超过5m时停止在这一块写数据。不加这句的话会出现第一块大小和源文件一样,其余块大小都为0
                if(chunkFile.length()>=chunkSize){
                    break;
                }
            }
            raf_rw.close();
        }
        raf_r.close();
    }
}

运行测试: 

文件合并流程:

1、找到要合并的文件并按文件合并的先后进行排序。

2、创建合并文件

3、依次从合并的文件中读取数据向合并文件写入数

文件合并的测试代码 :

    //将分块进行合并
    @Test
    public void testMerge() throws IOException {
        //块文件目录
        File chunkFolder = new File("D:\\develop\\upload\\chunk");
        //源文件
        File sourceFile = new File("D:\\develop\\upload\\1.项目背景.mp4");
        //合并后的文件
        File mergeFile = new File("D:\\develop\\upload\\1.项目背景_2.mp4");

        //1.取出所有分块文件
        File[] files = chunkFolder.listFiles();
        //2.将数组转成list,以便于排序
        List<File> filesList = Arrays.asList(files);
        //3.对分块文件排序
        Collections.sort(filesList, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                return Integer.parseInt(o1.getName())-Integer.parseInt(o2.getName());
            }
        });
        //向合并文件写的流
        RandomAccessFile raf_rw = new RandomAccessFile(mergeFile, "rw");
        //缓存区
        byte[] bytes = new byte[1024];
        //4.遍历每个分块,向合并的目标文件写
        for (File file : filesList) {
            //读分块的流
            RandomAccessFile raf_r = new RandomAccessFile(file, "r");
            int len = -1;
            while ((len=raf_r.read(bytes))!=-1){
                raf_rw.write(bytes,0,len);
            }
            raf_r.close();

        }
        raf_rw.close();
        //合并文件完成后对合并的文件md5校验
        FileInputStream fileInputStream_merge = new FileInputStream(mergeFile);
        FileInputStream fileInputStream_source = new FileInputStream(sourceFile);
        String md5_merge = DigestUtils.md5Hex(fileInputStream_merge);
        String md5_source = DigestUtils.md5Hex(fileInputStream_source);
        if(md5_merge.equals(md5_source)){
            System.out.println("文件合并成功");
        }

    }

5.2.3 视频上传流程

下图是上传视频的整体流程:

1、前端对文件进行分块

2、前端上传分块文件前请求媒资服务检查原文件和分块文件是否存在,如果已经存在则不需要再上传。

检查文件存在依据:是媒资主键为文件的md5值,两个文件md5值相等,则是一个文件。

3、如果分块文件不存在则前端开始上传

4、前端请求媒资服务上传分块。

5、媒资服务将分块上传至MinIO

注意:minio文件和文件的分块存储路径都应该尽量避免存在根目录下,这里将文件名前两位设成路径。

6、前端将分块上传完毕请求媒资服务合并分块。

7、媒资服务判断分块上传完成则请求MinIO合并文件

8、合并完成校验合并后的文件是否完整,如果完整则上传完成并删除分块,否则删除文件。

5.2.4 测试minio合并文件

1、将分块文件上传至minio

//将分块文件上传至minio
@Test
public void uploadChunk(){
    String chunkFolderPath = "D:\\develop\\upload\\chunk\\";
    File chunkFolder = new File(chunkFolderPath);
    //获取所有分块文件。listFiles()方法返回该文件路径下所有文件数组
    File[] files = chunkFolder.listFiles();
    //将分块文件上传至minio
    for (int i = 0; i < files.length; i++) {
        try {
           UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder().bucket("testbucket").object("chunk/" + i).filename(files[i].getAbsolutePath()).build();
            minioClient.uploadObject(uploadObjectArgs);
            System.out.println("上传分块成功"+i);
        } catch (Exception e) {
          e.printStackTrace();
        }
    }

}

2、通过minio的合并文件

//合并文件,要求分块文件最小5M
@Test
public void test_merge() throws Exception {
    List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
            .limit(6)
            .map(i -> ComposeSource.builder()
                    .bucket("testbucket")
                    .object("chunk/".concat(Integer.toString(i)))
                    .build())
            .collect(Collectors.toList());

    ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                    .bucket("testbucket").object("merge01.mp4")
                    .sources(sources).build();
    minioClient.composeObject(composeObjectArgs);

}
//清除分块文件
@Test
public void test_removeObjects(){
    //合并分块完成将分块文件清除
    List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
            .limit(6)
            .map(i -> new DeleteObject("chunk/".concat(Integer.toString(i))))
            .collect(Collectors.toList());

    RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("testbucket").objects(deleteObjects).build();
    Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
    results.forEach(r->{
        DeleteError deleteError = null;
        try {
            deleteError = r.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
}

使用minio合并文件报错:java.lang.IllegalArgumentException: source testbucket/chunk/0: size 1048576 must be greater than 5242880

minio合并文件默认分块最小5M,我们将分块改为5M再次测试。

5.3 接口定义,检查文件/分块、上传分块、合并分块

与前端的约定是操作成功返回{code:0}否则返回{code:-1}

定义接口如下:

package com.xuecheng.media.api;
/**
 * @description 大文件上传接口
 */
@Api(value = "大文件上传接口", tags = "大文件上传接口")
@RestController
public class BigFilesController {



    @ApiOperation(value = "文件上传前检查文件")
    @PostMapping("/upload/checkfile")
    public RestResponse<Boolean> checkfile(
            @RequestParam("fileMd5") String fileMd5
    ) throws Exception {
        return null;
    }

//chunk是分块序号
    @ApiOperation(value = "分块文件上传前的检测")
    @PostMapping("/upload/checkchunk")
    public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5,
                                            @RequestParam("chunk") int chunk) throws Exception {
       return null;
    }

    @ApiOperation(value = "上传分块文件")
    @PostMapping("/upload/uploadchunk")
    public RestResponse uploadchunk(@RequestParam("file") MultipartFile file,
                                    @RequestParam("fileMd5") String fileMd5,
                                    @RequestParam("chunk") int chunk) throws Exception {

        return null;
    }

    @ApiOperation(value = "合并文件")
    @PostMapping("/upload/mergechunks")
    public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5,
                                    @RequestParam("fileName") String fileName,
                                    @RequestParam("chunkTotal") int chunkTotal) throws Exception {
        return null;

    }


}

5.4 上传分块Service

5.4.1 检查文件和分块

接口完成进行接口实现,首先实现检查文件方法和检查分块方法。

@Override
public RestResponse<Boolean> checkFile(String fileMd5) {
    //查询文件信息
    MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
    if (mediaFiles != null) {
        //桶
        String bucket = mediaFiles.getBucket();
        //存储目录
        String filePath = mediaFiles.getFilePath();
        //文件流
        InputStream stream = null;
        try {
            stream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket)
                            .object(filePath)
                            .build());

            if (stream != null) {
                //文件已存在
                return RestResponse.success(true);
            }
        } catch (Exception e) {
           
        }
    }
    //文件不存在
    return RestResponse.success(false);
}



@Override
public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {

    //得到分块文件目录
    String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    //得到分块文件的路径
    String chunkFilePath = chunkFileFolderPath + chunkIndex;

    //文件流
    InputStream fileInputStream = null;
    try {
        fileInputStream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucket_videoFiles)
                        .object(chunkFilePath)
                        .build());

        if (fileInputStream != null) {
            //分块已存在
            return RestResponse.success(true);
        }
    } catch (Exception e) {
       
    }
    //分块未存在
    return RestResponse.success(false);
}

//得到分块文件的目录
private String getChunkFileFolderPath(String fileMd5) {
    return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
}

5.4.2 上传分块

@Override
public RestResponse uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {

    //得到分块文件的目录路径。“abcde”->“a/b/abcde”
    String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    //得到分块文件的路径
    String chunkFilePath = chunkFileFolderPath + chunk;
    //获取文件类型mimeType
    String mimeType = getMimeType(null);
    //将文件存储至minIO
    boolean b = addMediaFilesToMinIO(localChunkFilePath, mimeType, bucket_videoFiles, chunkFilePath);
    if (!b) {
        log.debug("上传分块文件失败:{}", chunkFilePath);
        return RestResponse.validfail(false, "上传分块失败");
    }
    log.debug("上传分块文件成功:{}",chunkFilePath);
    return RestResponse.success(true);

}
    //根据扩展名获取mimeType
    private String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        //根据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;

    }

5.4.3 完善接口层

@ApiOperation(value = "文件上传前检查文件")
@PostMapping("/upload/checkfile")
public RestResponse<Boolean> checkfile(
        @RequestParam("fileMd5") String fileMd5
) throws Exception {
    return mediaFileService.checkFile(fileMd5);
}


@ApiOperation(value = "分块文件上传前的检测")
@PostMapping("/upload/checkchunk")
public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5,
                                        @RequestParam("chunk") int chunk) throws Exception {
    return mediaFileService.checkChunk(fileMd5,chunk);
}

@ApiOperation(value = "上传分块文件")
@PostMapping("/upload/uploadchunk")
public RestResponse uploadchunk(@RequestParam("file") MultipartFile file,
                                @RequestParam("fileMd5") String fileMd5,
                                @RequestParam("chunk") int chunk) throws Exception {

    //创建临时文件
    File tempFile = File.createTempFile("minio", "temp");
    //上传的文件拷贝到临时文件
    file.transferTo(tempFile);
    //文件路径
    String absolutePath = tempFile.getAbsolutePath();
    return mediaFileService.uploadChunk(fileMd5,chunk,absolutePath);
}

启动前端工程,进入上传视频界面进行前后端联调测试。 

报错、Tomcat默认上传文件大小限制为1M,yml配置文件上传限制 

minio合并的分块小于5M时会报错:

解决:

前端对文件分块的大小为5MB,SpringBoot web默认上传文件的大小限制为1MB,这里需要在nacos里media-api工程yml配置如下:

spring:
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

max-file-size:单个文件的大小限制

Max-request-size: 单次请求的大小限制

5.5 合并分块开发

5.5.1 service开发

业务流程 :

1.获取分块文件路径
2.合并
3.验证md5合并后的文件和源文件是否一致,从而判断是否上传成功
4.文件信息入数据库
5.清除分块文件

代码实现: 

@Override
public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
    //=====1.获取分块文件路径=====
    String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    //组成将分块文件路径组成 List<ComposeSource>
    List<ComposeSource> sourceObjectList = Stream.iterate(0, i -> ++i)
            .limit(chunkTotal)
            .map(i -> ComposeSource.builder()
                    .bucket(bucket_videoFiles)
                    .object(chunkFileFolderPath.concat(Integer.toString(i)))
                    .build())
            .collect(Collectors.toList());
    //=====2.合并=====
    //文件名称
    String fileName = uploadFileParamsDto.getFilename();
    //文件扩展名
    String extName = fileName.substring(fileName.lastIndexOf("."));
    //合并文件路径
    String mergeFilePath = getFilePathByMd5(fileMd5, extName);
    try {
        //合并文件
        ObjectWriteResponse response = minioClient.composeObject(
                ComposeObjectArgs.builder()
                        .bucket(bucket_videoFiles)
                        .object(mergeFilePath)
                        .sources(sourceObjectList)
                        .build());
        log.debug("合并文件成功:{}",mergeFilePath);
    } catch (Exception e) {
        log.debug("合并文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
        return RestResponse.validfail(false, "合并文件失败。");
    }

    // ====3.验证md5合并后的文件和源文件是否一致,从而判断是否上传成功====
    //下载合并后的文件
    File minioFile = downloadFileFromMinIO(bucket_videoFiles,mergeFilePath);
    if(minioFile == null){
        log.debug("下载合并后文件失败,mergeFilePath:{}",mergeFilePath);
        return RestResponse.validfail(false, "下载合并后文件失败。");
    }

    try (InputStream newFileInputStream = new FileInputStream(minioFile)) {
        //minio上文件的md5值
        String md5Hex = DigestUtils.md5Hex(newFileInputStream);
        //比较md5值,不一致则说明文件不完整
        if(!fileMd5.equals(md5Hex)){
            return RestResponse.validfail(false, "文件合并校验失败,最终上传失败。");
        }
        //文件大小
        uploadFileParamsDto.setFileSize(minioFile.length());
    }catch (Exception e){
        log.debug("校验文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
        return RestResponse.validfail(false, "文件合并校验失败,最终上传失败。");
    }finally {
       if(minioFile!=null){
           minioFile.delete();
       }
    }

    //====4.文件信息入数据库。注入自己这个bean,加“currentProxy.”主要为了让组成事务。非事务方法调用事务方法必须用代理对象调用=====
//    @Autowired
//    MediaFileService currentProxy;
currentProxy.addMediaFilesToDb(companyId,fileMd5,uploadFileParamsDto,bucket_videoFiles,mergeFilePath);
    //=====5.清除分块文件=====
    clearChunkFiles(chunkFileFolderPath,chunkTotal);
    return RestResponse.success(true);
}

/**
 * 从minio下载文件
 * @param bucket 桶
 * @param objectName 对象名称
 * @return 下载后的文件
 */
public File downloadFileFromMinIO(String bucket,String objectName){
    //临时文件
    File minioFile = null;
    FileOutputStream outputStream = null;
    try{
        InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucket)
                .object(objectName)
                .build());
        //创建临时文件
        minioFile=File.createTempFile("minio", ".merge");
        outputStream = new FileOutputStream(minioFile);
        IOUtils.copy(stream,outputStream);
        return minioFile;
    } catch (Exception e) {
       e.printStackTrace();
    }finally {
        if(outputStream!=null){
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return null;
}
/**
 * 得到合并后的文件的地址
 * @param fileMd5 文件id即md5值
 * @param fileExt 文件扩展名
 * @return
 */
private String getFilePathByMd5(String fileMd5,String fileExt){
    return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
}

/**
 * 清除分块文件
 * @param chunkFileFolderPath 分块文件路径
 * @param chunkTotal 分块文件总数
 */
private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){

    try {
        List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                .collect(Collectors.toList());

        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("video").objects(deleteObjects).build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        results.forEach(r->{
            DeleteError deleteError = null;
            try {
                deleteError = r.get();
            } catch (Exception e) {
                e.printStackTrace();
                log.error("清楚分块文件失败,objectname:{}",deleteError.objectName(),e);
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
        log.error("清楚分块文件失败,chunkFileFolderPath:{}",chunkFileFolderPath,e);
    }
}

注意:

非事务方法调用事务方法必须用代理对象调用。

所以文件信息入数据库时,要注入自己这个bean,加“currentProxy.”,而不能加“this.”,主要为了让组成事务。

5.5.2 接口层完善

@ApiOperation(value = "合并文件")
@PostMapping("/upload/mergechunks")
public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5,
                                @RequestParam("fileName") String fileName,
                                @RequestParam("chunkTotal") int chunkTotal) throws Exception {
    Long companyId = 1232141425L;

    UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
    uploadFileParamsDto.setFileType("001002");
    uploadFileParamsDto.setTags("课程视频");
    uploadFileParamsDto.setRemark("");
    uploadFileParamsDto.setFilename(fileName);

    return mediaFileService.mergechunks(companyId,fileMd5,chunkTotal,uploadFileParamsDto);

}

5.5.2 合并分块测试

下边进行前后端联调:

1、上传一个视频测试合并分块的执行逻辑

进入service方法逐行跟踪。

2、断点续传测试

上传一部分后,停止刷新浏览器再重新上传,通过浏览器日志发现已经上传过的分块不再重新上传

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

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

相关文章

4年外包终上岸,我只能说这类公司能不去就不去

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是4年。现在终于跳槽到了互联网公司了&#xff0c;我想说的是&#xff0c;但凡有点机会&#xff0c;千万…

类图(类之间的关系)

一.概述 类图(Class diagram)是显示了模型的静态结构&#xff0c;特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。在软件工程中&#xff0c;类图是一种静态的结构图&#xff0c;描述了系统的类的集合…

基于Powell共轭方向法的UWB室内定位构型优化算法

基于Powell共轭方向法的UWB室内定位构型优化算法 阚昊宇 摘要&#xff1a; UWB室内定位系统的服务性能及定位精度很大程度上受UWB基站构型影响&#xff0c;而GDOP是衡量系统定位服务性能的重要指标。目前关于UWB室内定位基站构型的讨论主要集中于最小化限定基站数目下GDOP在自…

【系统集成项目管理工程师】项目资源管理

&#x1f4a5;十大知识领域&#xff1a;项目资源管理 项目资源管理包括以下 4 个过程: 编制项目人力资源计划组建项目团队建设项目团队管理项目团队 一、编制项目人力资源计划 确定与识别项目中的角色、所需技能、分配项目职责和汇报关系&#xff0c;并记录下来形成书面文件&am…

CDN如何阻止网络攻击

随着网络技术的发展&#xff0c;网络攻击事件也越来越多&#xff0c;对企业和个人的安全和稳定造成严重威胁。为此&#xff0c;高防CDN应运而生&#xff0c;成为广大用户保障网络安全的重要工具。什么是高防CDN?高防CDN的特点有哪些?高防CDN如何阻止网络攻击?接下来让我们一…

【云原生|Docker】14-Dokcer Harbor高可用部署

【云原生Docker】14-Dokcer Harbor高可用部署 文章目录 【云原生Docker】14-Dokcer Harbor高可用部署前言Harbor高可用方案单主复制双主复制多Harbor共享后端存储 Harbor高可用部署方案说明环境说明部署步骤安装nfs安装redis和PostgreSQL安装harbor配置nginx访问测试 总结 前言…

netty源码学习之-HashedWheelTimer

netty源码学习之-HashedWheelTimer 概述使用相关概念解析时间轮 运行时序图 源码workerHashedWheelTimeoutHashedWheelBucket 概述 该部分源码是netty的时间轮&#xff0c;netty的时间轮是单轮&#xff0c;其他时间轮是多轮设计&#xff0c;今天先了解下netty的时间轮设计 使用…

hot100:数组——49、53、55

49. 字母异位词分组 用hashmap存储&#xff0c;其中每个key&#xff0c;用这组异位词的排序后的字符串&#xff1b;value是这组异位词。比如“tea”和“ate”是一组异位词&#xff0c;他们的排序结果都是“aet”。 public List<List<String>> groupAnagrams(Stri…

交流电中的无功功率和有功功率,减少无功功率

有功&#xff0c;无功功率 从字面上理解就是做功和不做功的功率。不做功的是因为负载电路中有电感和电容的存在。 电容和电感的电压电流关系 设加在两端的电压都是 U U m a x s i n w t UU_{max}sinwt UUmax​sinwt 电容和电感两端电压电流的关系 电容两端的电压电流关系…

Javaee Spring基于XML的AOP开发

快速入门 1. 导入 AOP 相关坐标 2. 创建目标接口和目标类&#xff08;内部有切点&#xff09; 3. 创建切面类&#xff08;内部有增强方法&#xff09; 4. 将目标类和切面类的对象创建权交给 spring 5. 在 applicationContext.xml 中配置织入关系 6. 测试代码 项目…

【数据库】MySQL数据约束和表关系详解

目录 1.数据库约束 1.1约束类型 1.2NULL约束 1.3UNIQUE&#xff1a;唯一约束 1.4DEFAULT&#xff1a;默认值约束 1.5PRIMARY&#xff1a;主键约束 1.6FOREIGH KEY:外键约束 2.表的关系 2.1一对一 2.2一对多 2.3多对多 1.数据库约束 数据库中的数据保存在数据表中&am…

数据库基础篇 《9. 子查询》

目录 1. 需求分析与问题解决 1.1 实际问题 1.2 子查询的基本使用 ​编辑1.3 子查询的分类 分类方式1&#xff1a;我们按内查询的结果返回一条还是多条记录&#xff0c;将子查询分为 单行子查询 、 多行子查询 。 分类方式2&#xff1a; 我们按内查询是否被执行多次&#x…

4WE10D50型力士乐液压阀规格

安装位置 可选择&#xff0c;方向阀最好水平安装&#xff01;对于阀品种&#xff0c;如 &#xff0d; 不带阀芯对中弹簧 &#xff0d; 或带下垂电磁铁 其他安装位置能够导致功能失常或违反有关的技朮规定。 带泄油口的压力开关的安装位置必须选择&#xff0c;使它的泄油口…

Redis源码分析之网络模型

Redis网络模型 阅读源码的初衷Redis源码阅读 阅读源码的初衷 很多网上解释这个Redis为啥这么块&#xff1f;都会说Redis这么快的原因会有一个Redis才用了单线程&使用了多路io复用来检查io事件&#xff0c;单线程可以避免多线程对资源的竞争。如果我们使用了多线程那么就需…

Revit中栏杆扶手、坡道的绘制及插件太多问题

一、在Revit中栏杆与扶手的绘制方法有两种&#xff1a; ①绘制路径 ②放置在主体上 二、执行方式 功能区&#xff1a;“建筑”选项卡“楼梯坡道”面板“栏杆扶手”下拉菜单“绘制路径”。 三、绘制技巧 首先我们一起来看看如何设置栏杆扶手属性。 第一步&#xff1a;设置属…

【vue3学习系列】对比vue2生命周期做了哪些改变,vue3初学者快来看看

文章目录 前言官方生命周期图分析去除beforeCreate与createdsetup代替created其他钩子只是改了名称 剔除vue2后的生命周期图其他钩子函数keepalive错误捕获其他的一些钩子去官方文档看看即可 前言 看了下官方的生命周期的说明&#xff0c;感觉讲的不算太清晰&#xff0c;所以个…

C++ 内联函数(inline)

内联函数&#xff1a;就是在函数前加inline 让函数在调用的地方直接展开 可是内联函数有什么作用呢&#xff1f;&#xff1f; 我们都知道&#xff0c;如果调用一个函数的话&#xff0c;会建立栈帧&#xff0c;在建立栈帧的时候会进行压栈等一系列操作。 而内联函数会在调用的…

Nginx和tomcat反向代理

七层反向代理 实验准备&#xff1a;准备三台虚拟机 192.168.146.20 tomcat&#xff08;两个&#xff09; 192.168.146.30 tomcat 192.168.146.50 七层反向代理&#xff08;nginx&#xff09; 部署虚拟机192.168.146.20&#xff08;两个tomcat已部署完毕&#xff09; …

SwiftUI 4.0(iOS 16)极简实现一个美美哒的多选 Toggle 按钮组

概览 在 SwiftUI 4.0 之前&#xff0c;想要实现如下效果的多选/全选 Toggle 按钮组是要写不少行代码滴&#xff1a; 不过&#xff0c;在 iOS 16 之后我们仅用1行代码即可搞定以上所有&#xff01;在某些场合下这非常有用哦。 在本篇博文中&#xff0c;我们就来看看如何实现它…

2023年的深度学习入门指南(6) - 剪枝和量化

2023年的深度学习入门指南(6) - 剪枝和量化 从这一节开始&#xff0c;我们要准备一些技术专项了。因为目前大模型技术还在快速更新迭代中&#xff0c;各种库和实现每天都在不停出现。因为变化快&#xff0c;所以难免会遇到一些问题。对于细节有一定的把握能力起码可以做到出问…