阿里云OSS从入门到开发(一万五千字保姆教程)

OSS( Object Storage Service)

Java制作项目的过程中想实现文件上传功能,使用传统的上传文件存储到本地磁盘会存在许多弊端,并且极其不方便等问题 。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频 等在内的各种非结构化数据文件。

1、创建一个或者多个存储空间,向每个存储空间中添加一个或多个文件。

2、通过获取已上传文件的地址进行文件的分享和下载。

3、通过修改存储空间或文件的属性或元信息来设置相应的访问权限。

4、在阿里云管理控制台执行基本和高级OSS任务。

5、使用阿里云开发工具包或直接在应用程序中进行RESTful API调用执行基本和高级 OSS任务

1.注册阿里云账号

阿里云-计算,为了无法计算的价值

2.实名认证:

鼠标移动到头像,点击实名认证

3.开通OSS服务:

移动到三条杠这里

搜索OSS对象或者点击对象存储

点击开通服务

去支付0元

4.创建bucket

Bucket 就是你用来存储数据的地方,所有的对象(文件)都必须存放在某个 Bucket 中。每个 Bucket 可以包含多个对象(文件)

点击概况,点击创建bucket

输入名称,必须是小写

地区选离的近的: 选择接近用户或应用的地域可以降低访问延迟。如果你的应用主要在某个地区(例如中国、美国或欧洲)运行,最好选择离用户更近的区域,这样数据传输速度会更快,减少访问延迟。

注意一下这里,这里创建的时候不能修改,等到创建完成后可以把私有改成公共的

完成了创建

点击这里就可以看到我们创建的bucket

5、获取AccessKey ID 和 AccessKey Secret

在控制台中---》头像---》AccessKey

创建

通过验证

注意:!!!!创建好key后会弹出来

一定要保存好,没保存好就再也找不到

后面要用到

后续修改设置:完成所有的再设置这个

还记得当时我们创建的时候初始化是私有的吗,现在设置成公告访问的

将 Bucket 设置为公告访问使存储桶中的文件对外公开,任何人都可以通过 URL 访问这些文件。

2.OSS快速入门:

问题:SDK是什么??

SDK 是开发某个特定平台、应用或服务所需的一组工具和资源,通常包括库、API、工具、文档等。 (也就是官方提供的帮助开发的)

1.从官网获取相关SDK依赖:

点击

点击SDK示例

使用1.8版本引入此代码即可!无需再次引入下面的代码!

//创建测试工程,引入依赖
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

如果使用的是Java 9及以上的版本,需要添加的代码如下:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

2.配置环境变量:

到时候代码从环境变量中拿到这俩变量

新建环境变量

变量名:OSS_ACCESS_KEY_ID

变量名:AccessKey ID

3.官方代码

这里都是官方给的代码示例

查看OSS对应域名

因为当时我们创建bucket的时候地域不同,在这里找到对应的地域

我当时选的是北京,找到对应的就行 oss-cn-beijing

3.代码:

1.上传:

1.简单上传图片:

第三行:修改为自己的地域对应的

第七行:bucketName设置为Bucket名称

第九行:objectName设置为再Bucket中路径

第十二行:filePath设置本地要上传的路径+文件名

public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "springboottest01";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "springboottest01/1.png";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "D:\\test\\1.png";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

上传后打开自己创建的bucket就可以看到上传的1.png了

2.下载:

1.断点续传下载:

在下载OSS大文件(超过5 GB)到本地的过程中,如果出现网络中断、程序异常退出等问题导致文件下载失败,甚至重试多次仍无法完成下载,您需要使用断点续传下载的方式。断点续传下载将需要下载的大文件分成多个较小的分片并发下载,加速下载完成时间。如果下载过程中,某一分片下载失败,再次下载时会从Checkpoint文件记录的断点继续下载,无需重新下载所有分片。下载完成后,所有分片将合并成完整的文件。

