当前位置: 首页 > article >正文

MinIo 的操作与使用和避坑

文章目录

      • 一、介绍
      • 二、安装
      • 三、Client 连通与避坑
        • 1. SpringBoot 项目
        • 2. 自己的 Maven 项目(非 SpringBoot 项目)
          • 思路1:尝试从 Maven 仓库中替换该 jar 包
          • 思路2:改造有问题的 jar 包
      • 四、封装一些简单的方法

官方 API 文档:Documentation
官方中文文档:MinIO中文文档

一、介绍

  MinIO 是开源的对象存储服务器,相当于免费版的 OSS。

  MinIO 是一款高性能、分布式的开源对象存储系统,它是一款软件产品。MinIO 公司旨在解决非结构化数据增长的需求,开发了流行于业界的开源云存储软件 MinIO。

  虽然 MinIO 是 100% 开源的,但它既是一家公司又是一个开源项目。它采用 GNU AGPL v3 开源证书,拥有 GNU AGPL 代码的版权,同时还是 MinIO 项目的主要贡献者,可独立对 MinIO 进行维护。

  MinIO 基于 Apache License 2.0 开源协议的对象存储服务。它兼容 Amazon S3 云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。

二、安装

官网下载链接:官网下载界面
在这里插入图片描述

# 下载安装包
[root@xiaoqiang ~]# wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230930070229.0.0_amd64.deb -O minio.deb
[root@xiaoqiang  ~]# sudo dpkg -i minio.deb

# 设置
[root@xiaoqiang  ~]# sudo touch  /etc/default/minio
[root@xiaoqiang  ~]# cat /etc/default/minio 
MINIO_ROOT_USER="xiaoiang"
MINIO_ROOT_PASSWORD="heheda123"
MINIO_VOLUMES="/dataspace/xiaoqiang-data-backup"

# 创建用户和组,分配目录权限
[root@xiaoqiang  ~]# sudo groupadd -r minio-user
[root@xiaoqiang  ~]# sudo useradd -M -r -g minio-user minio-user
[root@xiaoqiang  ~]# sudo chown minio-user:minio-user -R /dataspace/xiaoqiang-data-backup

# 启动服务
[root@xiaoqiang  ~]# sudo systemctl daemon-reload
[root@xiaoqiang  ~]# sudo systemctl enable minio.service
[root@xiaoqiang  ~]# sudo systemctl start minio.service

注意: minio 与 Prometheus、clickhouse 端口冲突,如果启动服务失败,注意关掉相关服务。 netstat -antulp | grep 9000,kill 掉该进程,minio 服务可以重启成功。

在这里插入图片描述

参考:MinIO使用及整合起步依赖

三、Client 连通与避坑

  Java 代码:

        MinioClient minioClient =
                MinioClient.builder()
                        .endpoint("http://192.168.110.110:9000")
                        .credentials("xiaoqiang", "heheda123")
                        .build();
        System.out.println(minioClient);
        // 检查桶是否存在
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket("test").build());
        if (!found) {
            // 创建桶
            minioClient.makeBucket(MakeBucketArgs.builder().bucket("test").build());
        }

        //列出所有桶名
        List<Bucket> buckets = minioClient.listBuckets();
        for (Bucket i : buckets){
            System.out.println(i.name());
        }

  Maven 引入:

        <!--此依赖为minio的服务依赖-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.7</version>
        </dependency>

  报错:Unsupported OkHttp library found. Must use okhttp >= 4.11.0

在这里插入图片描述

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.2</version>
        </dependency>
        <!--或者-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.4.5</version>
        </dependency>
        <!--或者-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.3.7</version>
        </dependency>

  报错:Unsupported OkHttp library found. Must use okhttp >= 4.8.1

在这里插入图片描述

        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.2.1</version>
        </dependency>
        <!--或者-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.1.0</version>
        </dependency>
        <!--或者-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.0.3</version>
        </dependency>

  报错:Exception in thread "main" java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B

在这里插入图片描述
  尝试解决1:

在这里插入图片描述
  结论:不好使。

  尝试解决2:新建一个纯 Maven 项目。好使

在这里插入图片描述
在这里插入图片描述
  接下来的解决思路:将项目中的依赖依次注释最终只留 Minio 的依赖,看到底是哪个引入的依赖和 Minio 的依赖有冲突。

