SQLite3中级篇(C/C++编程接口)源代码解析

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:SQLite3是一种嵌入式数据库引擎,特别适用于C和C++开发的项目。本源代码示例深入探讨了SQLite3的C/C++编程接口,包括数据库连接管理、SQL语句执行、预编译语句、参数绑定、错误处理、事务处理、游标和结果集、数据库版本管理以及安全性和并发性。通过具体实现和实例,帮助开发者有效使用SQLite3 API进行高效的数据库操作。 SQlite3中级篇(C/C++编程接口) 源代码

1. SQLite3 API概述

SQLite是一个广泛使用的嵌入式SQL数据库引擎,它的API为开发者提供了与数据库交互的强大工具集。本章将为您介绍SQLite3 API的基础知识,包括其主要功能和使用场景,以及它们在实际应用中如何工作的基础。

SQLite3 API简介

SQLite3 API是一组用于操作SQLite数据库的函数集合。它支持多种编程语言,使得开发者能够方便地实现数据库的创建、查询、更新和管理等功能。无论是在桌面应用还是在移动应用中,SQLite3 API都扮演着核心角色,提供了一个无需依赖外部数据库服务器的轻量级数据库解决方案。

SQLite3 API的优势

使用SQLite3 API的主要优势在于其轻量级设计,它可以轻松地集成到各种应用程序中,不需要复杂的配置。此外,它提供了对SQL语言的完整支持,保证了跨平台的兼容性。对于开发者而言,API的易用性和灵活性是其受欢迎的主要原因。

SQLite3 API的使用不仅限于编程新手,它也能够满足专业人士对数据库管理的需求。下一章将介绍如何创建和关闭SQLite3数据库连接,这是与数据库进行交互的第一步。

2. 数据库连接创建与关闭

数据库的连接创建与关闭是SQLite3应用中的基础操作,涉及到程序与数据库交互的开始与结束。本章将介绍SQLite3的连接创建过程和如何正确关闭数据库连接,保证数据的完整性和程序的健壮性。

2.1 SQLite3数据库的连接过程

数据库连接创建是整个数据库交互过程的第一步,涉及到环境的初始化和数据库文件的打开。

2.1.1 初始化SQLite3环境

在打开数据库之前,必须初始化SQLite3环境。这通常涉及到调用 sqlite3_initialize() 函数,该函数的作用是确保SQLite3库在使用前已经被正确加载和初始化。

#include <sqlite3.h>
sqlite3 *db;
int rc = sqlite3_initialize();
if (rc != SQLITE_OK) {
    // 初始化失败处理
}

上述代码段展示了如何初始化SQLite3环境。如果环境初始化失败(返回值非 SQLITE_OK ),则需要采取适当的错误处理措施。

2.1.2 打开数据库连接

环境初始化之后,就可以创建并打开数据库连接了。通常使用 sqlite3_open() 函数来打开一个数据库文件,或者 sqlite3_open_v2() 函数来打开一个数据库并指定打开模式。

// 打开数据库连接
rc = sqlite3_open("example.db", &db);
if (rc != SQLITE_OK) {
    // 连接失败处理
    sqlite3_close(db);
} else {
    // 连接成功后的操作
}

如果数据库文件不存在,SQLite3会尝试创建一个新的数据库文件。在上述代码中,如果打开数据库失败,必须确保调用 sqlite3_close() 来关闭数据库连接句柄,以避免内存泄漏。

2.2 SQLite3数据库的关闭操作

关闭数据库连接是一个不可或缺的步骤,它不仅释放资源,还可以确保数据的一致性。

2.2.1 关闭单个数据库连接

关闭数据库连接的操作相对简单,只需调用 sqlite3_close() 函数即可。

// 关闭数据库连接
sqlite3_close(db);

2.2.2 关闭所有数据库连接

在某些情况下,我们可能需要关闭程序中所有的数据库连接,这时可以使用 sqlite3_close_all() 函数。

// 关闭所有打开的数据库连接
sqlite3_close_all();

