python连接access2007_结合使用 Python 和 TurboGears

开发人员:脚本语言

结合使用 Python 和 TurboGears

作者:Daniel Rubio

TurboGears 为 Python-istas 提供了一个非常好的框架,用于创建 Oracle 数据库支持的且支持 Web 的应用程序。

2007 年 11 月发表

多年来,作为一种脚本语言,Python 因其语法简洁明了并且问题最少已经取得了显著成功。大量“Python-istas”已经为该语言提供了多种用途(从图形界面一直到机器人技术),但是直到现在,数据库支持的 Web 应用程序仍然是一个要由 Python 社区来填补的空白。通过 TurboGears,您现在可以使用 Python 将同类最佳的对象关系 (O/R) 映射器、模板工具包以及其他所需的辅助部件集中到一起,以通过 Oracle 数据库技术将数据库支持的应用程序发布到 Web。

Python 和 TurboGears:背景和体系结构

多年来开发的大量第三方库和模块是 Python 受欢迎程度的有力证明。对于使用 Python 的 Web 绑定应用程序,并不缺少填补空白的内容;同样,还开发了大量用于访问关系数据库的 Python 片段,但这并没有缓解在开始时提到的 Python 在数据库支持的应用程序(作为整体支持 Web)领域的空白。

回顾 Java 平台企业版 (Java EE) 等平台的情况(这些平台不断推出用于处理数据库支持的 Web 应用程序中当前问题的规范和框架),TurboGears 是 Python 社区将 Python 生态系统的各个部分聚集到一起以应对当今数据库支持的 Web 应用程序的典型要求的解决方案。

在这种意义上,TurboGears 只是让 Python 使用者从众多常用的 Python 库中进行选择。表 1 列出了其中一些常用的 Python 库,它们可以解决数据库支持的 Web 应用程序中的大量问题。 Python 库 目标 一个对象关系映射器

一个面向对象的 HTTP 框架

一个基于 XML 的模板库

一个包含 Pythonesque 语法的 Ajax/JavaScript 库

一个模板引擎和代码生成工具

表 1 TurboGears 对 Python 库的支持

其中一些库对于 Pythonistas 可能很常见(用于创建基于静态的 Web 站点),其他库可能用于直接从某些非 Web 环境中的关系数据库访问数据,但是正是将这些 Python 部分组合在一起所需的集成代码使 TurboGears 成为 Python 社区的一个令人心动的附加项目。

简单了解 TurboGears 相对于其他 Python 项目的地位之后,我们将开始为 TurboGears 准备 Python 和 Oracle 环境。

设置环境:Python、Oracle 数据库、cx_Oracle 和 TurboGears

Python

Python 解释器可用于众多平台和处理器体系结构。如果您在 *nix box 上,默认情况下已经在其上安装了某个版本的可能性很大。对于 TurboGears,建议您在计算机上安装 Python 2.5.x 版本,虽然 TurboGears 也支持 Python 的早期版本。为了获得最佳效果并让您的 TurboGears 应用程序发挥最大的功效,建议升级至 2.5.x 版本。

根据您的 OS 和处理器体系结构,可以在此处下载一系列 2.5.x Python 版本。

Oracle 数据库

您可以从众多 Oracle 数据库版本中进行选择,用于您的 TurboGears 应用程序。无论是以前的 Oracle8i 版本还是最新的 Oracle 数据库 11g 版本(轻型 Oracle 数据库快捷版或 Oracle 数据库企业版),您可以依赖组织拥有的任何 Oracle 数据库版本,也可以通过 Python 中间件获取您的信息。除了少数几种 Python/Oracle 安装方法外,我们不会在此探究 Oracle 数据库的安装细节,因为该主题已经有了很好的资源。但是,如果您不熟悉 Oracle 数据库技术,下面几个链接可以引导您入门: Oracle 数据库 10g 快捷版:一个入门级数据库,占用空间很少,我在本文中使用该数据库来演示 TurboGears 的功能

Oracle 数据库版本和特性:用于更复杂的要求,对大量 Oracle 数据库版本进行比较

cx_Oracle

