ExcelJS架构解析:核心模块与设计模式

ExcelJS架构解析:核心模块与设计模式

本文深入解析ExcelJS的核心架构,重点分析Workbook和Worksheet类的设计模式、XForm转换器体系、流处理与内存优化机制,以及插件系统与扩展能力。通过组合模式、工厂方法、迭代器模式等设计模式的运用,ExcelJS实现了高效的Excel文档操作能力。文章详细探讨了稀疏数组存储、共享字符串优化、分批处理策略等内存管理技术,并展示了如何通过扩展机制定制功能,为处理大型Excel文件提供了完整的解决方案。

Workbook和Worksheet类结构分析

ExcelJS作为一款功能强大的Excel处理库,其核心架构围绕Workbook和Worksheet两个关键类展开。这两个类构成了整个库的基础骨架,通过精心的设计模式和模块化架构,实现了对Excel文档的全面操作能力。

Workbook类:工作簿管理器

Workbook类是ExcelJS的顶层容器,负责管理整个工作簿的生命周期、工作表集合以及全局资源。其设计采用了组合模式(Composite Pattern),将多个Worksheet对象组织成一个层次结构。

核心属性结构
class Workbook {
  constructor() {
    this.category = '';           // 文档分类
    this.company = '';            // 公司信息
    this.created = new Date();    // 创建时间
    this.modified = this.created; // 修改时间
    this.properties = {};         // 文档属性
    this.calcProperties = {};     // 计算属性
    this._worksheets = [];        // 工作表数组(稀疏数组)
    this._definedNames = new DefinedNames(); // 定义名称管理
    this.views = [];              // 工作簿视图
    this.media = [];              // 媒体资源(图片等)
    this.pivotTables = [];        // 数据透视表
  }
}
工作表管理机制

Workbook采用稀疏数组(_worksheets)来存储工作表,这种设计允许高效的工作表添加、删除和查找操作:

mermaid

工作表管理的关键方法包括:

  • addWorksheet(): 添加新工作表,自动分配ID和顺序号
  • removeWorksheet(): 移除指定工作表
  • getWorksheet(): 通过ID或名称查找工作表
  • worksheets getter: 返回排序后的可见工作表列表
文件格式支持

Workbook通过适配器模式支持多种文件格式:

get xlsx() {
  if (!this._xlsx) this._xlsx = new XLSX(this);
  return this._xlsx;
}

get csv() {
  if (!this._csv) this._csv = new CSV(this);
  return this._csv;
}

Worksheet类:工作表实现

Worksheet类代表单个工作表,实现了单元格管理、行列操作、样式设置等核心功能。其设计采用了中介者模式(Mediator Pattern),协调行、列、单元格之间的复杂交互。

核心数据结构
class Worksheet {
  constructor(options) {
    this._rows = [];              // 行数据(稀疏数组)
    this._columns = null;         // 列定义
    this._keys = {};              // 列键映射
    this._merges = {};            // 合并单元格记录
    this.properties = {};         // 工作表属性
    this.pageSetup = {};          // 页面设置
    this.headerFooter = {};       // 页眉页脚
    this.dataValidations = new DataValidations(); // 数据验证
    this.views = [];              // 工作表视图
    this._media = [];             // 媒体资源
  }
}
行列管理架构

Worksheet使用稀疏数组管理行数据,实现了高效的内存使用和快速访问:

mermaid

单元格操作接口

Worksheet提供了丰富的单元格操作方法:

方法名功能描述使用示例
getCell(ref)获取指定单元格worksheet.getCell('A1')
getRow(number)获取指定行worksheet.getRow(1)
getColumn(col)获取指定列worksheet.getColumn('A')
mergeCells(range)合并单元格worksheet.mergeCells('A1:B2')
addRow(data)添加数据行worksheet.addRow([1, 2, 3])
样式和格式管理

Worksheet通过属性对象管理样式配置:

// 默认行高和样式配置
this.properties = {
  defaultRowHeight: 15,
  dyDescent: 55,
  outlineLevelCol: 0,
  outlineLevelRow: 0
};

// 页面设置配置
this.pageSetup = {
  margins: {left: 0.7, right: 0.7, top: 0.75, bottom: 0.75},
  orientation: 'portrait',
  fitToPage: false,
  scale: 100
};