这个操作会关闭程序中所有打开的数据库连接,释放所有相关资源。需要注意的是,此函数只在较新的SQLite3版本中提供。

在使用数据库连接时,应注意异常情况下的资源释放,避免内存泄漏。建议在程序的退出处理函数中调用 sqlite3_close(db) sqlite3_close_all() ,以确保连接被正确关闭。

在本章中,我们学习了如何初始化SQLite3环境、打开和关闭数据库连接。这些是使用SQLite3进行数据库操作时的基础知识。接下来的章节中,我们将深入了解如何执行SQL语句以及通过回调函数处理查询结果。

3. SQL语句的执行与回调函数处理

3.1 SQL语句执行的基本方式

在使用SQLite3进行数据库操作时,执行SQL语句是核心任务之一。开发者可以使用不同的API函数来执行SQL语句,并处理执行结果。本章节将探讨如何使用SQLite3提供的API执行SQL语句,以及如何处理这些语句的执行结果。

3.1.1 执行SQL语句的API函数

SQLite3提供了多个API函数用于执行SQL语句,最常用的函数是 sqlite3_exec sqlite3_prepare 系列函数。 sqlite3_exec 函数适用于简单快捷的SQL语句执行,而 sqlite3_prepare 系列函数则更适合执行复杂查询和参数化查询。

下面是一个使用 sqlite3_exec 执行SQL语句的示例:

#include <stdio.h>
#include <sqlite3.h>

int main() {
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;

    rc = sqlite3_open("example.db", &db);
    if (rc) {
        fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
        return 1;
    }

    // 执行SQL语句
    const char* sql = "CREATE TABLE IF NOT EXISTS test(id INTEGER, name TEXT);";
    rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg);

    if (rc != SQLITE_OK) {
        fprintf(stderr, "SQL错误: %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
    } else {
        fprintf(stdout, "表创建成功\n");
    }

    sqlite3_close(db);
    return 0;
}

3.1.2 SQL语句执行结果的处理

sqlite3_exec 函数除了返回执行状态码之外,还可以通过回调函数来处理查询结果。下面的代码展示了如何定义和使用回调函数:

static int callback(void *data, int argc, char **argv, char **azColName) {
    int i;
    fprintf(stderr, "查询结果:\n");
    for(i = 0; i < argc; i++) {
        fprintf(stderr, "%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    fprintf(stderr, "\n");
    return 0;
}

// ...

// 使用回调函数执行查询
const char* sql = "SELECT * FROM test;";
rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);

3.2 回调函数在SQL语句执行中的应用

回调函数在SQLite3中扮演着至关重要的角色,尤其是在处理查询结果时。它们允许开发者以事件驱动的方式处理SQL查询的每一行数据。

3.2.1 回调函数的定义和使用

回调函数的定义需要符合特定的签名,以便于SQLite3库能够正确地调用它。回调函数的第一个参数通常是用户提供的数据指针,它在回调函数中可以用于传递额外的状态或信息。

下面是一个回调函数的定义示例:

// 回调函数的定义
static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
    for (int i = 0; i < argc; i++) {
        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    printf("\n");
    return 0;
}

3.2.2 回调函数在数据处理中的作用

在执行如 SELECT 这样的查询时,回调函数将被多次调用,每次对应结果集的一行数据。开发者可以在回调函数内部执行对数据的处理逻辑。

以下展示了如何在回调函数中处理查询结果:

// SQL查询语句
const char* sql = "SELECT * FROM test;";
// 执行SQL查询并传入回调函数
int rc = sqlite3_exec(db, sql, callback, 0, &zErrMsg);

通过回调函数,我们可以实现对数据的逐行处理,例如打印、验证、转换或存储等操作。

在本章节中,我们探讨了SQLite3中执行SQL语句的基本方式,包括API函数的使用和执行结果的处理。同时,我们深入讨论了回调函数在处理SQL查询结果中的重要作用。下一章节将介绍预编译语句的使用与效率提升,为读者进一步深入SQLite3的高级特性做准备。

4. 预编译语句的使用与效率提升

预编译语句是提高数据库操作性能和安全性的关键技术之一。通过理解预编译语句的概念、优势以及如何创建和使用它们,开发者可以显著提升代码的执行效率并减少安全漏洞。

4.1 预编译语句的概念和优势

4.1.1 预编译语句的定义

预编译语句,也称为预处理语句,是一种数据库查询方法,其中SQL语句被编译一次,但是参数可以在每次执行时被替换。这种方式的一个关键优势是它有助于防止SQL注入攻击,因为它分离了SQL语句的结构和传递给它的参数值。

4.1.2 预编译语句提升效率的原理

预编译语句的效率提升主要来源于几个方面: - 编译与执行分离 :预编译语句将SQL语句的编译和执行分开,数据库可以重用已经编译的查询计划。 - 减少数据库解析次数 :每次使用预编译语句时,数据库不需要重新解析SQL语句。 - 性能优化 :编译后的查询计划可以存储在缓存中,当相同的查询被重复执行时,数据库可以跳过解析步骤,直接使用缓存的计划。

4.2 预编译语句的创建与使用

4.2.1 创建预编译语句的API

在SQLite3中,创建预编译语句通常是通过 sqlite3_prepare_v2() 函数完成的,该函数的原型如下:

sqlite3_stmt *sqlite3_prepare_v2(
  sqlite3 *db,              /* Database handle */
  const char *zSql,         /* SQL statement, UTF-8 encoded */
  int nByte,                /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,   /* OUT: Statement handle */
  const char **pzTail       /* OUT: Pointer to unused portion of zSql */
);

4.2.2 使用预编译语句执行SQL

使用预编译语句执行SQL涉及绑定参数到语句,然后执行语句。以下是一个使用预编译语句的示例代码:

#include <sqlite3.h>
#include <stdio.h>

int main() {
  sqlite3 *db;
  sqlite3_stmt *stmt;
  const char *sql = "SELECT * FROM users WHERE id = ?;";

  // 打开数据库连接
  sqlite3_open("example.db", &db);

  // 准备SQL语句
  sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);

  // 绑定参数,假设我们要查询id为1的用户
  sqlite3_bind_int(stmt, 1, 1);

  // 执行语句
  while (sqlite3_step(stmt) == SQLITE_ROW) {
    // 在这里处理每一行结果
    const char *name = (const char*)sqlite3_column_text(stmt, 1);
    printf("Name: %s\n", name);
  }

  // 清理
  sqlite3_finalize(stmt);
  sqlite3_close(db);
}

通过预编译语句,我们不仅提高了代码的执行效率,还增强了安全性。由于参数通过 sqlite3_bind_ 系列函数安全地绑定,因此可避免直接将参数拼接到SQL语句中,从而降低了SQL注入的风险。

5. 参数安全绑定防止SQL注入

SQL注入是一种常见的网络安全攻击手段,通过在应用程序的输入中插入恶意的SQL代码,攻击者可以对数据库执行未授权的操作,甚至完全控制数据库。在本章节中,我们将深入探讨SQL注入的原理与危害,并着重介绍参数安全绑定技术,旨在提升应用程序的安全性,防止SQL注入攻击。

5.1 SQL注入的原理与危害

SQL注入攻击之所以危险,在于它能够通过正常的数据库请求,悄悄地插入恶意的SQL代码。当应用程序执行未经验证或清理的用户输入时,攻击者就能够利用这些输入破坏SQL语句的结构,从而执行攻击代码。

5.1.1 SQL注入攻击的实现方式

SQL注入通常可以分为几种类型,比如布尔型盲注、时间型盲注、联合查询注入、堆叠查询注入等。每种注入方式都有其特定的实现方法,但核心思想都是利用应用程序将用户输入作为SQL语句的一部分来执行。

例如,假设有一个简单的用户认证逻辑,其中包含了SQL查询:

SELECT * FROM users WHERE username = '$username' AND password = '$password';