1. SpringBoot 项目

  最终发现是 spring-boot-starter-parent 的依赖导致的。

在这里插入图片描述
在这里插入图片描述

  居然是 SpringBoot 的顶层依赖导致的,也是绝了。尝试了下低版本居然可以。

在这里插入图片描述

  更高的版本居然也可以。

在这里插入图片描述
  经测试,框起来的版本可以用。
在这里插入图片描述

2. 自己的 Maven 项目(非 SpringBoot 项目)

  经排查,居然是 phoenix 的引入导致的,该 jar 包是从 Cloudera Manager 集群中下载后通过 mvn install 命令手动引入依赖,网上 Maven 仓库并没有这个 jar 包,现在该 jar 包中的 okio 和 minio 中的 okio 冲突导致的。报错:Exception in thread "main" java.lang.NoSuchFieldError: Companion

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  而且通过 exclusion 排除该 jar 包的方式并不好使,可能得重新编译该 jar 包。

思路1:尝试从 Maven 仓库中替换该 jar 包

在这里插入图片描述
在这里插入图片描述
  但总是导入一个依赖就缺另一个依赖。

思路2:改造有问题的 jar 包

  打开 phoenix-5.0.0-cdh6.2.0-client.jar 文件删除 okio 再重新手动安装。测试后发现该方法管用。
在这里插入图片描述

四、封装一些简单的方法

来自:minio的基本使用——java

package com.jdh.minio.config;
 
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
 
/**
 * @ClassName: MinioFile
 * @Author: jdh
 * @CreateTime: 2022-04-15
 * @Description:
 */
@Configuration
@Slf4j
public class MinioFileUtil {
 
    @Resource
    private MinioProperties minioProperties;
 
    private MinioClient minioClient;
 
    /**
     * 这个是6.0.左右的版本
     * @return MinioClient
     */
//    @Bean
//    public MinioClient getMinioClient(){
//
//        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
//
//        try {
//            return new MinioClient(url, minioProperties.getAccessKey(), minioProperties.getSecretKey());
//        } catch (InvalidEndpointException | InvalidPortException e) {
//            e.printStackTrace();
//            log.info("-----创建Minio客户端失败-----");
//            return null;
//        }
//    }
 
    /**
     * 下面这个和上面的意思差不多,但是这个是新版本
     * 获取一个连接minio服务端的客户端
     *
     * @return MinioClient
     */
    @Bean
    public MinioClient getClient() {
 
        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
        MinioClient minioClient = MinioClient.builder()
                .endpoint(url)    //两种都可以,这种全路径的其实就是下面分开配置一样的
//                        .endpoint(minioProperties.getIp(),minioProperties.getPort(),minioProperties.getSecure())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
        this.minioClient = minioClient;
        return minioClient;
    }
 
    /**
     * 创建桶
     *
     * @param bucketName 桶名称
     */
    public void createBucket(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("创建桶的时候,桶名不能为空!");
        }
 
        // Create bucket with default region.
        minioClient.makeBucket(MakeBucketArgs.builder()
                .bucket(bucketName)
                .build());
    }
 
    /**
     * 创建桶,固定minio容器
     *
     * @param bucketName 桶名称
     */
    public void createBucketByRegion(String bucketName, String region) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("创建桶的时候,桶名不能为空!");
        }
        MinioClient minioClient = this.getClient();
 
        // Create bucket with specific region.
        minioClient.makeBucket(MakeBucketArgs.builder()
                .bucket(bucketName)
                .region(region) //
                .build());
 
