EasyExcel 学习笔记

简介


官网地址:https://easyexcel.opensource.alibaba.com/
项目地址:https://gitee.com/HUGUOAN/easy-excel

快速开始

新建项目

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

导入依赖

EasyExcel -> QuickStart -> pom.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hga</groupId>
    <artifactId>QuickStart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

安装插件

File -> settings -> plugins -> Marketplace

在这里插入图片描述

开启注解编译

File -> settings -> Build、Execution、Deployment -> Complier -> Annotation Processors

在这里插入图片描述

新建项目结构

在这里插入图片描述

代码编写

1. 工具类 - FilePathUtil

package com.hga.utils;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

/**
 *  获取项目类路径-工具类
 */
public class FilePathUtil {
    /**
     * 获取项目类路径
     * @return 类路径地址
     */
    public static String getPath(){
        String path = FilePathUtil.class.getClassLoader().getResource("").getPath();
        // 设置路径字符集为 UTF-8
        path = URLDecoder.decode(path, StandardCharsets.UTF_8);
        return path;
    }

    /**
     * 获取项目路径
     * @return 项目路径地址
     */
    public static String getProjectPath(){
        return FilePathUtil.class.getResource("/").getPath().replace("classes/","");
    }

    /**
     * 用于测试输出路径
     * @param args
     */
    public static void main(String[] args) {
        System.out.println(getPath());   //  E:/Java/Study/EasyExcel/QuickStart/target/classes/
        System.out.println(getProjectPath());  // E:/Java/Study/EasyExcel/QuickStart/target/
    }
}

2. 简单写excel

官网案例:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write

  1. 编写模型类并加入注解
  2. 编写获取测试数据的方法
  3. 调用官方API完成写功能
2.1 编写模型类并加入注解

在这里插入图片描述

com -> hga -> pojo -> Employee.java

package com.hga.pojo;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 与 excel 文件相对应的模型类
 *
 * 解释: excel 中每一列 对应于 模型中的每一个属性
 */
@Data                                // lombok 中的注解 : 帮助我们构建setter、getter、toString、equals、hashcode 方法
@NoArgsConstructor                   // lombok 中的注解 : 生成无参构造方法
@AllArgsConstructor                  // lombok 中的注解 : 生成全参数构造方法 顺序为属性编写的顺序
public class Employee {
    /*
        @ExcelProperty("文字")  easyExcel 中的注解: 添加到对应的属性上,对应文字,将是生成的excel的列名
        @ExcelIgnore           easyExcel 中的注解: 添加到对应的属性上,忽略当前字段,将不会生成对应的excel的列
     */
    // 成员变量
    @ExcelProperty("员工工号")        
    private Integer id;
    @ExcelProperty("员工姓名")
    private String name;
    @ExcelProperty("入职日期")
    private Date date;
    @ExcelProperty("员工工资")
    private Double salary;
    /**
     * 忽略的属性
     */
    @ExcelIgnore
    private String ignore;
}
2.2 编写获取测试数据的方法

在这里插入图片描述

com -> hga -> write -> SimpleWrite.java

package com.hga.write;

import com.alibaba.excel.util.ListUtils;
import com.hga.pojo.Employee;

import java.util.Date;
import java.util.List;

/**
 * 快速入门: 写数据
 */
public class SimpleWrite {
    /**
     * 测试数据
     * @param count 条数
     * @return 数据集合
     */
    private List<Employee> data(int count) {
        List<Employee> list = ListUtils.newArrayList();    // alibaba 提供的工具类创建 集合
        for (int i = 1; i <= count; i++) {
            list.add(new Employee(i, "姓名"+i,new Date(),6.6 * i,""));
        }
        return list;
    }
}
2.3 调用官方API完成写功能

com -> hga -> write -> SimpleWrite.java

官方提供的三种方法:

/**
     * 最简单的写
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 直接写即可
     */
    @Test
    public void simpleWrite() {
        // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入

        // 写法1 JDK8+
        // since: 3.0.0-beta1
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class)
            .sheet("模板")
            .doWrite(() -> {
                // 分页查询数据
                return data();
            });

        // 写法2
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());

        // 写法3
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        }
    }

本案例使用第二种:

    @Test
    public void write(){
        // 获取文件路径 并拼接文件名
        String fileName = FilePathUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, Employee.class).sheet("sheet-写入的数据").doWrite(data(10));
    }