设计模式应用分析

1. 工厂方法模式(Factory Method)

Workbook的addWorksheet方法实现了工厂方法模式,封装了Worksheet对象的创建过程:

addWorksheet(name, options) {
  const id = this.nextId;
  const worksheetOptions = {
    id,
    name,
    orderNo: lastOrderNo + 1,
    workbook: this,
    ...options
  };
  return new Worksheet(worksheetOptions);
}
2. 迭代器模式(Iterator Pattern)

Worksheet提供了eachRow方法来遍历所有行,实现了迭代器模式:

eachRow(options, iteratee) {
  // 实现行迭代逻辑
  for (let rowNumber = minRow; rowNumber <= maxRow; rowNumber++) {
    const row = this.getRow(rowNumber);
    if (row && row.hasValues) {
      iteratee(row, rowNumber);
    }
  }
}
3. 观察者模式(Observer Pattern)

通过事件机制,Worksheet可以通知相关组件状态变化:

// 样式变化时通知相关单元格
cell.style = newStyle;
// 触发样式更新事件

性能优化策略

内存管理优化
  • 稀疏数组存储: 使用_rows稀疏数组避免存储空行,节省内存
  • 延迟初始化: 单元格和行列对象在首次访问时创建
  • 批量操作: 提供addRows等方法支持批量数据添加
访问性能优化

mermaid

扩展性设计

Worksheet类通过模块化设计支持功能扩展:

// 数据验证模块
this.dataValidations = new DataValidations();

// 条件格式模块  
this.conditionalFormattings = [];

// 表格功能
this.tables = {};

// 数据透视表
this.pivotTables = [];

这种架构设计使得ExcelJS能够灵活应对各种复杂的Excel操作需求,同时保持良好的性能和可维护性。

XForm转换器模式解析

ExcelJS采用了一种高度模块化和可扩展的XForm转换器模式来处理Excel文件的XML序列化和反序列化。这种设计模式是项目架构的核心,负责将JavaScript对象模型与Office Open XML格式之间进行双向转换。

XForm体系结构

XForm转换器体系采用分层设计,包含基础XForm、简单XForm、复合XForm和列表XForm等多种类型,形成了一个完整的转换器生态系统。

mermaid

核心XForm类型详解

1. BaseXform基类

BaseXform是所有转换器的基类,定义了统一的接口规范:

class BaseXform {
  // 准备阶段:模型预处理
  prepare(model, options) {
    // 可选准备步骤,用于模型预处理
  }

  // 渲染阶段:模型转XML
  render(xmlStream, model) {
    // 将模型转换为XML流
  }

  // 解析阶段:XML转模型
  parseOpen(node) { /* XML节点打开处理 */ }
  parseText(text) { /* 文本内容处理 */ }
  parseClose(name) { /* XML节点关闭处理 */ }

  // 协调阶段:后处理
  reconcile(model, options) {
    // 可选的后处理步骤
  }
}
2. 简单XForm实现

简单XForm处理基本数据类型,如字符串、布尔值、数字等:

class StringXform extends BaseXform {
  constructor(options) {
    super();
    this.tag = options.tag;    // XML标签名
    this.attr = options.attr;  // 属性名(如存在)
  }

  render(xmlStream, model) {
    if (model !== undefined) {
      xmlStream.openNode(this.tag);
      if (this.attr) {
        xmlStream.addAttribute(this.attr, model);
      } else {
        xmlStream.writeText(model);
      }
      xmlStream.closeNode();
    }
  }

  parseOpen(node) {
    if (node.name === this.tag) {
      if (this.attr) {
        this.model = node.attributes[this.attr];
      } else {
        this.text = [];
      }
    }
  }
}
3. 复合XForm实现

复合XForm通过映射表管理子转换器,处理复杂结构:

class CompositeXform extends BaseXform {
  parseOpen(node) {
    // 查找匹配的子转换器
    this.parser = this.parser || this.map[node.name];
    if (this.parser) {
      this.parser.parseOpen(node);
      return true;
    }

    if (node.name === this.tag) {
      this.model = this.createNewModel(node);
      return true;
    }

    return false;
  }