如果攻击者输入用户名为 admin' -- ,且密码留空,那么SQL语句将变为:

SELECT * FROM users WHERE username = 'admin' --' AND password = '';

这里 -- 在SQL中代表注释的开始,因此密码验证部分将被注释掉,如果数据库中存在用户名为admin的用户,攻击者将绕过密码验证。

5.1.2 SQL注入的影响及防范意识

SQL注入的影响是多方面的。除了能够绕过身份验证、窃取或修改数据外,攻击者甚至能通过注入提权,控制数据库服务器。因此,防范SQL注入是每个使用数据库的应用程序所必须重视的。

为了防止SQL注入,开发者需要具备以下防范意识:

  • 对所有用户输入进行验证,拒绝不符合预期格式的输入。
  • 使用参数化查询或预编译语句,这样用户输入不会直接拼接到SQL语句中。
  • 限制数据库权限,确保应用数据库账户只有必需的权限,避免使用管理员账户。
  • 定期对应用程序进行安全审计和代码审查。

5.2 参数安全绑定技术

参数安全绑定技术是指在执行SQL语句时,使用预定义的参数位置来接收用户输入,而不是将输入直接拼接到SQL语句中。这一技术可以有效地隔离输入内容与SQL语句的结构,从而避免了因输入不当而引发的注入风险。

5.2.1 安全绑定参数的方法

在SQLite3中,安全绑定参数的方法主要有两种:使用 sqlite3_prepare_v2() 函数准备SQL语句和使用 sqlite3_bind_* 系列函数绑定参数。

例如,创建一个预编译语句并安全地绑定参数:

sqlite3_stmt *stmt;
const char *sql = "SELECT * FROM users WHERE username = ? AND password = ?;";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);

// 绑定参数
sqlite3_bind_text(stmt, 1, "admin", -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, "strongpassword", -1, SQLITE_STATIC);

// 执行查询
sqlite3_step(stmt);

在这个例子中, ? 代表参数占位符, sqlite3_bind_text 函数用于绑定文本类型的参数。

5.2.2 安全绑定参数的实际应用

在实际应用中,安全绑定参数的应用应贯穿整个应用程序的数据库操作。每个与数据库交互的函数都应遵循以下步骤:

  1. 准备SQL语句,并确保使用参数占位符。
  2. 对用户输入进行严格的验证和清理。
  3. 使用 sqlite3_bind_* 系列函数绑定经过验证的参数。

下面的示例代码展示了如何在实际应用中使用参数安全绑定:

void getUser(const char *username, const char *password) {
    sqlite3_stmt *stmt;
    const char *sql = "SELECT * FROM users WHERE username = ? AND password = ?;";

    // 连接到数据库
    if (sqlite3_open("example.db", &db) != SQLITE_OK) {
        // 错误处理...
    }

    // 准备预编译语句
    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        // 错误处理...
    }

    // 绑定参数
    sqlite3_bind_text(stmt, 1, username, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, password, -1, SQLITE_TRANSIENT);

    // 执行查询
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        // 处理查询结果
    }

    // 清理资源
    sqlite3_finalize(stmt);
    sqlite3_close(db);
}

在这个函数中,我们对用户名和密码进行了安全绑定。即使攻击者尝试通过 username password 输入注入恶意代码,它们也会被当作普通文本处理,不会影响SQL语句的结构。

通过参数安全绑定技术的使用,我们可以极大地降低SQL注入的风险,提高应用程序的安全性。开发者应养成良好的编程习惯,始终将安全意识放在首位,从而构建更加稳固的数据库应用。

6. 错误信息和代码获取方法

6.1 错误处理的基本概念

6.1.1 错误代码的种类与意义

在SQLite3数据库编程中,错误代码是追踪和诊断问题的关键。它们可以帮助开发者理解在执行某个操作时发生了什么类型的错误。SQLite3定义了一套丰富的错误代码,这些代码可以帮助开发者了解错误发生的具体原因,并采取相应的应对措施。例如,错误代码 SQLITE_BUSY 表示尝试访问的数据库正在被使用,而 SQLITE_NOTADB 则表示尝试打开的文件不是一个SQLite数据库文件。