2.4 查看结果

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

3. 简单读excel

官方案例:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read

  1. 编写模型类并加入注解
  2. 编写监听器
  3. 调用官方API完成读功能
3.1 编写模型类并加入注解

这里我们就读取刚刚写的excel,模型类就是 Employee.java

3.2 编写监听器

监听器:当从excel中读取数据时,就会触发监听器中的对应方法。

3.3 调用官方API完成读功能

在这里插入图片描述

com -> hga -> read -> SimpleRead

官方提供的三种方法:

 /**
     * 最简单的读
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>
     * 3. 直接读即可
     */
    @Test
    public void simpleRead() {
        // 写法1:JDK8+ ,不用额外写一个DemoDataListener
        // since: 3.0.0-beta1
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
            for (DemoData demoData : dataList) {
                log.info("读取到一条数据{}", JSON.toJSONString(demoData));
            }
        })).sheet().doRead();

        // 写法2:
        // 匿名内部类 不用额外写一个DemoDataListener
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
            /**
             * 单次缓存的数据量
             */
            public static final int BATCH_COUNT = 100;
            /**
             *临时存储
             */
            private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

            @Override
            public void invoke(DemoData data, AnalysisContext context) {
                cachedDataList.add(data);
                if (cachedDataList.size() >= BATCH_COUNT) {
                    saveData();
                    // 存储完成清理 list
                    cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                saveData();
            }

            /**
             * 加上存储数据库
             */
            private void saveData() {
                log.info("{}条数据,开始存储数据库!", cachedDataList.size());
                log.info("存储数据库成功!");
            }
        }).sheet().doRead();

        // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
        // 写法3:
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();

        // 写法4
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 一个文件一个reader
        try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
            // 构建一个sheet 这里可以指定名字或者no
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            // 读取一个sheet
            excelReader.read(readSheet);
        }
    }

本案例使用第一种:

package com.hga.read;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

/**
 *  快速入门: 读数据
 */
@Slf4j
public class SimpleRead {
    @Test
    public void read(){
        String fileName = FilePathUtil.getProjectPath() + "simpleWrite1711275798756.xlsx";
        // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`监听器的构造函数设置(默认提供的监听器) 可以自己实现
        EasyExcel.read(fileName, Employee.class, new PageReadListener<Employee>(dataList -> {
            for (Employee data : dataList) {
                System.out.println(data.toString());
            }
        })).sheet().doRead();
    }
}
3.4 查看结果

在这里插入图片描述

进阶教程

1. 批量写数据

在这里插入图片描述

com -> hga -> write -> ManyWrite.java

官方提供的方法:
官方提供

 /**
     * 重复多次写入
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>
     * 2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>
     * 3. 直接调用二次写入即可
     */
    @Test
    public void repeatedWrite() {
        // 方法1: 如果写到同一个sheet
        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
            for (int i = 0; i < 5; i++) {
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法2: 如果写到不同的sheet 同一个对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

        // 方法3 如果写到不同的sheet 不同的对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class
                // 实际上可以一直变
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<DemoData> data = data();
                excelWriter.write(data, writeSheet);
            }
        }

    }

本案例使用第二种:

package com.hga.write;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * 批量写数据
 */
public class ManyWrite {

    /**
     * 测试数据
     * @param count 条数
     * @return 数据集合
     */
    private List<Employee> data(int count) {
        List<Employee> list = ListUtils.newArrayList();    // alibaba 提供的工具类创建 集合
        for (int i = 1; i <= count; i++) {
            list.add(new Employee(i, "姓名"+i,new Date(),6.6 * i,""));
        }
        return list;
    }

    @Test
    public void write(){
        // 方法2: 如果写到不同的sheet 同一个对象
        String fileName = FilePathUtil.getProjectPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, Employee.class).build()) {
            // 统计开始写前的时间
            long startTime = System.currentTimeMillis();
            // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
            for (int i = 0; i < 5; i++) {
                // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                List<Employee> data = data(1000000);
                excelWriter.write(data, writeSheet);
            }
            // 统计结束的时间
            long entTime = System.currentTimeMillis();

            System.out.println("最后耗时时间:"+ (entTime - startTime) + "毫秒");
        }
    }
}

在这里插入图片描述

2. 模板写数据

写入到excel中的数据格式不是很好看,我们可以提前写好一些样式,形成模板,然后导入数据。
官网案例:官方方法