cx_Oracle 充当核心和最低级的 API,用于将 Python 环境桥接到 Oracle 数据库。cx_Oracle 之于 Python 就好比 Oracle JDBC 驱动程序之于 Java,使应用程序能够通过适当的细节级别执行原始的 SQL 查询和操作数据库游标。

但是,与 Java 应用程序(它们现在很少直接使用原始的 SQL 命令,而是依赖仅使用 JDBC 驱动程序作为构建块的更高级的 O/R 映射器)类似,cx_Oracle 安装需要使用 TurboGears 的更高级的 O/R 映射模块。

您可以下载 cx_Oracle 预置二进制文件或者 cx_Oracle 源代码,后者用于从头构建 cx_Oracle 以适应您的 Oracle/Python 版本组合(如果需要)。

cx_Oracle 实用程序

尽管 TurboGears 操作不需要,但您可能还会对两个与 cx_Oracle 相关的开源 Python/Oracle 项目感兴趣: cx_OracleTool:一系列用 Python 编写的跨平台工具,用于执行常见的 Oracle 数据库开发人员任务

cx_OracleDBATools:一系列用 Python 编写的跨平台工具,用于执行数据库管理员任务,如启动、停止和创建 Oracle 数据库

TurboGears

一旦您完成前面的所有安装和运行工作,就可以设置 TurboGears 了,该过程极为简单,只需执行一个 Python 脚本。安装脚本 tgsetup.py 可以从此处下载,应该使用 $ python tgsetup.py 进行调用,它执行以下任务: 下载并安装默认情况下在 TurboGears 中使用的大量 Python 模块,如 Cheetah、CherryPy、Kid、SQLObject 和 Easy Install

安装快捷方式脚本 tg-admin 以简化 TurboGears 项目的创建和配置,并将各自的快捷方式脚本安装到已安装的 Python 模块中:cheetah、kid、sqlobject-admin 和 easy_install

如果您希望进一步验证您的 TurboGears 安装,在 [PYTHON_HOME]/lib/python2.5/site-packages/ 下查看默认情况下在 TurboGears 中使用的系列 Python 模块。至于快捷方式脚本,默认情况下,这些脚本将置于系统的路径目录(在 *nix 上通常为 /usr/local/bin/)中,以便可以从系统的任何目录中调用它们。

SQLAlchemy

作为最后一个步骤,您需要安装 SQLAlchemy,它是为我们的 TurboGears 项目选择的 Python O/R 映射器,下一部分将对该选择进行详细介绍。

您可以使用随 TurboGears 一同安装的 easy_install 脚本。只需执行 easy_install SQLAlchemy,即可开始按序进行 SQLAlchemy 的下载/安装。

创建第一个 TurboGears 项目

我们的示例 TurboGears 项目旨在将员工信息提供给 Acme 人力资源部门,从而提供支持 Ajax 的界面以增强该应用程序背后的导航和数据加载功能。此外,为了简化开发,我们还将使用 Oracle 数据库快捷版中的 HR 数据模式,它将充当用于许多开发的一个常用启动数据模型。

之后,我们将使用 tg-admin 实用程序开始创建我们的项目。清单 1 显示了该过程。 清单 1 创建 TurboGears 项目 $ tg-admin quickstart --sqlalchemyEnter project name: ACME HREnter package name [acmehr]:Do you need Identity (usernames/passwords) in this project? [no]<100~150 lines processing results>

tg-admin quickstart --sqlalchemy 命令将启动一个项目向导,该向导随后将要求我们提供一个项目名称。输入 ACME HR。以下两个问题将提供用括号括起来的默认答案。对于我们的项目需求,这些值是适合的,因此单击 Enter 键接受。一旦您完成该过程,系统将创建一个名为 ACME-HR 的目录,其中包含该项目的所有文件。

接下来,转到 ACME-HR 目录,在文本编辑器中打开 dev.cfg 文件,在以 sqlalchemy.uri 开头的每行前面加上注释,取消注释/修改 server.socket_port=8010 行。后者是必需的端口修改,因为默认的 8080 可能会与 Oracle 数据库快捷版 Web 服务器发生冲突。一旦您完成这个小修改,请执行同一顶层目录中的 start-acmehr.py 脚本,以启动测试 Web 服务器并开始为 http://localhost:8010/ 上的项目提供服务。如果您将浏览器定位到该地址,将看到与以下类似的屏幕。