  onParserClose(name, parser) {
    // 子转换器完成解析后的回调
    this.model[name] = parser.model;
  }
}

XForm转换流程

XForm转换器遵循严格的四阶段处理流程:

mermaid

实际应用示例

以CoreXform为例,展示复合XForm的实际应用:

class CoreXform extends BaseXform {
  constructor() {
    super();
    this.map = {
      'dc:creator': new StringXform({tag: 'dc:creator'}),
      'dc:title': new StringXform({tag: 'dc:title'}),
      'cp:lastModifiedBy': new StringXform({tag: 'cp:lastModifiedBy'}),
      'cp:lastPrinted': new DateXform({tag: 'cp:lastPrinted', format: CoreXform.DateFormat}),
      'dcterms:created': new DateXform({
        tag: 'dcterms:created',
        attrs: CoreXform.DateAttrs,
        format: CoreXform.DateFormat,
      }),
    };
  }

  render(xmlStream, model) {
    xmlStream.openNode('cp:coreProperties', CoreXform.CORE_PROPERTY_ATTRIBUTES);
    
    // 委托给子转换器进行渲染
    this.map['dc:creator'].render(xmlStream, model.creator);
    this.map['dc:title'].render(xmlStream, model.title);
    this.map['cp:lastModifiedBy'].render(xmlStream, model.lastModifiedBy);
    
    xmlStream.closeNode();
  }
}

设计优势分析

XForm转换器模式在ExcelJS中展现出以下设计优势:

特性优势描述应用示例
模块化设计每个XForm只负责特定类型的转换,职责单一StringXform只处理字符串转换
可组合性复合XForm可以嵌套其他XForm,形成层次结构CoreXform包含多个子XForm
双向转换支持模型到XML和XML到模型的双向转换统一的render和parse接口
流式处理支持XML流处理,内存效率高parseStream方法处理大文件
类型安全强类型转换确保数据一致性各种to*Attribute工具方法

高级特性

1. 条件渲染机制

XForm支持智能的条件渲染,只在必要时生成XML内容:

static toAttribute(value, dflt, always = false) {
  if (value === undefined) {
    if (always) {
      return dflt;
    }
  } else if (always || value !== dflt) {
    return value.toString();
  }
  return undefined; // 不生成属性
}
2. 弱引用缓存

StylesXform等复杂转换器使用WeakMap进行对象缓存,避免重复处理:

addStyleModel(model, cellType) {
  if (this.weakMap && this.weakMap.has(model)) {
    return this.weakMap.get(model); // 返回缓存结果
  }
  // ...处理样式
  this.weakMap.set(model, styleId); // 缓存结果
  return styleId;
}
3. 异步解析支持

所有XForm都支持异步解析,适用于流式处理场景:

async parseStream(stream) {
  return this.parse(parseSax(stream));
}

XForm转换器模式是ExcelJS架构的精髓所在,它通过高度模块化的设计实现了复杂的XML处理逻辑,同时保持了代码的可维护性和扩展性。这种模式不仅适用于Excel文件处理,也为其他XML密集型应用提供了优秀的架构参考。

流处理与内存优化机制

ExcelJS在处理大型Excel文件时面临着严峻的内存挑战,特别是当文件包含数万行数据或大量共享字符串时。为了解决这一问题,ExcelJS实现了一套高效的流处理架构和内存优化机制,使得开发者能够处理GB级别的Excel文件而不会耗尽系统内存。

流式读写架构

ExcelJS的流处理架构基于Node.js的Stream API,采用了生产者-消费者模式来处理Excel文件。整个架构可以分为三个主要层次:

mermaid

核心流处理类

ExcelJS提供了专门的流处理类来处理大型Excel文件:

类名功能描述内存优化特性
WorkbookWriter工作簿级流写入分工作表处理,延迟提交
WorksheetWriter工作表级流写入行级流处理,分批提交
WorkbookReader工作簿级流读取SAX解析,事件驱动
WorksheetReader工作表级流读取按行解析,即时释放
内存缓冲机制

ExcelJS实现了自定义的StreamBuf类来优化内存使用,这是一个双缓冲区的设计:

class StreamBuf extends Stream.Duplex {
  constructor(options) {
    this.bufSize = options.bufSize || 1024 * 1024; // 1MB默认缓冲区
    this.buffers = []; // 缓冲区池
    this.batch = options.batch || false; // 批处理模式
  }
  
