• Minio入门系列【5】JAVA集成Minio之存储桶操作API使用详解


    1 前言

    1.1 官方文档和SDK

    官方文档:https://min.io/docs/minio/kubernetes/upstream/index.html?ref=docs-redirect
    SDK:https://github.com/minio/minio-java
    Minio 提供了多种语言的SDK,比如java、go、python等。JAVA开发平台可以选择JS和java SDK,也就是前端和后端都可以直接集成minio。
    在这里插入图片描述

    1.2 技术方案

    每个OSS的用户都会用到上传服务。Web端常见的上传方法是用户在浏览器或App端上传文件到应用服务器,应用服务器再把文件上传到OSS。具体流程如下图所示。
    在这里插入图片描述
    和数据直传到OSS相比,以上方法有三个缺点:

    • 上传慢:用户数据需先上传到应用服务器,之后再上传到OSS。网络传输时间比直传到OSS多一倍。如果用户数据不通过应用服务器中转,而是直传到OSS,速度将大大提升。而且OSS采用BGP带宽,能保证各地各运营商之间的传输速度。

    • 扩展性差:如果后续用户多了,应用服务器会成为瓶颈。

    • 费用高:需要准备多台应用服务器。由于OSS上传流量是免费的,如果数据直传到OSS,不通过应用服务器,那么将能省下几台应用服务器。

    目前通过Web前端技术上传文件到OSS,有三种技术方案:

    • 利用OSS js SDK将文件上传到OSS,也就是前端直连OSS,但是容易暴露认证信息,安全性不太高。

    • 使用表单上传方式,将文件上传到OSS。利用OSS提供的接口临时接口,使用表单上传方式将文件上传到OSS。然后请求后端,告知上传完成,进行后续处理。

    • 先上传到应用服务器,再请求OSS上传,这种安全性较高,可以对数据和认证进行管控,但是性能最差。

    2 集成 JAVA SDK

    因为一般的非互联网项目,对性能要求不高,所以采用JAVA SDK集成MInio,然后提供接口给Web端调用就行了。

    2.1 环境搭建

    首先搭建一个Maven基础工程,引入相关依赖,这里引入的是最新的8.3.1版本。还引入了okhttp的最新包,不然某些API会提示版本太低。

    <dependency>
      			<groupId>io.miniogroupId>
      			<artifactId>minioartifactId>
      			<version>8.3.1version>
    		dependency>
            <dependency>
                <groupId>com.squareup.okhttp3groupId>
                <artifactId>okhttpartifactId>
                <version>4.9.2version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.2 初始化客户端

    可以看到现在minio都是采用Builder构建者模式来构造对象,和之前有很大的区别,所以需要注意。

    //url为地址,accessKey和secretKey为用户名和密码
    MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
    
    • 1
    • 2
    • 3

    2.3 存储桶基础操作

    2.3.1 存储桶是否存在

    检查存储桶是否存在。

    public boolean bucketExists(BucketExistsArgs args)
    
    • 1

    示例代码:

        /**
         * 判断桶是否存在
         */
        public static boolean bucketExists(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3.2 创建存储桶

    创建一个启用给定区域和对象锁定功能的存储桶。

    public void makeBucket(MakeBucketArgs args)
    
    • 1

    示例代码:

        /**
         * 添加存储桶
         */
        public static void makeBucket(String url, String accessKey, String secretKey, String bucketName, String region, boolean objectLock)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).objectLock(objectLock).build());
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    创建后,就可以在控制台看到这些存储桶了,最后那个被锁定的存储桶,上传文件及删除后,发现还是会显示存在这些对象,实际磁盘上的文件并没有删除

    2.3.3 查询存储桶信息列表

    列出所有桶的桶信息。

    public List<Bucket> listBuckets()
    
    • 1

    示例代码:

        /**
         * 查询存储桶信息列表
         */
        public static List<Bucket> listBuckets(String url, String accessKey, String secretKey) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            return minioClient.listBuckets();
        }
    
        public static void main(String[] args) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            List<Bucket> buckets = listBuckets("url", "accessKey", "secretKey");
            for (Bucket bucket : buckets) {
                System.out.println(bucket.creationDate() + ", " + bucket.name());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    打印信息如下,返回的创建时间是美国时间,需要注意。
    在这里插入图片描述

    2.3.4 删除存储桶

    删除一个空桶。

    public void removeBucket(RemoveBucketArgs args) 
    
    • 1

    示例代码:

        /**
         * 删除存储桶
         */
        public static void removeBucket(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:要确保存储桶存在,否则会报错,删除时最好调用bucketExists()方法判断是否存在

    2.4 设置存储桶操作

    2.4.1 加密配置

    设置桶的加密配置,以允许在该桶中上传对象时,采用对应加密配置对数据进行加密。当前支持配置的服务端加密方式为KMS托管密钥的服务端加密(SSE-KMS),及AES256加密。
    设置桶的加密配置:

    public void setBucketEncryption(SetBucketEncryptionArgs args)
    
    • 1

    获取桶的加密配置:

    public SseConfiguration getBucketEncryption(GetBucketEncryptionArgs args)
    
    • 1

    2.4.2 生命周期

    生命周期管理可适用于以下典型场景:

    • 周期性上传的日志文件,可能只需要保留一个星期或一个月。到期后要删除它们。

    • 某些文档在一段时间内经常访问,但是超过一定时间后便可能不再访问了。这些文档需要在一定时间后转化为低频访问存储,归档存储或者删除。

    存储桶生命周期配置:

    public void setBucketLifecycle(SetBucketLifecycleArgs args)
    
    • 1

    获取桶的生命周期配置:

    public LifecycleConfiguration getBucketLifecycle(GetBucketLifecycleArgs args) 
    
    • 1

    示例代码:

     // 5. 生命周期
            List<LifecycleRule> rules = new LinkedList<>();
            // 配置生命周期规则
            rules.add(
                    new LifecycleRule(
                            Status.ENABLED, // 开启状态
                            null,
                            new Expiration((ZonedDateTime) null, 365, null), // 保存365天
                            new RuleFilter("logs/"), // 目录配置
                            "rule2",
                            null,
                            null,
                            null));
            LifecycleConfiguration lifecycleConfiguration = new LifecycleConfiguration(rules);
            // 添加生命周期配置
            minioClient.setBucketLifecycle(
                    SetBucketLifecycleArgs.builder().bucket("my-bucketname").config(lifecycleConfiguration).build());
            // 获取配置
            LifecycleConfiguration lifecycleConfiguration1111 =
                    minioClient.getBucketLifecycle(
                            GetBucketLifecycleArgs.builder().bucket("my-bucketname").build());
            List<LifecycleRule> rules1 = lifecycleConfiguration1111.rules();
            for (int i = 0; i < rules1.size(); i++) {
                System.out.println("Lifecycle status is " + rules1.get(i).status());
                System.out.println("Lifecycle prefix is " + rules1.get(i).filter().prefix());
                System.out.println("Lifecycle expiration days is " + rules1.get(i).expiration().days());
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    打印结果如下:
    在这里插入图片描述

    2.4.3 通知配置

    可以使用存储桶事件通知来监控存储桶中对象上发生的事件。

    MinIO 服务器支持的各种事件类型有:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    存储桶配置通知:

    public void setBucketPolicy(SetBucketPolicyArgs args)
    
    • 1

    获取桶的通知配置:

    public NotificationConfiguration getBucketNotification(GetBucketNotificationArgs args)
    
    • 1

    代码示例:

    // 6. 通知配置 
            // Add a new SQS configuration.
            NotificationConfiguration notificationConfiguration = new NotificationConfiguration();
            List<QueueConfiguration> queueConfigurationList = notificationConfiguration.queueConfigurationList();
            QueueConfiguration queueConfiguration = new QueueConfiguration();
            queueConfiguration.setQueue("arn:minio:sqs::1:webhook");
    
            List<EventType> eventList = new LinkedList<>();
            eventList.add(EventType.OBJECT_CREATED_PUT);
            eventList.add(EventType.OBJECT_CREATED_COPY);
            queueConfiguration.setEvents(eventList);
            queueConfiguration.setPrefixRule("images");
            queueConfiguration.setSuffixRule("pg");
    
            queueConfigurationList.add(queueConfiguration);
            notificationConfiguration.setQueueConfigurationList(queueConfigurationList);
    
            // Set updated notification configuration.
            minioClient.setBucketNotification(
                    SetBucketNotificationArgs.builder().bucket("my-bucketname").config(notificationConfiguration).build());
            System.out.println("Bucket notification is set successfully");
    
            NotificationConfiguration minioClientBucketNotification =
                    minioClient.getBucketNotification(
                            GetBucketNotificationArgs.builder().bucket("my-bucketname").build());
            System.out.println(minioClientBucketNotification);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.4.4 策略配置

    添加存储桶策略配置。

    public void setBucketPolicy(SetBucketPolicyArgs args) 
    
    • 1

    获取桶的桶策略配置。

    public String getBucketPolicy(GetBucketPolicyArgs args)
    
    • 1

    2.4.5 复制配置

    存储桶复制旨在将存储桶中的选定对象复制到目标存储桶,内容较多,后续补上

    添加存储桶的复制配置

    public void setBucketReplication(SetBucketReplicationArgs args)
    
    • 1

    获取桶的桶复制配置:

    public ReplicationConfiguration getBucketReplication(GetBucketReplicationArgs args)
    
    • 1

    2.4.6 存储桶标签

    当为桶添加标签时,该桶上所有请求产生的计费话单里都会带上这些标签,从而可以针对话单报表做分类筛选,进行更详细的成本分析。例如:某个应用程序在运行过程会往桶里上传数据,我们可以用应用名称作为标签,设置到被使用的桶上。在分析话单时,就可以通过应用名称的标签来分析此应用的成本。

    setBucketTags可以为存储桶设置标签。

    public void setBucketTags(SetBucketTagsArgs args)
    
    • 1

    getBucketTags获取桶的标签。

    public Tags getBucketTags(GetBucketTagsArgs args)
    
    • 1

    示例代码:

            // 1. 存储桶标签
            Map<String, String> map = new HashMap<>();
            map.put("Project", "Project One");
            map.put("User", "jsmith");
            // 设置标签
            minioClient.setBucketTags(SetBucketTagsArgs.builder().bucket("my-bucketname").tags(map).build());
            // 查询标签
            Tags bucketTags = minioClient.getBucketTags(GetBucketTagsArgs.builder().bucket("my-bucketname").build());
            System.out.println(bucketTags.get().toString());
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    返回结果:
    在这里插入图片描述

    2.4.7 多版本设置

    若开启了多版本控制,上传对象时,OBS自动为每个对象创建唯一的版本号。上传同名的对象将以不同的版本号同时保存在OBS中。

    若未开启多版本控制,向同一个文件夹中上传同名的对象时,新上传的对象将覆盖原有的对象。

    某些功能(例如版本控制、对象锁定和存储桶复制)需要使用擦除编码分布式部署 MinIO。开启了版本控制后,允许在同一密钥下保留同一对象的多个版本。

    设置存储桶的版本控制配置。

    public void setBucketVersioning(SetBucketVersioningArgs args)
    
    • 1

    获取存储桶的版本控制配置。

    public VersioningConfiguration getBucketVersioning(GetBucketVersioningArgs args)
    
    • 1

    代码示例:

    // 2. 版本配置
            // 'my-bucketname'启用版本控制
            minioClient.setBucketVersioning(
                    SetBucketVersioningArgs.builder()
                            .bucket("my-bucketname")
                            .config(new VersioningConfiguration(VersioningConfiguration.Status.ENABLED, null))
                            .build());
            System.out.println("Bucket versioning is enabled successfully");
    
            //  'my-bucketname'暂停版本控制
            minioClient.setBucketVersioning(
                    SetBucketVersioningArgs.builder()
                            .bucket("my-bucketname")
                            .config(new VersioningConfiguration(VersioningConfiguration.Status.SUSPENDED, null))
                            .build());
            System.out.println("Bucket versioning is suspended successfully");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.4.8 对象锁定配置

    对象锁定设置后,删除对象后,会仍然存在磁盘中。

    在存储桶中设置对象锁定配置。

    public void setObjectLockConfiguration(SetObjectLockConfigurationArgs args) 
    
    • 1

    获取存储桶中的对象锁配置。

    public ObjectLockConfiguration getObjectLockConfiguration(GetObjectLockConfigurationArgs args)
    
    • 1

    需要先设置存储桶为对象锁定模式,示例代码:

    // 3. 将保留模式设置为Compliance,且持续时间为100天
            // 设置锁定对象的保留模式及时限
            ObjectLockConfiguration config =
                    new ObjectLockConfiguration(RetentionMode.COMPLIANCE, new RetentionDurationDays(100));
            minioClient.setObjectLockConfiguration(
                    SetObjectLockConfigurationArgs.builder()
                            .bucket("my-bucketname-in-eu-with-object-lock")
                            .config(config)
                            .build());
            System.out.println("object-lock configuration is set successfully");
            // 获取锁定配置
            ObjectLockConfiguration objectLockConfiguration =
                    minioClient.getObjectLockConfiguration(
                            GetObjectLockConfigurationArgs.builder()
                                    .bucket("my-lock-enabled-bucketname")
                                    .build());
    
            System.out.println("Object-lock configuration of bucket");
            System.out.println("Mode: " + objectLockConfiguration.mode());
            System.out.println("Duration: " + objectLockConfiguration.duration());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2.5 删除配置

    minio提供了一些列的delete方法用于删除配置,比较简单,就不举例说明了。

    2.5.1 删除桶的加密配置

    public void deleteBucketEncryption(DeleteBucketEncryptionArgs args)
    
    • 1

    2.5.2 删除存储桶的生命周期配置

    public void deleteBucketLifecycle(DeleteBucketLifecycleArgs args)
    
    • 1

    2.5.3 删除桶的标签

    public void deleteBucketTags(DeleteBucketTagsArgs args)
    
    • 1

    2.5.4 删除桶的桶策略配置

    public void deleteBucketPolicy(DeleteBucketPolicyArgs args)
    
    • 1

    2.5.5 删除存储桶的存储桶复制配置

    public void deleteBucketReplication(DeleteBucketReplicationArgs args)
    
    • 1

    2.5.6 删除桶的通知配置

    public void deleteBucketNotification(DeleteBucketNotificationArgs args)
    
    • 1

    3 相关工具类

    import io.minio.*;
    import io.minio.errors.*;
    import io.minio.messages.Bucket;
    
    import java.io.IOException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import java.util.List;
    
    /**
     * minio工具类
     *
     * @author wuKeFan
     * @date 2023-09-08 14:08:10
     */
    public class MinioUtil {
    
        /**
         * 判断桶是否存在
         */
        public static boolean bucketExists(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        }
    
        /**
         * 添加存储桶
         */
        public static void makeBucket(String url, String accessKey, String secretKey, String bucketName, String region, boolean objectLock)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).objectLock(objectLock).build());
        }
    
        /**
         * 指定地区添加存储桶
         */
        public static void makeBucket(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    
        /**
         * 指定地区添加存储桶并锁定对象
         */
        public static void makeBucket(String url, String accessKey, String secretKey, String bucketName, String region)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).region(region).build());
        }
    
        /**
         * 删除存储桶
         */
        public static void removeBucket(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        }
    
        /**
         * 设置桶公有
         */
        public static void setBucketPublicPolicy(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            String sb = "{\"Version\":\"2012-10-17\"," +
                    "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" +
                    "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," +
                    "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName +
                    "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" +
                    bucketName +
                    "/*\"]}]}";
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder()
                            .bucket(bucketName)
                            .config(sb)
                            .build());
        }
    
        /**
         * 设置桶私有
         */
        public static void setBucketPrivatePolicy(String url, String accessKey, String secretKey, String bucketName)
                throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder().bucket(bucketName)
                            .config(
                                    "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
                            )
                            .build());
        }
    
        /**
         * 查询存储桶信息列表
         */
        public static List<Bucket> listBuckets(String url, String accessKey, String secretKey) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
            MinioClient minioClient = MinioClient.builder().endpoint(url)
                    .credentials(accessKey, secretKey).build();
            return minioClient.listBuckets();
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
  • 相关阅读:
    微信小程序 生成跳转体验版url,可直接跳转到体验版小程序(可通过此方法测试模板消息)
    R语言使用mlr包创建回归任务、指定回归学习函数为随机森林回归模型、网格搜索、交叉验证获取随机森林的最佳超参数组合、结合最优参数组合训练最终的随机森林回归模型
    MySQL高级SQL语句(下)
    「聊设计模式」之 设计模式的前世今生
    rust学习-http-server端
    虹科分享 | Chae$4:针对金融和物流客户的新Chaes恶意软件变体 | 自动移动目标防御
    利用 Python PyPDF2库轻松提取PDF文本(及其他高级操作)
    Chromium Canvas工作流
    FPGA代码设计规范一些探讨
    IDEA创建JavaFX项目
  • 原文地址:https://blog.csdn.net/qq_37284798/article/details/132964198