//        // Create object-lock enabled bucket with specific region.
//        minioClient.makeBucket(
//                MakeBucketArgs.builder()
//                        .bucket("my-bucketname")
//                        .region("us-west-1")
//                        .objectLock(true)
//                        .build());
    }
 
    /**
     * 修改桶名
     * (minio不支持直接修改桶名,但是可以通过复制到一个新的桶里面,然后删除老的桶)
     *
     * @param oldBucketName 桶名称
     * @param newBucketName 桶名称
     */
    public void renameBucket(String oldBucketName, String newBucketName) throws Exception {
        if (!StringUtils.hasLength(oldBucketName) || !StringUtils.hasLength(newBucketName)) {
            throw new RuntimeException("修改桶名的时候,桶名不能为空!");
        }
 
    }
 
    /**
     * 删除桶
     *
     * @param bucketName 桶名称
     */
    public void deleteBucket(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("删除桶的时候,桶名不能为空!");
        }
 
        minioClient.removeBucket(
                RemoveBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
    }
 
    /**
     * 检查桶是否存在
     *
     * @param bucketName 桶名称
     * @return boolean true-存在 false-不存在
     */
    public boolean checkBucketExist(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("检测桶的时候,桶名不能为空!");
        }
 
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }
 
    /**
     * 列出所有的桶
     *
     * @return 所有桶名的集合
     */
    public List<Bucket> getAllBucketInfo() throws Exception {
 
        //列出所有桶
        List<Bucket> buckets = minioClient.listBuckets();
        return buckets;
    }
 
    /**
     * 列出某个桶中的所有文件名
     * 文件夹名为空时,则直接查询桶下面的数据,否则就查询当前桶下对于文件夹里面的数据
     *
     * @param bucketName 桶名称
     * @param folderName 文件夹名
     * @param isDeep     是否递归查询
     */
    public Iterable<Result<Item>> getBucketAllFile(String bucketName, String folderName, Boolean isDeep) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("获取桶中文件列表的时候,桶名不能为空!");
        }
        if (!StringUtils.hasLength(folderName)) {
            folderName = "";
        }
        System.out.println(folderName);
        Iterable<Result<Item>> listObjects = minioClient.listObjects(
                ListObjectsArgs
                        .builder()
                        .bucket(bucketName)
                        .prefix(folderName + "/")
                        .recursive(isDeep)
                        .build());
 