  // 智能内存管理:按需分配,及时释放
  _getWritableBuffer() {
    if (this.buffers.length && !this.buffers[this.buffers.length - 1].full) {
      return this.buffers[this.buffers.length - 1];
    }
    const buf = new ReadWriteBuf(this.bufSize);
    this.buffers.push(buf);
    return buf;
  }
}

共享字符串优化

在处理包含大量重复文本的Excel文件时,共享字符串机制可以显著减少内存占用:

mermaid

共享字符串表实现
class SharedStrings {
  constructor() {
    this._map = new Map();       // 字符串到索引的映射
    this._array = [];            // 索引到字符串的映射
    this.count = 0;              // 总字符串数量
    this.uniqueCount = 0;        // 唯一字符串数量
  }

  add(value) {
    let index = this._map.get(value);
    if (index === undefined) {
      index = this.uniqueCount;
      this._map.set(value, index);
      this._array.push(value);
      this.uniqueCount++;
    }
    this.count++;
    return index;
  }
  
  // 内存使用统计
  get memoryUsage() {
    let totalSize = 0;
    for (const str of this._array) {
      totalSize += Buffer.byteLength(str, 'utf8');
    }
    return {
      totalStrings: this.count,
      uniqueStrings: this.uniqueCount,
      memoryBytes: totalSize,
      compressionRatio: (this.count / this.uniqueCount).toFixed(2)
    };
  }
}

分批处理与延迟提交

ExcelJS采用分批处理策略来避免内存峰值,通过cork()uncork()机制控制数据流:

// 工作表写入器的分批提交机制
class WorksheetWriter {
  constructor(options) {
    this.rows = [];
    this.batchSize = options.batchSize || 1000; // 每1000行提交一次
  }
  
  addRow(rowData) {
    this.rows.push(rowData);
    if (this.rows.length >= this.batchSize) {
      this._commitBatch();
    }
  }
  
  _commitBatch() {
    // 使用StreamBuf进行分批写入
    const streamBuf = new StreamBuf({batch: true});
    this.rows.forEach(row => {
      this._writeRowToStream(row, streamBuf);
    });
    
    // 提交到ZIP压缩流
    this.zip.append(streamBuf, {name: `xl/worksheets/sheet${this.id}.xml`});
    this.rows = []; // 清空当前批次
  }
}

性能优化策略

1. 缓冲区大小调优

ExcelJS允许根据可用内存动态调整缓冲区大小:

// 根据系统内存自动调整缓冲区
function getOptimalBufferSize() {
  const totalMem = os.totalmem();
  if (totalMem > 16 * 1024 * 1024 * 1024) { // 16GB以上
    return 4 * 1024 * 1024; // 4MB
  } else if (totalMem > 8 * 1024 * 1024 * 1024) { // 8GB
    return 2 * 1024 * 1024; // 2MB
  } else {
    return 1 * 1024 * 1024; // 1MB
  }
}
2. 内存使用监控

内置内存监控机制帮助开发者识别内存瓶颈:

class MemoryMonitor {
  constructor() {
    this.peakMemory = 0;
    this.samples = [];
  }
  
  sample() {
    const memory = process.memoryUsage();
    this.peakMemory = Math.max(this.peakMemory, memory.heapUsed);
    this.samples.push({
      timestamp: Date.now(),
      heapUsed: memory.heapUsed,
      heapTotal: memory.heapTotal,
      external: memory.external
    });
    
    // 内存警告阈值(500MB)
    if (memory.heapUsed > 500 * 1024 * 1024) {
      this.emit('memoryWarning', memory);
    }
  }
  
  getReport() {
    return {
      peakMemory: this.peakMemory,
      averageMemory: this.samples.reduce((sum, s) => sum + s.heapUsed, 0) / this.samples.length,
      sampleCount: this.samples.length
    };
  }
}

实际应用示例

流式写入大型数据集
const ExcelJS = require('exceljs');
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({
  filename: 'large-dataset.xlsx',
  useSharedStrings: true, // 启用共享字符串优化
  useStyles: false        // 禁用样式以减少内存使用
});