图 1 TurboGears 项目欢迎屏幕

就这么简单。完成这几个步骤后,您的 TurboGears 项目现在已启动并运行。现在,让我们探究如何从 TurboGears 访问 Oracle 数据库。

TurboGears 模型

正如我前面提到的,TurboGears 利用对象关系映射器来访问关系数据库中的数据,并将其传送到 Python 中间件层以进行业务处理。默认情况下,TurboGears 使用名为 SQLObject 的 Python O/R 映射器,但是正如您在前面步骤中注意到的那样,您使用的是 sqlalchemy 标记,该标记使用配置文件创建项目,利用这些配置文件,可以改为使用 SQLAlchemy 作为默认的 O/R 映射器。有关该选择的详细信息,请参见“SQLAlchemy 与 SQLObject”边栏。

配置 TurboGears 使用 Oracle 数据库的第一步是,将数据库的访问参数指定给主要的开发配置文件 dev.cfg,并添加以下行: sqlalchemy.dburi="oracle://hr:hr@xe"

其中,hr:hr 指的是数据库用户名/口令,@xe 是包含 Oracle 数据库快捷版提供的示例 HR 模式的 Oracle 实例。

接下来,向下移到 acmehr 子目录,打开名为 models.py 的文件,该文件将包含访问 Oracle 数据库所需的所有声明,这些内容将是其余部分的重点。

由于我们已经有一个现成的 HR 模式可以使用,下面要做的第一件事就是建立与该模式中一些关系表的联系。清单 2 说明了包含锁定 EMPLOYEES 和 JOBS 表所需的代码的 models.py。 清单 2 现有表的 TurboGears models.py #Default imports for sqlalchemy and turbogearsfrom sqlalchemy import *from turbogears.database import metadata, sessionfrom sqlalchemy.ext.assignmapper import assign_mapper# Import binder method, needed to access pre-existing tablesfrom turbogears.database import bind_meta_databind_meta_data()#Declare tables to accessemployees = Table('employees', metadata, autoload=True)jobs = Table('jobs', metadata, autoload=True)#Declare class objects class Employee(object): passclass Job(object): pass#Performing table/object binding, placing in session assign_mapper(session.context, Employee, employees)assign_mapper(session.context, Job, jobs)

正如您看到的,从 TurboGears 访问现有数据库表同样分三步走:创建一个表对象,创建其相应的 Python 对象表示,然后将它们映射到一起。这样,我们就完成了将数据模型引入 Python 的过程。就这么简单。

现在,可以定义实际的业务方法了,这些业务方法将在 TurboGears 控制器中操作该数据。

TurboGears 控制器

TurboGears 控制器将充当我们刚刚创建的对象模型与负责生成最终 Web 页的模板之间的代理程序。在同一 acmehr 子目录中,打开 controllers.py 文件,并对其进行修改以获得清单 3。 清单 3 TurboGears controllers.py from turbogears import controllers, expose, flashimport modelclass Root(controllers.RootController): #Default home page @expose(template="acmehr.templates.welcome") def index(self): import time # log.debug("Happy TurboGears Controller Responding For Duty") flash("Your application is now running") return dict(now=time.ctime()) # Return HR list, with filtering capabilities by Job type @expose(template="acmehr.templates.employees") def hr(self,job_id="ALL",sort="LAST_NAME"): job_list = model.Job.select() if (job_id == "ALL"): employee_list = model.Employee.select(order_by=[sort]) return dict(employees=employee_list,jobs=job_list,selectedjob_id=job_id) else: employee_list = model.Employee.select('JOB_ID = \'' + job_id + '\'',order_by=[sort]) return dict(employees=employee_list,jobs=job_list,selectedjob_id=job_id) # Returns a simple string, for a given employee_id/manager in text @expose("xml-rpc") def supervisor(self,employee_id=None): if (employee_id): manager = model.Employee.get(employee_id) # Check if the ID check out if(manager): manager_string = " " + manager.first_name + " " + manager.last_name + " - " + manager.phone_number + " " return str(manager_string) else: return "No employee with that ID" else: return "Please pass an employee ID" 相对于其默认状态,该 controllers.py 文件增加了三个内容: 开头的“import model”语句,该语句授予您访问 model.py 文件的权限,该文件包含您要操纵的模型对象。