2.1 编写读取单条数据模板

在这里插入图片描述
结果:(这里就不演示代码了)
在这里插入图片描述

2.2 编写读取多条数据模板

在这里插入图片描述
使用模板写入多条数据代码:
在这里插入图片描述

com -> hga -> write -> FillWrite.java

package com.hga.write;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * 填充代码: 使用模板
 */
public class FillWrite {
    /**
     * 测试数据
     * @param count 条数
     * @return 数据集合
     */
    private List<Employee> data(int count) {
        List<Employee> list = ListUtils.newArrayList();    // alibaba 提供的工具类创建 集合
        for (int i = 1; i <= count; i++) {
            list.add(new Employee(i, "张三"+i,new Date(),6.6 * i,""));
        }
        return list;
    }

    @Test
    public void write(){
        // 方案2 分多次 填充 会使用文件缓存(省内存)
        // 获取文件路径 并拼接文件名
        String fileName = FilePathUtil.getProjectPath() + "listFill" + System.currentTimeMillis() + ".xlsx";
        // 读取模板文件
        String templateFileName = FilePathUtil.getProjectPath() + "template.xlsx";
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();
            excelWriter.fill(data(10), writeSheet);
        }
    }

}

在这里插入图片描述

3. 自定义监听器读数据

官网案例:官网案例

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;

    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        demoDAO.save(cachedDataList);
        log.info("存储数据库成功!");
    }
}

自定义监听器
在这里插入图片描述
定义Dao层,操作数据库操作
在这里插入图片描述

3.1 编写Dao层方法

com -> hga -> dao -> EmployeeDao.java

package com.hga.dao;

import com.hga.pojo.Employee;
import java.util.List;

/**
 * 模拟操作数据库方法
 */
public class EmployeeDao {

    public void save(List<Employee> list){
        // 模拟写入到数据中
        list.forEach(System.out::println);
    }
}

3.2 编写监听器

com -> hga -> listener -> EmployeeListener.java

package com.hga.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.hga.dao.EmployeeDao;
import com.hga.pojo.Employee;

import java.util.ArrayList;

/**
 * 监听器: 类似于回调函数,在开始前做什么操作,在完成后做什么操作等。
 */
public class EmployeeListener  implements ReadListener<Employee> {
    // 定义缓存集合的大小
    private int count = 5;
    // 定义缓存数据集合
    private ArrayList<Employee> list = new ArrayList<>(count);
    
    // 导入dao层接口
    private EmployeeDao employeeDao;
    
    public EmployeeListener(EmployeeDao dao){
        this.employeeDao = dao;
    }


    // 每次读取一行数据都会执行监听器中的这个方法
    @Override
    public void invoke(Employee employee, AnalysisContext analysisContext) {
        // 如果每读一条数据,就执行插入数据库中,那么效率必定会慢,所以我们先将读取的数据,写入到缓存集合中,当缓存集合达到指定大小,再写入到数据库中,然后清空缓存集合。
        list.add(employee);
        // 判断
        if(list.size() >= count){
            // 写入数据库中,
            employeeDao.save(list);
            
            // 清空缓存集合
            list.clear();
        }
    }

    // 读取完整个excel之后,执行这个方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 可能读取完剩余几条数据没有达到缓存大小,所以在读取完整个sheet页后,再向数据库中写入当前缓存集合中剩余的数据
        // 写入数据库中,
        employeeDao.save(list);

        // 清空缓存集合
        list.clear();
    }
}

3.3 使用自定义监听器读excel

com -> hga -> read -> MyRead.java

package com.hga.read;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.hga.dao.EmployeeDao;
import com.hga.listener.EmployeeListener;
import com.hga.pojo.Employee;
import com.hga.utils.FilePathUtil;
import org.junit.Test;

/**
 * 使用自定义监听器读取数据
 */
public class MyRead {

    @Test
    public void read(){
        String fileName = FilePathUtil.getProjectPath() + "simpleWrite1711275798756.xlsx";
        // 构建excel读对象, 使用自定义的EmployeeListener 对象。
        ExcelReader reader = EasyExcel.read(fileName, Employee.class, new EmployeeListener(new EmployeeDao())).build();
        // 构建easyExcel的sheet页读对象
        ReadSheet sheet = EasyExcel.readSheet().build();
        reader.read(sheet);
    }
}

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值