public class demo {
    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "springboottest01";
        // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "springboottest01/1.png";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
        try {
            // 请求10个任务并发下载。
            DownloadFileRequest downloadFileRequest = new DownloadFileRequest(bucketName, objectName);
            // 指定Object下载到本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
            downloadFileRequest.setDownloadFile("D:\\local\\1.png");
            // 设置分片大小,单位为字节,取值范围为100 KB~5 GB。默认值为100 KB。
            downloadFileRequest.setPartSize(1 * 1024 * 1024);
            // 设置分片下载的并发数,默认值为1。
            downloadFileRequest.setTaskNum(10);
            // 开启断点续传下载,默认关闭。
            downloadFileRequest.setEnableCheckpoint(true);
            // 设置断点记录文件的完整路径,例如D:\\localpath\\examplefile.txt.dcp。
            // 只有当Object下载中断产生了断点记录文件后,如果需要继续下载该Object,才需要设置对应的断点记录文件。下载完成后,该文件会被删除。
            //downloadFileRequest.setCheckpointFile("D:\\localpath\\examplefile.txt.dcp");

            // 下载文件。
            DownloadFileResult downloadRes = ossClient.downloadFile(downloadFileRequest);
            // 下载成功时,会返回文件元数据。
            ObjectMetadata objectMetadata = downloadRes.getObjectMetadata();
            System.out.println(objectMetadata.getETag());
            System.out.println(objectMetadata.getLastModified());
            System.out.println(objectMetadata.getUserMetadata().get("meta"));
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (Throwable ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

2.流式下载:

数据并不是一次性地下载到客户端,而是分批或分段地传输。在接收数据的同时,客户端可以开始处理已经接收到的数据。

这种方法适合下载超过内存限制的文件、实时处理数据以减少内存占用,以及通过网络分步获取数据的场景

例子: 客户端在接收到数据后可以立刻开始处理(例如观看视频或听音乐),而不需要等待全部数据下载完毕。

OSS逐块读取文件内容,并将其存储到字节数组中。

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.*;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class Stream {
    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。关于其他Region对应的Endpoint信息,请参见访问域名和数据中心。
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "examplebucket";
        // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampledir/exampleobject.txt";
        // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
        String region = "cn-hangzhou";

        // 创建OSSClient实例。
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        try {
            // ossObject包含文件所在的存储空间名称、文件名称、文件元数据以及一个输入流。
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            InputStream inputStream = ossObject.getObjectContent();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            // 读取文件内容到字节数组。
            byte[] readBuffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(readBuffer)) != -1) {
                byteArrayOutputStream.write(readBuffer, 0, bytesRead);
            }
            // 获取最终的字节数组。
            byte[] fileBytes = byteArrayOutputStream.toByteArray();
            // 打印字节数组的长度。
            System.out.println("Downloaded file size: " + fileBytes.length + " bytes");
            // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
            inputStream.close();
            byteArrayOutputStream.close();
            // ossObject对象使用完毕后必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
            ossObject.close();
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (Throwable ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}

3.批量:

官方给的方法:

3.1ossutil:

这里使用ossutil

使用ossutil,您可以在Windows、macOS和Linux系统上通过命令行高效管理阿里云对象存储服务(OSS),并执行批量操作或自动化任务。本文将介绍如何通过命令行完成创建存储空间(Bucket)、上传文件、下载文件、列举文件,以及删除文件和删除Bucket等操作。

安装:

cmd进入ossutil.exe那一层,然后输入下面这句代码配置

ossutil.exe config

默认路径为 "C:\Users\Administrator.ossutilconfig",按回车键将使用默认文件

输入id和密钥,设置地区,千万不要多加一个oss-(我就这样错的)

ossutil批量上传:

ossutil cp -r D:/local/img oss://my-bucket/img/
例子:把本地D:/local/img上传到桶名/img文件夹下
ossutil cp -r D:/local/img oss://springboottest01/img/
ossutil批量下载:

ossutil cp oss://example_bucket/path/to/directory/ ./local_dir/ -r

例子:从桶名/文件下载到本地imags文件夹下
ossutil cp oss://springboottest01/img/ D:/imags -r

这样这三张图片就下载下来了

3.2 OSS Java SDK

阿里云的 OSS Java SDK 本身并不直接提供多文件下载(批量下载)的接口,但是可以通过并发的方式来实现批量下载多个文件。可以创建多个线程来并行下载多个文件

1.批量上传单线程:
@SpringBootTest
class moreTest02 {

   public static final String endpoint = "你的endpoint";
	public static final String accessKeyId = "你的accessKeyId";
	public static final String accessKeySecret = "你的accessKeySecret";
	public static final String bucketName = "你的桶名";

    public static OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    @Test
    void contextLoads() {
    }

    public static void main(String[] args) throws InterruptedException {

        //单线程插入

        Long start = System.currentTimeMillis();
        String[] urls = new String[2];
        urls[0] = "D:/local/img/5.png";
        urls[1] = "D:/local/img/4.png";

        Thread uploaderThread = null;
        for (String url : urls) {
//            String fileBaseName = url.substring(url.lastIndexOf("/") + 1);
            try {
                ossClient.putObject(bucketName, "fileBaseName.png", new File(url));
                System.out.println(url + "上传图片完成!");
            } catch (Exception e) {
                System.out.println(url + "上传图片失败!");
                e.printStackTrace();
            }
        }
        Long end = System.currentTimeMillis();

        Long time  = end - start;
        System.out.println("time = " + time);
        ossClient.shutdown();
    }

}

2.批量上传多线程:
@SpringBootTest
class moreTest01 {
    /**
     * 多线程上传图片
     */

   public static final String endpoint = "你的endpoint";
	public static final String accessKeyId = "你的accessKeyId";
	public static final String accessKeySecret = "你的accessKeySecret";
	public static final String bucketName = "你的桶名";

    public static OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    public static void main(String[] args) throws InterruptedException {
        //多线程插入

        Long start = System.currentTimeMillis();
        System.out.println("start = " + start);
        String[] urls = new String[2];
        urls[0] = "D:/local/img/5.png";
        urls[1] = "D:/local/img/4.png";

        Thread uploaderThread = null;
        for (String url : urls) {
            uploaderThread = new Thread(new Fileuploader(url,start));
            uploaderThread.start();

        }

        Thread.sleep(5000);
        ossClient.shutdown();
    }
    // 文件上传器
    static class Fileuploader implements Runnable{
        private final String fileURL;
        private final Long start;
        public Fileuploader(String fileURL,Long start) {
            this.fileURL = fileURL;
            this.start = start;
        }
        @Override
        public void run() {

            String fileBaseName = fileURL.substring(fileURL.lastIndexOf("/") + 1);
            try {

                ossClient.putObject(bucketName, fileBaseName, new File(fileURL));
                System.out.println(fileURL + "上传图片完成!");
                Long end = System.currentTimeMillis();
                Long time = end - start;
                System.out.println("time = " + time);

            } catch (Exception e) {
                System.out.println(fileURL + "上传图片失败!");
                e.printStackTrace();
            }
        }

    }
}

3.使用CompletableFuture实现异步编程

我有一个问题:多线程和异步有什么区别???这个交给你们尝试

@SpringBootTest
public class CompleteTableTests {
    @Test
    void contextLoads() {
    }

	public static final String endpoint = "你的endpoint";
	public static final String accessKeyId = "你的accessKeyId";
	public static final String accessKeySecret = "你的accessKeySecret";
	public static final String bucketName = "你的桶名";

    public static OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    public static void main(String[] args) {

        //使用completableFuture多线程

        Long start = System.currentTimeMillis();
        String[] urls = new String[3];
        urls[0] = "E:/IDEABackground/wallhaven-j3dkjq.png";
        urls[1] = "E:/IDEABackground/wallhaven-rd6req.png";
        urls[2] = "E:/IDEABackground/wallhaven-rdpvmm.png";
        //异步无返回值
        for (String url : urls) {
            System.out.println("url = " + url);
            CompletableFuture<Void> f1 = CompletableFuture.runAsync(()->{
                System.out.println(start);
                try {
                    System.out.println(start);
                    ossClient.putObject(bucketName, "fileBaseName.png", new File(url));
                    System.out.println(url + "上传图片完成!");
                } catch (Exception e) {
                    System.out.println(url + "上传图片失败!");
                    e.printStackTrace();
                }

//                ossClient.shutdown();
            });
            f1.whenComplete((Long, throwable) -> {
                Long end = System.currentTimeMillis();
                Long time = end - start;
                System.out.println("time = " + time);
            });
        }
        try {
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.3ossbrowser

ossbrowser是阿里云官方提供的OSS图形化管理工具,

4.拓展知识点:

1.冷归档存储和深度冷归档存储

冷归档、深度冷归档不支持开启直读,冷归档、深度冷归档类型的Object需要解冻后才能读取。

1.1冷归档:

冷归档存储通常指存储那些较少访问、长期存储的数据。例如,备份数据、日志文件、合规性数据等,这些数据的访问频率较低,但必须保存一定时间以满足法律或行业规定。

与常规存储相比,冷归档存储的存储费用通常较低,但读取数据的费用较高。

由于冷归档存储的目的是节省成本,它可能需要一定的时间来检索数据。当需要访问这些数据时,恢复时间通常较慢,需要几小时或几天不等。

1.2深度冷归档:

深度冷归档存储是冷归档存储的更高效、更便宜的一种类型,专门用于存储几乎永远不会访问的数据,或者只在非常长时间后才可能需要访问的数据。例如,用于存档历史数据、长期存储的记录等。

  1. 价格比冷归档存储更低
  2. 恢复时间非常长

2:SDK 代表 Software Development Kit(软件开发工具包)

是一组为开发者提供的工具、库和文档,用于帮助他们创建应用程序或软件。SDK 通常包含开发所需的必要资源 API(应用程序接口),库和框架,示例代码等

5.阿里云存储深入了解:

5.1层级问题

一个桶里放一些文件。

对象存储没有目录层级结构

虽然我们可以在这里看到有文件夹

这其实只是模拟实现的。

Object 会存储 id、文件内容、元数据三部分信息:

就像打了个 标签 一样,并不是说文件存储在这个 标签 下,只是你可以用这个 标签 来检索文件。

5.2:accessKey 不够安全

不提供资源拥有者所属账号的访问密钥(AccessKey)的情况下,通过临时访问凭证以及签名URL的方式授权第三方下载文件(Object)。

阿里云STS(Security Token Service)是阿里云提供的一种临时访问权限管理服务。RAM提供RAM用户和RAM角色两种身份。其中,RAM角色不具备永久身份凭证,而只能通过STS获取可以自定义时效和访问权限的临时身份凭证,即安全令牌(STS Token)。

先创建用户再授权:

这个页面记得保存,不然就找不到了

 

6.上传头像到OSS:

1.工具类:

@ConfigurationProperties(prefix = "oss")注解用于将配置文件中的属性绑定到Java对象的字段上。prefix = "oss"指定了配置文件中属性的前缀,即所有以oss.开头的属性都会被绑定到AliOSSUtils类的相应字段上。

自动从配置文件中读取并绑定相应的属性值

/**
 * 阿里云 OSS 工具配置类
 */
@Data
@Component
@ConfigurationProperties(prefix = "oss")
public class AliOSSUtils {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

}

2.配置文件:

oss:
  endpoint: oss-cn-beijing.aliyuncs.com
  access-key-id: 你的id
  access-key-secret: 你的密钥
  bucket-name: springboottest01

3.controller:

@Controller
@ResponseBody
public class OssUploadController {

    @Autowired
    private AliOSSUtils aliOSSUtils;

    /**
     * 阿里云OSS存储:将用户上传的图片放入阿里云OSS上
     * @throws IOException
     */
    @PostMapping("/ossupload")
    public String uploadDemo(String username, MultipartFile image) throws IOException {
        // 用户名,作参照,无意义,去掉也行
        System.out.println(username);

        // 1.获取用户上传的文件名
        String filename = image.getOriginalFilename();

        // 优化:为了保证图片名称的唯一性 不会被多用户覆盖
        filename = UUID.randomUUID() + filename.substring(filename.lastIndexOf("."));

        // 创建OSSClient实例
        OSS ossClient = new OSSClientBuilder().build(aliOSSUtils.getEndpoint(), aliOSSUtils.getAccessKeyId(), aliOSSUtils.getAccessKeySecret());

        try {
            // 上传文件到OSS
            ossClient.putObject(aliOSSUtils.getBucketName(), filename, image.getInputStream());
            System.out.println("File uploaded: " + filename);
        } catch (Exception e) {
            e.printStackTrace();
            return "上传失败";
        } finally {
            // 关闭OSSClient
            ossClient.shutdown();
        }
        return "上传成功";
    }
}

4.html代码:

enctype="multipart/form-data"用于指定表单数据的编码类型。它在文件上传场景中非常重要。

  1. 只有multipart/form-data编码类型支持文件上传。其他编码类型无法处理文件数据
  2. multipart/form-data 。 指定传输数据为二进制类型,比如图片、mp3、文件。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图片上传</title>
</head>
<body>
<form method="post" action="/ossupload" enctype="multipart/form-data">
    用户名:<input name="username" type="text" />
    头像:<input name="image" type="file"/>
    提交:<input type="submit" value="提交">
</form>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值