hr 业务方法,它采用了三个输入参数:表示请求的 selfobject 以及 job_id 和 sort 参数,后两个预定义了传入请求没有提供值时的默认值。一旦进入方法主体,您将发现 model.Job.select()(它负责从我们的模型中选择所有 Job 对象),后跟一个对 job_id 参数的值进行 Tcheck 的条件。

在该条件中,您还将发现一个类似的声明,形式为 model.Employee.select(order_by=[sort]),它负责从我们的模型中选择所有 Employee 对象。但是,请注意该声明增加了一个 order_by=[sort] 声明,指明对象应该按 sort 值排序,默认情况下,sort 值为 LAST_NAME,表示 Employee 对象中的实际字段,因此就有了我们的 Employee 数据库表。类似地,还是在该条件内,您可以发现 model.Employee.select('JOB_ID = \'' + job_id + '\'',order_by=[sort]),指明只应选择 JOB_ID 与指定的 job_id 参数匹配的 Employee 对象。

完成我们的 hr 方法的是 return 语句,该语句声明一个 Python 字典,其中包含我们已处理的 Employee 和 Job 对象列表以及所选的 job_id 值。这让我们思考一个问题,这些值返回到哪里。如果您查看方法的开头部分,您将发现声明 @expose(template="acmehr.templates.employees"),它指明该方法返回的值应该在名为 acmehr.templates.employees 的模板中公开,当然,该模板属于将向最终用户显示的 HTML 类型。但是在我们了解实际的模板之前,让我们先解决最后的控制器方法 supervisor。

supervisor 方法负责返回一个字符串,该字符串包含员工的姓名以及该员工的电话号码。在这种情况下,由于您预先知道某个员工已经是主管/经理,您只需通过员工 ID 使用 model.Employee.get(employee_id) 创建一个查询,这将返回相应的 Employee 对象。

注意,这个最后的方法返回一个字符串,因为该方法将以独占方式被异步调用 (a.k.a. Ajax),以纯文本格式返回其结果以供浏览器使用。同样,请注意该方法不向任何模板公开,而是使用 @expose("xml-rpc") 指明其 XML-RPC 或 RESTful 性质,这在 Ajax 设计中很普遍。

最后,我们的控制器方法使用的命名和参数惯例还与它们的访问 URL 有关。这两个方法均被放入 Root 类中,指明这些方法将在我们的 Web 服务器的 root 目录中可以访问,方法的名称进一步充当虚拟目录。尝试访问 http://localhost:8010/hr/ 将调用 hr 方法,请求 http://localhost:8010/supervisor/ 将返回 supervisor 方法获得的结果。

此外,TurboGears 还使用 URL 子目录惯例来检测传递给该方法的参数。因此,如果您希望将 job_id 和 sort 值传递给 hr 方法,您可以调用以下 URL:http://localhost:8010/hr/PU_CLERK/FIRST_NAME,它将调用 job_id 值为 PU_CLERK 并且 sort 值为 FIRST_NAME 的 hr。

最后一个过程通过按方法的输入参数的顺序关联每个子目录而生效,提供对该类的 URL 参数字符串的简单访问,方法是使用 ? 和 & 字符,形式为 http://localhost:8010/hr?job_id=PU_CLERK&sort=FIRST_NAME,当然,这在 TurboGears 中也生效。该惯例快捷方式只是您应该了解的事情,因为在下面的部分中,我们将使用它来创建模板。

了解 TurboGears 控制器之后,您可以将它们放到一起以探究 TurboGears 视图。

TurboGears 视图

对于视图,TurboGears 依赖 Python Kid 模板引擎以及 Mochikit 等其他项目,因为它们提供现成的简单 Ajax 特性。由于我们只能探究这么多特性,如您刚刚接触的 TurboGears 模块(数据库层中的 SQLAlchemy 和控制器层中的 CherryPy),在这个有限的空间中,下面是使用了前述的 Python 模块的一个很基本的模板。图 2 说明了显示的模板,清单 4 本身包含模板代码。