const worksheet = workbook.addWorksheet('Data');
const batchSize = 5000;

// 模拟流式写入100万行数据
async function writeLargeDataset() {
  let rowCount = 0;
  
  for (let i = 0; i < 1000000; i++) {
    worksheet.addRow({
      id: i + 1,
      name: `Item ${i + 1}`,
      value: Math.random() * 1000,
      timestamp: new Date()
    });
    
    rowCount++;
    
    // 每5000行提交一次批次
    if (rowCount % batchSize === 0) {
      await worksheet.commit();
      console.log(`Committed ${rowCount} rows, memory: ${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB`);
    }
  }
  
  // 提交剩余数据
  await worksheet.commit();
  await workbook.commit();
}

writeLargeDataset().catch(console.error);
内存使用对比表

下表展示了不同处理模式下的内存使用情况:

处理模式100万行内存占用处理时间适用场景
全内存模式800-1200MB快速小型文件(<10MB)
流式处理(无共享字符串)200-300MB中等中型文件(10-100MB)
流式处理(有共享字符串)100-150MB稍慢大型文件(>100MB)
分批次流处理50-80MB较慢超大型文件(>1GB)

最佳实践建议

  1. 根据文件大小选择模式:小文件使用全内存模式,大文件使用流式处理
  2. 启用共享字符串:对于文本密集型文件,可减少30-70%的内存使用
  3. 合理设置批次大小:根据可用内存调整batchSize,通常5000-10000行为宜
  4. 监控内存使用:在生产环境中实施内存监控,预防内存泄漏
  5. 及时释放资源:处理完成后调用workbook.commit()确保资源释放

通过这套流处理与内存优化机制,ExcelJS能够高效处理从KB到GB级别的Excel文件,为开发者提供了灵活而强大的数据处理能力。

插件系统与扩展能力

ExcelJS作为一个功能强大的Excel处理库,其架构设计充分考虑了扩展性和灵活性。虽然项目本身没有显式的插件系统,但通过巧妙的模块化设计和面向对象架构,为开发者提供了多种扩展方式。

核心扩展机制

ExcelJS通过类继承和组合模式提供了强大的扩展能力。每个核心组件都设计为可扩展的基类,允许开发者通过继承来添加自定义功能。

// 自定义工作表类示例
class CustomWorksheet extends ExcelJS.Workbook.Worksheet {
  constructor(options) {
    super(options);
    this.customProperties = {};
  }
  
  addCustomFeature(data) {
    // 实现自定义功能
    this.customProperties = { ...this.customProperties, ...data };
    return this;
  }
  
  toCustomJSON() {
    return {
      ...super.toJSON(),
      customProperties: this.customProperties
    };
  }
}

事件系统与钩子机制

虽然ExcelJS没有内置的事件发射器,但通过方法重写可以实现类似钩子的功能:

mermaid

// 方法拦截示例
class HookableWorksheet extends ExcelJS.Workbook.Worksheet {
  addRow(data, options) {
    // 前置钩子
    if (this.beforeAddRow) {
      this.beforeAddRow(data, options);
    }
    
    const result = super.addRow(data, options);
    
    // 后置钩子
    if (this.afterAddRow) {
      this.afterAddRow(result, data, options);
    }
    
    return result;
  }
}

自定义值类型扩展

ExcelJS支持多种单元格值类型,开发者可以通过扩展机制添加自定义值类型:

内置值类型描述扩展方式
NumberValue数字值继承CellValue基类
StringValue字符串值实现render方法
FormulaValue公式值注册自定义解析器
RichTextValue富文本值添加样式处理器
// 自定义值类型示例
class CustomValueType extends ExcelJS.CellValue {
  constructor(value) {
    super();
    this.value = value;
  }
  
  get type() {
    return 'custom';
  }
  
  render() {
    return `CUSTOM:${this.value}`;
  }
  
  toJSON() {
    return {
      type: this.type,
      value: this.value
    };
  }
}

样式与格式扩展

ExcelJS的样式系统采用组合模式设计,易于扩展新的样式属性:

mermaid

// 样式扩展示例
workbook.Style.prototype.addCustomProperty = function(key, value) {
  if (!this.customProperties) {
    this.customProperties = {};
  }
  this.customProperties[key] = value;
  return this;
};