错误代码的获取通常是通过 sqlite3_errcode() sqlite3_errmsg() 这类函数实现的。 sqlite3_errcode() 返回与数据库连接相关的最近错误代码,而 sqlite3_errmsg() 则返回对应的错误信息字符串。这些信息对于开发人员而言是至关重要的,因为它们提供了足够的细节来帮助定位和解决问题。

6.1.2 错误信息的获取与展示

获取SQLite3错误信息的步骤通常包括几个部分。首先,需要确定错误发生的具体上下文,这可能涉及到检查特定的数据库连接或是全局的SQLite3环境。其次,要调用相应的API函数来检索错误代码和错误消息。最后,将这些信息展示给用户或记录到日志文件中,以便于后续的分析和调试。

在代码层面上,错误信息的获取通常非常直接:

sqlite3 *db;
char *errMsg;
int errCode;

// 执行某项操作导致错误发生
// 例如: sqlite3_exec(db, "INVALID_SQL_STATEMENT", NULL, NULL, NULL);

// 获取错误代码
errCode = sqlite3_errcode(db);

// 获取错误信息
errMsg = sqlite3_errmsg(db);

// 打印错误信息
printf("SQLite3 Error Code: %d\nSQLite3 Error Message: %s\n", errCode, errMsg);

// 清除错误信息,为下一次错误准备
sqlite3_free(errMsg);

在上述代码中,我们首先检查是否有错误发生,然后检索错误代码和消息。错误信息通过 printf 函数展示给用户,同时释放了由 sqlite3_errmsg() 返回的内存,以避免内存泄漏。

6.2 错误处理的高级技巧

6.2.1 错误处理策略的制定

在进行SQLite3数据库开发时,制定一套有效的错误处理策略是至关重要的。好的错误处理策略可以帮助开发者减少调试时间,并提高应用程序的稳定性和用户体验。一个有效的策略应该包括以下几个方面:

  1. 记录详细的错误日志 :记录错误发生时的操作上下文,包括使用的SQL语句、执行时间、错误代码和错误消息。这有助于问题的定位和重现。
  2. 使用错误回调函数 :可以为SQLite3设置一个错误回调函数,以便在错误发生时立即得到通知。这有助于在错误发生时立即采取行动。

  3. 设置超时和重试机制 :对于一些临时的错误,如数据库锁或网络问题,可以通过设置超时和重试机制来处理,这样可以减少因临时性问题导致的服务中断。

6.2.2 异常处理流程的优化

异常处理流程的优化涉及到如何高效地处理异常情况,防止它们影响整个应用程序的运行。以下是优化异常处理流程的一些方法:

  1. 避免隐藏错误 :在处理异常时,确保所有的错误都能被适当地记录和展示。隐藏错误可能会导致问题的积累,使得问题变得更难解决。

  2. 分类处理不同错误 :不同的错误可能需要不同的处理流程。例如,验证性错误可能只需要提醒用户重新输入,而严重的技术问题则可能需要通知系统管理员。

  3. 优化重试逻辑 :如果错误是可以恢复的,可以设计自动重试逻辑来处理这些错误。然而,这需要考虑到重试的成本以及避免无限循环的风险。

  4. 优雅地处理致命错误 :对于一些致命性错误,如数据库文件损坏或磁盘空间不足,应该优雅地处理它们。这意味着应用程序应该能够安全地关闭或转移到一个安全的状态,并通知用户问题所在。

通过这些策略,开发者可以确保在面对错误时,应用程序能够稳定运行,同时为用户提供清晰的反馈。下面是一个错误回调函数的示例代码:

// 设置错误回调函数
void my_sqlite_error_callback(void *NotUsed, int argc, char **argv, char **azColName) {
    // 这里可以根据实际需要记录日志等
    fprintf(stderr, "SQLite Error: %s\n", argv[0]);
}