图 2 TurboGears 为 Acme HR 显示的模板

清单 4 TurboGears employee.kid 模板

Welcome to TurboGears
Displaying
All Positions
First Name Last NamePhoneDirect Supervisor (Manager)
${employee.first_name} ${employee.last_name}${employee.phone_number}Click to see

在了解模板本身之前,您需要对 acmehr/config 子目录中的 app.cfg 文件进行一项简单的配置。在文本编辑器中打开该文件,添加以下行:tg.include_widgets = ['turbogears.mochikit']。该操作会将 Mochikit Ajax 库添加到所有项目模板中,因此您不必在每个模板中对该库的位置进行硬编码。

接下来,您需要在名为 employees.kid 的文件(该文件位于 acmehr/templates 子目录)中创建模板(重新调用我们的控制器 @expose value of template="acmehr.templates.employees"),并将清单 4 的内容放到其中,如下所示。

在我们的项目模板中,您应该了解的第一件事就是它继承了另一个模板的某些布局行为。查看根 标签和属性 py:extends="'master.kid'",该属性指明在呈现模板之前,TurboGears 将继承 master.kid 模板的布局行为,在该示例中,包括页眉内容、页脚内容、主体布局和其他行为特征。

现在,让我们转至模板中的主表,在该区域的右侧,您将发现

编码。现在,您将重新调用我们的 hr 控制器方法,该方法的 return 方法包含供模板使用的员工列表,因此这最后一行将要执行的任务是针对员工列表创建一个 for 循环,使得每个迭代在名为“employee”的变量中可用。

在这个最后的循环中,您将发现以下行,如

${employee.first_name} 和 ${employee.phone_number}

${} 语法用于呈现变量,加点注释指明对象的字段值,该字段值与在 HR 数据库模式中使用的字段名一致。该过程的最终结果是模板打印出 HTML 表中的所有 employee 对象。

除了主表外,您还将发现模板中的两个部分允许最终用户对 employee 的值进行排序或筛选。查看主表中的标题单元格,您将注意到名和姓的值都打包到 HTML 链接中,形式为:

这些包装程序链接要做的是使用提供的参数重新调用 hr 控制器方法,在该示例中,当模板呈现后,将出现一个按名或姓排序的员工列表。

employee 表的筛选机制与排序机制类似。在主表上方,您将发现一个 HTML 选择列表,其中包含 Acme 提供的所有职务。注意我们如何利用主表中使用的同一术语 py:for="job in jobs",除了我们使用它来遍历在控制器中添加的职务部分。

这个包含职务的 HTML 选择列表也具有 οnchange="filterHR (this)" 属性,这使得它在最终用户更改列表选项时触发一个名为 filterHR 的 JavaScript 函数。如果您向上滚动到开头部分,将注意到 filterHR 会提取所选的职务,构建一个 URL,然后使用 location.href 重新调用 hr 控制器方法,以便呈现一个模板,其中包含使用提供的参数筛选后新获得的员工列表。

模板中的筛选和排序函数都需要呈现一个完整的屏幕刷新,因此让我们说明该模板提供的最后一个功能,它支持数据的异步加载,即,众所周知的 Ajax 设计过程。

使用该 Ajax 意味着,最终用户在主界面中只能看到少量的数据,但是如果他们希望,可以单击来获得其余的员工信息,而不必进行屏幕刷新。图 3 显示了某些单元格以这种形式显示的主界面。

图 3 TurboGears 为 Acme HR 显示的模板,包含应用的筛选和 Ajax 单元格

在主表的最后一个单元格中,我们提供了一个 HTML 链接,它指向名为 supervisorHR 的 JavaScript 函数,该函数采用了两个输入参数:在行中显示的 employee_id 及其相应的 manager_id。