// 使用自定义样式
const cell = worksheet.getCell('A1');
cell.style.addCustomProperty('dataValidation', {
  type: 'list',
  formula1: '"Yes,No"'
});

流处理扩展

对于大数据量处理,ExcelJS提供了流式处理接口,可以扩展自定义的流处理器:

// 自定义流处理器
class CustomStreamProcessor {
  constructor(options = {}) {
    this.options = options;
    this.buffer = [];
  }
  
  write(data) {
    // 自定义处理逻辑
    const processed = this.processData(data);
    this.buffer.push(processed);
    return true;
  }
  
  processData(data) {
    // 实现自定义数据处理
    return data.map(item => ({
      ...item,
      processedAt: new Date().toISOString()
    }));
  }
  
  end() {
    return Promise.resolve(this.buffer);
  }
}

// 集成到ExcelJS流处理
const processor = new CustomStreamProcessor();
workbook.csv.readStream(stream)
  .pipe(processor)
  .then(processedData => {
    // 处理完成
  });

转换器(Xform)系统

ExcelJS的核心转换器系统采用了模板方法模式,允许开发者创建自定义的Xform类:

mermaid

// 自定义Xform示例
const BaseXform = require('../xform/base-xform');

class CustomXform extends BaseXform {
  get tag() {
    return 'custom';
  }
  
  render(xmlStream, model) {
    xmlStream.openNode(this.tag);
    xmlStream.leafNode('value', model.customValue);
    xmlStream.closeNode();
  }
  
  parseOpen(node) {
    if (node.name === this.tag) {
      this.model = {};
      return true;
    }
    return false;
  }
  
  parseText(text) {
    // 解析文本内容
  }
  
  parseClose(name) {
    if (name === this.tag) {
      return false;
    }
    return true;
  }
}

集成第三方库

ExcelJS可以轻松集成第三方库来扩展功能,如图表生成、数据验证等:

// 集成图表库示例
const ChartJS = require('chart.js');

class ExcelChartBuilder {
  static addChartToWorksheet(worksheet, chartConfig, anchorCell) {
    const canvas = document.createElement('canvas');
    const chart = new ChartJS(canvas, chartConfig);
    
    // 将图表转换为图像并添加到工作表
    const imageData = canvas.toDataURL('image/png');
    worksheet.addImage({
      base64: imageData,
      extension: 'png',
      anchor: {
        from: { col: anchorCell.col, row: anchorCell.row },
        to: { 
          col: anchorCell.col + 6, 
          row: anchorCell.row + 4 
        }
      }
    });
  }
}

性能优化扩展

对于需要处理大量数据的场景,可以通过扩展来优化性能:

// 批量操作扩展
Worksheet.prototype.batchUpdate = function(operations) {
  this.suspendEvents();
  try {
    operations.forEach(op => {
      switch (op.type) {
        case 'setValue':
          this.getCell(op.cell).value = op.value;
          break;
        case 'setStyle':
          this.getCell(op.cell).style = op.style;
          break;
        // 更多操作类型...
      }
    });
  } finally {
    this.resumeEvents();
  }
  return this;
};

// 使用批量操作
worksheet.batchUpdate([
  { type: 'setValue', cell: 'A1', value: 'Name' },
  { type: 'setValue', cell: 'B1', value: 'Age' },
  { type: 'setStyle', cell: 'A1:B1', style: { font: { bold: true } } }
]);

通过上述扩展机制,开发者可以根据具体需求灵活地定制和增强ExcelJS的功能,无论是添加新的数据类型、集成第三方服务,还是优化处理性能,都能找到合适的扩展方式。

总结

ExcelJS通过精心的架构设计实现了强大的Excel处理能力。其核心在于Workbook和Worksheet类的组合模式设计,XForm转换器体系的高效XML处理,以及流式读写和内存优化机制。该库采用模块化架构,支持多种设计模式,提供了灵活的扩展接口,能够处理从KB到GB级别的Excel文件。开发者可以通过类继承、自定义XForm、事件钩子等机制扩展功能,满足各种复杂场景需求。ExcelJS的架构不仅解决了大数据量处理的内存挑战,还为开发者提供了高性能、可扩展的Excel操作解决方案。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值