// 在初始化数据库时调用
sqlite3_config(SQLITE_CONFIG_VALIDATIONCALLBACK, my_sqlite_error_callback);

在这个例子中, my_sqlite_error_callback 函数会在SQLite3遇到错误时被调用,开发者可以根据实际需要在此函数中添加记录日志、通知用户等逻辑。

通过合理的错误处理和优化,开发者可以显著提高应用程序的健壮性和可靠性,减少因错误导致的损失和维护成本。

7. 事务控制命令与处理

事务是数据库管理系统中的一个核心概念,它确保了一系列操作要么全部完成,要么全部不执行,保持数据库的完整性和一致性。理解事务的控制命令对于数据库管理员和开发者来说至关重要。

7.1 事务的基本概念与作用

7.1.1 事务的定义及其重要性

事务是由一个或多个数据库操作组成的逻辑单元,这些操作作为一个整体被执行:要么全部成功,要么全部回滚。在SQLite3中,事务确保了即使发生错误或系统崩溃,数据库的状态也不会处于部分完成的中间状态。

BEGIN TRANSACTION;
-- 执行一系列数据库操作
COMMIT; -- 或者 ROLLBACK;

7.1.2 事务控制命令的种类

SQLite3提供了一系列用于事务控制的命令,主要包括:

  • BEGIN TRANSACTION :开始一个新的事务。
  • COMMIT :提交当前事务,保存所有更改。
  • ROLLBACK :回滚当前事务,撤销所有更改。
  • SAVEPOINT :设置一个保存点。
  • RELEASE SAVEPOINT :删除一个保存点。
  • ROLLBACK TO SAVEPOINT :回滚事务到指定的保存点。

7.2 事务的操作与错误恢复

7.2.1 开启、提交与回滚事务

开启事务是通过 BEGIN TRANSACTION 命令实现的。提交或回滚事务意味着对数据库状态的更改变为永久或被撤销。

BEGIN TRANSACTION;
-- 执行数据库操作
INSERT INTO table_name (column1, column2) VALUES (value1, value2);
-- 执行更多操作
-- 如果所有操作成功,则提交事务
COMMIT;

如果在事务过程中遇到错误,应使用 ROLLBACK 命令来撤销所有在当前事务中所做的更改。

BEGIN TRANSACTION;
-- 假设有一个错误发生
INSERT INTO table_name (column1, column2) VALUES ('error', 'data');
-- 撤销操作
ROLLBACK;

7.2.2 事务中的错误处理与恢复

事务的一个重要方面是它允许开发者处理错误并从错误中恢复。在事务过程中遇到错误时,可以使用 ROLLBACK 来撤销事务中的所有更改,或者使用 SAVEPOINT 来实现更精细的错误恢复。

BEGIN TRANSACTION;
SAVEPOINT my_savepoint;
-- 执行一系列操作
-- 如果遇到错误
ROLLBACK TO SAVEPOINT my_savepoint;
-- 可以选择再次尝试操作或者继续回滚事务
COMMIT;

在实际应用中,应根据业务需求决定使用 ROLLBACK 还是 SAVEPOINT SAVEPOINT 提供了更灵活的错误恢复策略,允许恢复到事务中的特定点,而不仅仅是回滚整个事务。

总结来说,事务控制命令是确保数据一致性、可靠性和完整性的重要工具。了解和正确使用这些命令对于构建健壮的数据库应用至关重要。在接下来的章节中,我们将深入探讨如何在实际应用中优化SQLite3的使用,包括性能优化和故障应对策略。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:SQLite3是一种嵌入式数据库引擎,特别适用于C和C++开发的项目。本源代码示例深入探讨了SQLite3的C/C++编程接口,包括数据库连接管理、SQL语句执行、预编译语句、参数绑定、错误处理、事务处理、游标和结果集、数据库版本管理以及安全性和并发性。通过具体实现和实例,帮助开发者有效使用SQLite3 API进行高效的数据库操作。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值