//        for (Result<Item> result : listObjects) {
//            Item item = result.get();
//            System.out.println(item.objectName());
//        }
 
        return listObjects;
    }
 
    /**
     * 删除文件夹
     *
     * @param bucketName 桶名
     * @param objectName 文件夹名
     * @param isDeep     是否递归删除
     * @return
     */
    public Boolean deleteBucketFile(String bucketName, String objectName) {
        if (!StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("删除文件的时候,桶名或文件名不能为空!");
        }
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        } catch (Exception e) {
            log.info("删除文件失败");
            return false;
        }
    }
 
    /**
     * 删除文件夹
     *
     * @param bucketName 桶名
     * @param objectName 文件夹名
     * @param isDeep     是否递归删除
     * @return
     */
    public Boolean deleteBucketFolder(String bucketName, String objectName, Boolean isDeep) {
        if (!StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("删除文件夹的时候,桶名或文件名不能为空!");
        }
        try {
            ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucketName).prefix(objectName + "/").recursive(isDeep).build();
            Iterable<Result<Item>> listObjects = minioClient.listObjects(args);
            listObjects.forEach(objectResult -> {
                try {
                    Item item = objectResult.get();
                    minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());
                } catch (Exception e) {
                    log.info("删除文件夹中的文件异常", e);
                }
            });
            return true;
        } catch (Exception e) {
            log.info("删除文件夹失败");
            return false;
        }
    }
 
    /**
     * 获取文件下载地址
     *
     * @param bucketName 桶名
     * @param objectName 文件名
     * @param expires    过期时间,默认秒
     * @return
     * @throws Exception
     */
    public String getFileDownloadUrl(String bucketName, String objectName, Integer expires) throws Exception {
 
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)//下载地址的请求方式
                .bucket(bucketName)
                .object(objectName)
                .expiry(expires, TimeUnit.SECONDS)//下载地址过期时间
                .build();
        String objectUrl = minioClient.getPresignedObjectUrl(args);
        return objectUrl;
    }
 
    /**
     * 获取文件上传地址(暂时还未实现)
     *
     * @param bucketName 桶名
     * @param objectName 文件名
     * @param expires    过期时间,默认秒
     * @return
     * @throws Exception
     */
    public String getFileUploadUrl(String bucketName, String objectName, Integer expires) throws Exception {
 
        // 过期时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now().plusSeconds(60);
        PostPolicy postPolicy = new PostPolicy(bucketName, zonedDateTime);
 
        // 获取对象的默认权限策略
        StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        String objectPolicy = statObjectResponse.headers().get("x-amz-object-policy");
 
        String presignedObjectUrl = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .method(Method.POST)
                        .expiry(expires) // 预签名的 URL 有效期为 1 小时
                        .build());
 
        MyMinioClient client = new MyMinioClient(minioClient);
 
        return presignedObjectUrl;
    }
 
    /**
     * 创建文件夹
     *
     * @param bucketName 桶名
     * @param folderName 文件夹名称
     * @return
     * @throws Exception
     */
    public ObjectWriteResponse createBucketFolder(String bucketName, String folderName) throws Exception {
 
        if (!checkBucketExist(bucketName)) {
            throw new RuntimeException("必须在桶存在的情况下才能创建文件夹");
        }
        if (!StringUtils.hasLength(folderName)) {
            throw new RuntimeException("创建的文件夹名不能为空");
        }
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(folderName + "/")
                .stream(new ByteArrayInputStream(new byte[0]), 0, 0)
                .build();
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(putObjectArgs);
 
 
        return objectWriteResponse;
    }
 
    /**
     * 检测某个桶内是否存在某个文件
     *
     * @param objectName 文件名称
     * @param bucketName 桶名称
     */
    public boolean getBucketFileExist(String objectName, String bucketName) throws Exception {
        if (!StringUtils.hasLength(objectName) || !StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("检测文件的时候,文件名和桶名不能为空!");
        }
 
        try {
            // 判断文件是否存在
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()) &&
                    minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()) != null;
            return exists;
        } catch (ErrorResponseException e) {
            log.info("文件不存在 ! Object does not exist");
            return false;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
 
    /**
     * 判断桶中是否存在文件夹
     *
     * @param bucketName 同名称
     * @param objectName 文件夹名称
     * @param isDeep     是否递归查询(暂不支持)
     * @return
     */
    public Boolean checkBucketFolderExist(String bucketName, String objectName, Boolean isDeep) {
 
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(isDeep).build());
 
        return results.iterator().hasNext(); // 文件夹下存在文件
    }
 
    /**
     * 根据MultipartFile file上传文件
     * minio 采用文件流上传,可以换成下面的文件上传
     *
     * @param file       上传的文件
     * @param bucketName 上传至服务器的桶名称
     */
    public boolean uploadFile(MultipartFile file, String bucketName) throws Exception {
 
        if (file == null || file.getSize() == 0 || file.isEmpty()) {
            throw new RuntimeException("上传文件为空,请重新上传");
        }
 
        if (!StringUtils.hasLength(bucketName)) {
            log.info("传入桶名为空,将设置默认桶名:" + minioProperties.getBucketName());
            bucketName = minioProperties.getBucketName();
            if (!this.checkBucketExist(minioProperties.getBucketName())) {
                this.createBucket(minioProperties.getBucketName());
            }
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        // 获取上传的文件名
        String filename = file.getOriginalFilename();
        assert filename != null;
        //可以选择生成一个minio中存储的文件名称
        String minioFilename = UUID.randomUUID().toString() + "_" + filename;
        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
 
        InputStream inputStream = file.getInputStream();
        long size = file.getSize();
        String contentType = file.getContentType();
 
        // Upload known sized input stream.
        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName) //上传到指定桶里面
                        .object(minioFilename)//文件在minio中存储的名字
                        //p1:上传的文件流;p2:上传文件总大小;p3:上传的分片大小
                        .stream(inputStream, size, -1) //上传分片文件流大小,如果分文件上传可以采用这种形式
                        .contentType(contentType) //文件的类型
                        .build());
 
        return this.getBucketFileExist(minioFilename, bucketName);
    }
 
    /**
     * 上传本地文件,根据路径上传
     * minio 采用文件内容上传,可以换成上面的流上传
     *
     * @param filePath 上传本地文件路径
     * @Param bucketName 上传至服务器的桶名称
     */
    public boolean uploadPath(String filePath, String bucketName) throws Exception {
 
        File file = new File(filePath);
        if (!file.isFile()) {
            throw new RuntimeException("上传文件为空,请重新上传");
        }
 
        if (!StringUtils.hasLength(bucketName)) {
            log.info("传入桶名为空,将设置默认桶名:" + minioProperties.getBucketName());
            bucketName = minioProperties.getBucketName();
            if (!this.checkBucketExist(minioProperties.getBucketName())) {
                this.createBucket(minioProperties.getBucketName());
            }
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        String minioFilename = UUID.randomUUID().toString() + "_" + file.getName();//获取文件名称
        String fileType = minioFilename.substring(minioFilename.lastIndexOf(".") + 1);
 
        minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(minioFilename)//文件存储在minio中的名字
                        .filename(filePath)//上传本地文件存储的路径
                        .contentType(fileType)//文件类型
                        .build());
 
        return this.getBucketFileExist(minioFilename, bucketName);
    }
 
    /**
     * 文件下载,通过http返回,即在浏览器下载
     *
     * @param response   http请求的响应对象
     * @param bucketName 下载指定服务器的桶名称
     * @param objectName 下载的文件名称
     */
    public void downloadFile(HttpServletResponse response, String bucketName, String objectName) throws Exception {
        if (response == null || !StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("下载文件参数不全!");
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        //获取一个下载的文件输入流操作
        GetObjectResponse objectResponse = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
 
        OutputStream outputStream = response.getOutputStream();
        int len = 0;
        byte[] buf = new byte[1024 * 8];
        while ((len = objectResponse.read(buf)) != -1) {
            outputStream.write(buf, 0, len);
        }
        if (outputStream != null) {
            outputStream.close();
            outputStream.flush();
        }
        objectResponse.close();
    }
 
    /**
     * 文件下载到指定路径
     *
     * @param downloadPath 下载到本地路径
     * @param bucketName   下载指定服务器的桶名称
     * @param objectName   下载的文件名称
     */
    public void downloadPath(String downloadPath, String bucketName, String objectName) throws Exception {
        if (downloadPath.isEmpty() || !StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("下载文件参数不全!");
        }
 
        if (!new File(downloadPath).isDirectory()) {
            throw new RuntimeException("本地下载路径必须是一个文件夹或者文件路径!");
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        downloadPath += objectName;
 
        minioClient.downloadObject(
                DownloadObjectArgs.builder()
                        .bucket(bucketName) //指定是在哪一个桶下载
                        .object(objectName)//是minio中文件存储的名字;本地上传的文件是user.xlsx到minio中存储的是user-minio,那么这里就是user-minio
                        .filename(downloadPath)//需要下载到本地的路径,一定是带上保存的文件名;如 d:\\minio\\user.xlsx
                        .build());
    }
 
}

http://www.kler.cn/news/159647.html

相关文章:

  • Mysql行格式(记录格式)详解
  • Leetcode—2477.到达首都的最少油耗【中等】
  • 《PFL》论文阅读笔记
  • Docker 的基本概念和优势,以及在应用程序开发中的实际应用。
  • 基本数据类型与引用数据类型的区别(java基础)
  • 【软件测试】技术精选:Jmeter常见的几种报错
  • 云HIS:新一代云架构医院信息管理系统源码(java语言)
  • Flink流批一体计算(24):Flink SQL之mysql维表实时关联
  • 2、RocketMQ源码分析(二)
  • 前端并发多个请求并失败重发
  • Opencv框选黑色字体进行替换(涉及知识点:selectROI,在控制台输入字体大小,颜色,内容替换所选择的区域)
  • ORACLE使用Mybatis-plus批量插入
  • vue select选择下拉组织树,解决不出现横向滚动条
  • C#用MathNet生成矩阵,并打印矩阵元素
  • 智能优化算法应用:基于冠状病毒群体免疫算法无线传感器网络(WSN)覆盖优化 - 附代码
  • Python list tuple dict set的文件持久操作
  • acwing算法基础之时空复杂度分析
  • MFC对话框集成OSG无法响应键盘事件
  • 使用Pytorch从零开始实现BERT
  • python爬虫-某公开数据网站实例小记
  • vue中el-upload结合vuedraggable实现图片的上传、排序、删除以及预览等功能
  • 2023-12-01 AndroidR 系统在root目录下新建文件夹和创建链接,编译的时候需要修改sepolicy权限
  • 创建腾讯云存储桶---上传图片--使用cos-sdk完成上传
  • 简介Kadane算法及相关的普通动态规划
  • RepidJson将内容格式化后写入文件
  • 【OpenSSH升级】升级后证书认证登录突然失效
  • C语言能判断一个变量是int还是float吗?
  • 若依框架 前台自己获取登录用户信息
  • 矩阵处理—转圈打印矩阵
  • HomeAssistant如何添加HACS插件实现公网控制米家与HomeKit等智能家居