如果您检查模板开头附近的 supervisorHR JavaScript 函数,将注意到我们首先声明了一个条件,以检查是否提供了 supervisor_id,然后将进入以下情形: 如果有 supervisor_id — 使用 MochiKit 库构建 XmlHttp/Aja 请求,调用 URL 指向我们的 supervisor 控制器函数,使用 supervisor_id 作为输入参数,这(正如前面所指出的)将以纯文本格式返回主管的姓名和电话号码。接下来,声明一个回调方法,在该方法中,通过进行 Ajax 调用的员工 ID 单元格呈现 Ajax 响应,Ajax 调用过程是通过另一个 MochiKit 函数实现的,它将使用单元格的 id 值将单元格的内容改写为 Ajax 响应(在 transport.responseTex 中提供)。

如果没有 supervisor_id — 不必执行 XmlHttp/Ajax 请求,因为该员工没有主管。在这种情况下,我们还是要使用上述 MochiKit 函数改写员工 ID 单元格,而不是放置一个硬编码的文本消息以表明该员工没有主管。

做完这些,我们将进入 Oracle 数据库支持的 Acme HR TurboGears 项目的结尾部分。但是在说一些结束语之前,下一部分将解决您在 TurboGears 项目超出该开发应用程序中提供的基本案例时,可能会遇到的问题。

TurboGears 生产问题:Oracle/Python Unicode 和 mod_python 与 Oracle HTTP Server

TurboGears 和 Python 的构建都是用来以 Unicode 格式表示字符串,通常缩写为 UTF-8(Unicode 转换格式)。在大多数情况下,这一表示字符串的最恰当的格式不会出现什么问题,但是在某些情况下,它可能会成为很难识别的错误。

TurboGears 中的 Unicode 错误涉及在最终 Web 布局中用 ? 表示的残缺字符以及堆栈中出现足够强烈的冲突时应用程序完全停止等多个方面。

如果您仅使用英语一种语言,您可以跳过这一部分。但是,如果您使用某些欧洲或亚洲语言,您可能需要调整 TurboGears 安装以正确管理 Unicode。

在 TurboGears 和 Python 中与使用特定语言有关的问题在于它们包含的特殊字符。é、ñ、ö 或 ç 等字符通常以它们自己的原始编码显示,使用 TurboGears 和 Python 使用的默认 Unicode 格式将形成冲突。

要避免此类错误,请参考表 2,其中包含一系列按层分类的步骤,要支持 UTF-8,建议采取这些步骤。 层 TurboGears UTF-8 配置 视图 将一个诸如 之类的 标签添加到您的所有 模板,指定 UTF-8。

视图/控制器 将 kid.encoding="utf-8" 参数添加到您项目中的 app.cfg 文件。

模型 将 sqlalchemy.convert_unicode=True 参数添加到您 项目中的 dev.cfg/prod.cfg 文件。

一旦您的 TurboGears 项目开始增长,另一个具有重要影响的要素是备份 Web 服务器。在我们的 Acme HR 示例项目中,我们使用了一个集成到 TurboGears 中的非常简单的测试服务器,但是这决不是一个旨在接受生产站点的负载的 Web 服务器。

对于生产部署,建议您使用 Apache Web 服务器和 mod_python 模块,前者是 Oracle HTTP Server 所基于的 Web 服务器,后者是从该 Web 服务器内部调用 Python 脚本所需的一个模块。

该过程需要对 Oracle HTTP Server 配置文件 (httpd.conf) 进行几项配置更改以及安装 mod_python,包括创建必需的引导脚本以通过 Oracle HTTP Server 为您的项目提供服务。您可以在 docs.turbogears.org/1.0/mod_python 找到这些步骤的全部记录。

结论

正如您希望在我们的简短项目中所体验的那样,TurboGears 为寻求可以在上面创建 Oracle 数据库支持的支持 Web 的应用程序的全面框架的 Python-istas 提供了不错的选择。

同样,对于使用 Oracle 技术的组织,TurboGears 还提供了其他方法,通过这些方法可以将数据存储公布到 Web,添加到诸如 Java、PHP 和 Ruby 等中间件语言不断增长的主体,而且在该过程中,允许内部 Python-istas 专家留在他们最高效的环境中并将 Oracle 数据发布到 Web。 Daniel Rubio [ http://www.webforefront.com/] Daniel Rubio 是一位在企业软件开发方面具有 10 年以上经验的软件顾问。他最近建立了 Mashup Soft,开始专门研究如何使用 Web 服务进行混搭。

将您的意见发送给我们

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值