级别: 中级 王飞鹏 (wangfp@cn.ibm.com), 软件工程师,
IBM
何磊 (leihe@cn.ibm.com), 软件工程师,
IBM
蔡旭斌 (caixubin@cn.ibm.com), 资深软件工程师,
IBM
2008 年 11 月 05 日 DB2 Everyplace 是 IBM 公司的一款移动数据库中间件产品,它用于在移动设备与企业数据库之间进行数据同步。基于 DB2 Everyplace 的解决方案可以使用户灵活方便地在远离办公室的地点访问所需的数据,这可以带来工作效率的大幅提高。本文通过引入一个快递公司的工作场景:邮递员对邮包的分发和获取。展示了在此场景下,开发基于 DB2 Everyplace 解决方案的具体过程,包括服务器和客户端的架构设计以及 DB2 Everyplace 具体特征和功能在应用程序中的实现方法。本文将为移动应用开发人员如何开发基于 DB2 Everyplace 的解决方案提供一个实例。
解决方案简介
本文将介绍一个基于 IBM DB2 Everyplace 产品的快递解决方案。在本章中,我们将通过介绍解决方案的应用场景和 DB2 Everyplace 的知识背景,进而给出这个解决方案的架构设计和流程实现。
场景介绍
在一个快递公司日常工作中,邮递人员是工作的主体,他们奔波于客户和公司之间,进行包裹的收取或者分发工作。但是他们常常会面临这样的问题:邮递人员在处理包裹的过程中,由于传统的信息交换方式往往不能使他们高效地与客户或者与快递公司之间进行信息的交换,从而使得他们无法迅速地获取客户的需求或者得到公司的工作安排指示。虽然传统的通讯方式比如打电话可以进行部分的工作安排和协调工作,但是由于缺乏一个统一的信息管理平台,不同邮递人员之间也无法进行有效地协调,使得整体的工作效率低下。
快递公司希望能有一个先进的信息系统帮助他们管理和协调日常工作,使得邮递人员能够及时得到自己的工作任务,并将自己的工作情况及时地更新到这个系统。而且在整体工作的协调上,能根据各个邮递人员实时的具体状况,比如当前位置、是否空闲等信息来合理安排新的工作,从而带来整体工作效率的提高,减少人力成本,并减少客户的等待服务时间,提高客户的满意度,创造新的商业价值。
IBM 公司基于 DB2 Everyplace 移动数据库中间件产品对此场景提出了一个优秀的解决方案原型,我们称为 Mobile Parcel Demo。在后面的章节中将对此解决方案的实现思路和方法作具体的介绍,以此使读者了解 DB2 Everyplace 产品,并掌握基于此产品的解决方案开发过程。
知识背景
DB2 Everyplace 作为 IBM 普适计算解决方案的一部分,将企业数据服务从后端的业务数据库扩展到前端的手持设备,使得前端与后端的数据进行无缝连接,从而满足了电子商务移动化的需要。(见图 1)
DB2 Everyplace 基于三层架构,由下面三部分组成:
- DB2 Everyplace 移动设备端(Mobile Devices):包括移动数据库和同步客户端。DB2 Everyplace 移动数据库允许不同的手持设备上的数据可以存放在数据库中进行管理。DB2 Everyplace 同步客户端运行于手持设备端负责移动数据库与同步服务器进行同步。
- DB2 Everyplace 同步服务器(Sync Server):作为企业数据源与手持设备移动数据库之间的桥梁,DB2 Everyplace 同步服务器运行于服务器端,并负责管理业务数据库与手持设备上的移动数据库进行双向的数据同步。同步服务器还负责镜像数据库和企业后台源数据库的复制。
- DB2 Everyplace 后台源数据库(Data Source):DB2 Everyplace 支持多个业界著名的数据库,包括 DB2,Oracle,Microsoft SQL Server 和 Informix。
图 1. DB2 Everyplace 产品的结构模型
解决方案架构设计
我们在上一节中已经介绍了 DB2 Everyplace 是如何在客户端和服务器端进行数据同步的。解决方案的设计也是基于这样一种客户端 - 服务器(C-S)的架构模式。我们用图 2 来表示 DB2 Everyplace 是如何应用到这个具体的场景中的:
图 2. 解决方案应用场景流程图
由图 2 我们可以看到解决方案的具体流程。在此场景中,一共有三类主体:客户(Customer)、快递公司前台(Front Desk)和快递公司邮递员(Operator),其中邮递员是客户端的操作主体,而前台是服务器端的操作主体。具体的工作流程分为两类,分别为邮件的获取 (Parcel Pickup) 和邮件的分发 (Parcel Delivery)。
客户如果想要投递一个自己的邮件,则要向快递公司前台发起一个投递请求(Pickup Request),要求邮递员上门取包裹。前台(服务器端)根据请求与客户设定一个预约(Appointment),并将相关信息,如预约的客户信息、时间信息等存入公司的源数据库 (DB2 Source Database),源数据库与镜像数据库进行复制,并通过 DB2 Everyplace 同步服务器与邮递员手中的移动设备进行数据同步。移动设备上安装有解决方案的客户端应用程序,对数据进行相关处理之后,邮递员就可以得到自己的取件任务信息,并在指定的时间赴约,获取客户要投递的包裹。交易完成之后,通过客户端应用程序将交易的信息如收据等存入自己设备上的 DB2 Everyplace 数据库,之后可以通过移动设备与服务器端的数据同步,将相关信息提交到快递公司的前台。这样邮递员就完成了一次邮包的获取任务。
类似地,当快递公司收到某邮包需要让邮递员投递给相关客户的时候,前台将与客户设定一个投递预约,邮递员将根据此预约在指定的时间把邮件投递给这位客户。
可以看到,在整个场景流程中,邮递员只需要通过移动设备与公司前台数据库进行同步(Synchronize)就可以获取自己的任务,并将任务完成信息反馈,整个过程快捷高效。
搭建开发环境
Mobile Parcel Demo 是一个 C-S 架构的应用程序。在本章中,我们从客户端和服务器端分别介绍所用到的开发工具以及开发环境的搭建。
客户端开发环境
客户端应用程序采用 WinCE 平台上的 J2ME 开发,使用的开发工具为 WebSphere Studio Device Developer(WSDD)。WSDD 是 IBM Workplace Client Technology,Micro Edition 平台的一个主要集成组件,它是一个基于 Eclipse 的集成开发环境,用于创建和测试可以部署到手机和其它嵌入式设备上的应用程序。
下面介绍在 WSDD 上搭建一个 J2ME 开发环境的过程。
- 打开 WSDD,新建一个项目(见图 3)。
图 3. 创建 WSDD 项目
- 选择项目类型,点击“下一步”(见图 4)。
图 4. 选择 J2ME 作为项目类型
- 填写项目名称,点击“下一步”(见图 5)。
图 5. 填写项目名称
- 选择类库:JCL Foundation 1.0,点击“完成”(见图 6)。这样,一个新的 J2ME 项目就成功建立起来了,可以在 src 文件夹下添加 Java 文件以编写我们的客户端应用程序。
图 6. 选择类库
- 为了使我们编写的程序能部署到移动设备上,必须配置应用程序的运行环境,包括配置构建版本等操作。
点击菜单中的“运行”->“运行…”项,见图 7,弹出运行环境配置面板。
图 7. 配置构建版本
- 见图 8,在“运行”面板中新建一个启动配置。
图 8. 新建启动配置
- 配置这个启动配置,包括:
在“Java 应用程序”选项卡中配置“项目”、“设备或 JRE”、“Java 应用程序”三条配置信息。见图 9,其中将“项目”输入栏中的信息与我们所创建的 J2ME 项目相关联;在“设备或 JRE”中配置 J9 虚拟机与我们要将应用程序安装到移动设备的具体路径;在“Java 应用程序”中添加并配置我们所需的通用构建器以生成 JAR 文件到移动设备。
图 9. 配置启动配置
- 在“自变量”选项卡中配置所需的程序自变量和 VM 自变量。一般来说程序自变量不用填写,而在 VM 自变量中需指明应用程序在移动设备上引用的 Java 虚拟机(比如 J9)和外部库的路径(见图 10)。
图 10. 配置环境变量
- 其他配置选项卡一般为默认值即可。
在 WSDD 中编写完成应用程序之后,确保已经有移动设备连接到本地工作站上,在“运行”选项卡中点击“运行”按钮,即可将客户端应用程序按照配置部署到移动设备上了。
服务器端开发环境
服务端应用程序采用 Rational Software Development Platform (RSDP) 开发环境。RSDP 是 IBM Rational 软件开发平台,它基于 Eclipse 技术,将各种不同的开发技术融合于一个平台。它提供了 Java/J2ee/Web Service、UML、C++ 等技术的统一开发环境,并使得开发和测试都变得简单和可控制。
我们的服务器端应用程序主要使用 JSP 进行开发。搭建开发环境的过程并不复杂,首先创建一个企业应用程序(此应用程序最后将被导出为 EAR 包部署在 WebSphere Application Server 上),见图 11,在左侧“项目资源管理器”面板中右键点击“企业应用程序”文件夹,并选择新建 ->企业应用程序项目。
图 11. 创建企业应用程序项目
在弹出的对话框中输入“db2e_mparcel”作为项目名称,点击“完成”。
我们需要创建一个动态 Web 项目作为企业应用程序项目的子模块,这个动态 Web 项目是真正实现服务器端功能的 JSP 页面集合。见图 12,在左侧的“项目资源管理器”面板中右键点击“动态 Web 项目”,并选择新建 ->动态 Web 项目。
图 12. 创建 Web 项目
在弹出的对话框中输入“mparcel”作为项目名称,并点击“完成”来创建这个 Web 项目。
在这个例子中,我们在 Web 项目下的“Java Resources”文件夹中编写我们的 Java Beans 和 Java Class,在“WebContent”中编写 JSP 页面。在“部署描述符”中编写 Servlet 和相关的部署配置信息。
在动态 Web 项目“mparcel”中编写我们的代码后,我们可以通过右键点击 JSP 文件,并选择运行 ->在服务器上运行来将我们的 Web 项目部署到 RSDP 中内置的 WebSphere Application Server 上,浏览其页面效果,也可以通过选择“调试”菜单来调试我们的项目。
建立了动态 Web 项目之后,我们需要将这个项目与之前建立的企业应用程序相关联。见图 13,在我们建立的企业应用程序“db2e_mparcel”下的“模块”文件夹上右键 ->导入 ->WAR 文件,在弹出的面板中的“Web 项目”输入框中用下拉框选择我们建立的动态 Web 项目“mparcel”并点击“完成”,就将 Web 项目包含进企业应用程序。
图 13. 关联项目
类似的,我们还可以在“项目实用程序 JAR”和“实用程序 JAR”中关联这个企业应用程序所需的项目和其他 JAR。这样,服务器端的开发环境就搭建完成了。
解决方案应用程序开发
本章将详细介绍客户端和服务器端的应用程序的具体开发过程,包括使用的具体技术、关键功能的实现方式以及部分代码样例。
客户端应用程序开发
Mobile Parcel Demo 的客户端是一个基于 J2ME 的应用程序,采用经典的 MVC 三层结构。Model 层使用 JDBC 与 DB2 Everyplace 数据库连接;使用 ESWT 作为 View 层;DB2 Everyplace 的关键功能和特征,比如数据的同步等在 Controller 层实现。下面分别介绍。
客户端应用程序的数据库是 DB2 Everyplace,首先我们要编写代码来完成与数据库的连接以及数据访问。DB2 Everyplace 数据库的表和其内容是通过与企业数据库比如 DB2 同步 (Synchronize) 得到的。在同步之后,应用程序将通过 JDBC 与数据库表相连接,并使用 SQL 语句进行相应的操作。在编写连接程序之前,需要先将 db2ejdbc.jar 和 isync4j.jar 以及 jdbc.jar 这 3 个 jar 包引入我们的 WSDD 项目 ParcelClient 下,并在 WSDD 运行面板中配置相应的类路径(见客户端开发环境一节)。见清单 1,整个数据库连接过程与在非移动平台数据库 JDBC 连接类似。
清单 1.DB2E 数据库连接和封装
public class DB {
private static DB instance = null;
private Connection con;
// 构造函数
private DB() {
try {
DB2eDataSource ds = new DB2eDataSource();
ds.setUrl("jdbc:db2e:/parcelclient/data");// 设置索要连接的 db2e 数据库路径
con = ds.getConnection();// 获得连接
con.setAutoCommit(false);
}
// 处理异常 , 省略
catch (SQLException sqlEx) {
......
}
}
// 单键方法用来返回 DB 唯一实例
public static synchronized DB getInstance() {
if (instance == null) {
instance = new DB();
}
return instance;
}
}
|
我们再来看看客户端的界面实现。客户端程序的界面部分采用 eSWT 编写,SWT(Standard Widget Toolkit)是一个开源的 GUI 编程框架,而 eSWT 作为 SWT 的一个子集,是在嵌入式编程中的一个 GUI 库。首先我们要在项目中引入 eswt-converged.jar 包,才能在程序中使用这个 GUI 框架。
我们将介绍一个使用 eSWT 编写的简单表单界面过程。要在界面上绘制图像或者控件的一般过程如下:首先,创建一个界面外壳 Shell,并获取相关的显示信息和设置格式,然后在这个 Shell 上创建一系列的控件,并设置他们的响应函数(Listener),最后使用 FormLayout 来设置这些控件的位置信息。在完成了 Shell 上所有控件的绘制之后,需要重载一个 Shell.open() 函数,来打开这个界面。清单 2 的代码简单展示了这个过程。
清单 2. eSWT 创建用户界面
public class MainShell {
private Shell shell;
public MainShell(final Display display) {
shell = new Shell(display, SWT.RESIZE | SWT.CLOSE); // 创建 shell 实例
FormLayout layout = new FormLayout();// 设置界面整体信息
layout.marginWidth = 100;
layout.marginHeight = 100;
shell.setLayout(layout);
shell.setText("Parcel_Main");
// 创建一个 button 控件
Button sync = new Button(shell, SWT.PUSH);
sync.setText("Sync");
sync.addListener(SWT.Selection, new Listener() { // 设置响应函数
public void handleEvent(Event event) {
goSync();
}
});
// 设置 Layout 位置信息
FormData syncLayout = new FormData();
syncLayout.left = new FormAttachment(40, 0);
syncLayout.right = new FormAttachment(80, 0);
syncLayout.height = 40;
syncLayout.bottom = new FormAttachment(100,0);
sync.setLayoutData(syncLayout);
}
// 重载 open 方法
public void open() {
shell.open();
}
}
|
客户端应用程序提供可视化界面,使得邮递员可以方便地对包裹进行收取和分发,以及对相关工作例如包裹信息的查询,客户端和服务器端的同步等。图 14 是客户端程序的逻辑结构。
图 14. 客户端用户逻辑和界面结构
读者可以通过将这个结构流程与之前提到的应用场景做比较来更好地了解客户端部分的逻辑。
最后,我们来看看客户端的中间逻辑层的实现。客户端逻辑的关键功能是通过调用 DB2 Everyplace 提供的 API 来实现的,有关 API 的具体说明请参见参考资源中的 DB2 Everyplace Application and Development Guide。在这里我们举一个例子,介绍客户端与服务器端数据库同步实现的具体方法。DB2 Everyplace v912 开始支持 XML 类型和 BLOB 类型的同步。
同步的过程大致是这样的:
- 首先,在连接 Sync Server 之前需要设置相关的连接信息,比如驱动信息,Server 的 URL,连接的用户名、密码等信息。我们使用一个 property 文件来保存这个信息,在 sync 初始化的时候读进这个文件,并将相关信息设置完毕。这个 property 文件可以通过客户端的一个页面进行修改。
- 然后,通过读取 Sync Driver 并从 Sync Manager 获取 Sync Service 的一个实例创建一个同步服务,并设置一个此服务的事件处理函数 Sync Listener 来处理相关的同步信息(通过实现接口 ISyncListener 中的方法 eventIssued),完成数据的同步。
此过程的主要代码片断见清单 3。
清单 3. Synchronize 的实现过程
public class ParcelSync implements ISyncListener{
// 设置一些 Sync 主体和变量
private ISyncProvider provider;
private ISyncService service;
private ISyncConfigStore config;
private ISyncDriver syncer;
//Implement eventIssued 方法处理相关 Sync 事件
public int eventIssued(ISyncEvent evt){
// 各类事件的处理
switch(evtType) {
case ISync.EVTTYPE_INFO:
case ISync.EVTTYPE_ERROR:
// 返回
return ISync.RTNCB_DONE;
// 其他 case,以下省略
……
default:
break;
}
return ISync.RTNCB_DEFAULT;
}
public void goSync(String propFile){
try {
int rc = 0;
// 读取 property 文件
readProperties(propFile);
// 加载 sync driver
Class.forName(userProps.getProperty("syncdriver"));
// 获取 synchronization service 实例
provider =
ISyncManager.getISyncProvider(userProps.getProperty("syncprotocol"));
service =
provider.createSyncService(userProps.getProperty("server.url"), userProps);
// 获取 configuration store 实例
config = service.getConfigStore(userProps.getProperty("path"));
// 获取 sync driver 实例以运行 synchronization
syncer = config.getSyncDriver();
// 设置 listener
syncer.setSyncListener(this);
// 同步每个 subscription sets, 省略具体枚举过程
rc = syncer.sync();
ssArr = config.getSubscriptionSets();
……
}
catch(ISyncException ie){
System.out.println("Exception code:" + ie.getCode());
ie.printStackTrace();
}
} //goSync
}
|
按照以上过程,调用 goSync() 方法就能实现与服务器端的数据同步。
服务器端应用程序开发
在服务器端,我们使用 DB2 作为源数据库,在之前建立的动态 Web 项目中,使用 JDBC 与数据库相连接。服务器端的主要功能是设立客户预约 (appointment) 以安排邮递员的工作,并可以进行各种数据的查询请求,以获得相关的工作信息。还有就是将一些 DB2 Everyplace 同步服务器端的操作进行封装,在 Web 界面中提供方便操作,比如源数据库和镜像数据库的复制 (Replicate),通过调用 Sync Server 的 dsyreplicate.bat 文件来实现,此文件在 DB2 Everyplace 安装目录下的 Server/bin 文件夹中(见清单 4)。
清单 4.在 Web 页面中调用 DB2 Everyplace 的 .bat 文件
//DB2E 的安装路径
String cmd = DB2E_Install_Path +"\\dsyreplicate.bat";
// 命令集 , 选择要复制的数据库名称
String[] cmds = { cmd,"parcelmd" };
// 调用功能
final Process p = Runtime.getRuntime().exec(cmds, null, dir);
|
在数据库设计中,我们引入了 XML 字段和 BLOB 字段分别用来存储交易的收据信息和签名信息。DB2 Everyplace 的数据同步能很好的支持这两种特殊格式的字段,在解决方案中使用了 DB2 Everyplace 产品的这个非常好的特性和功能。最后我们将数据库表导出为 ddl 文件以满足我们的发布和部署需要。
具体来说,服务器端是基于 JSP 的 Web 应用程序。图 15 是服务器端的页面结构图。
图 15. 服务器端 Web 应用程序结构
值得一提的是,在服务器端我们还需要做的一个非常重要工作就是建立 DB2 Everyplace 数据同步的设置,包括同步的用户 (User)、组 (Group)、预订 (Subscription) 和预订集 (Subscription Set) 等。只有在 DB2 Everyplace 中正确配置了这些选项,客户端与服务器端的数据同步才有可能实现。通常,我们通过启动 DB2 Everyplace 中的移动设备管理中心(MDAC)来配置这些信息,具体方法请参见参考资源中的 DB2 Everyplace Sync Server Administrator Guide。考虑到我们的解决方案在部署时需要具有通用性,手动的配置方式不够方便快捷,所以我们引入了 Sync Server 的 XML 配置方式。这种方式允许用户将现有的 Sync Server 配置导出为 XML 文档,并在其他 Sync Server 环境中导入这个 XML 文档以完成相同的 Sync Server 配置信息。我们通过调用 Sync Server 的 dsyadminxml.bat 文件来实现这个功能,这个文件也在 DB2 Everyplace 安装目录下的 Server/bin 文件夹里。导出当前 Sync Server 配置信息的命令是“dsyadminxml.bat -x 要导出的文 XML 件名”,将已有 XML 配置文件导入 Sync Server 的命令是“dsyadminxml.bat -d 要导入的 XML 文件名”。
应用程序的打包和发布
客户端打包和发布
在使用 WSDD 开发环境将客户端的应用程序部署到移动设备上之后,我们需要将程序打包成 .cab 文件以使客户端程序可以方便的再次安装到其他移动设备 WinCE 系统上。我们需要使用 Microsoft 的 Cabwiz.exe 工具将 J2ME 程序打包,步骤如下:
首先将整个应用程序目录放置到 Cabwiz.exe 同一目录下,随后编写一个打包配置文件 cab.inf,这也是最重要的一步。清单 5 给出一个样例,读者可以参照此样例写出自己的 inf 文件。
清单 5. inf 文件样例
[Version] ; 标识信息
Signature ="$Windows NT$"
Provider ="DB2E"
CESignature ="$Windows CE$"
[CEStrings] ; 应用程序信息
CompanyName="IBM"
AppName =" ParcelClient"
InstallDir = \%AppName% ; Install in \Program Files\app_name
[SourceDisksNames] ; 源目录信息
; 指出要打包的源文件夹所在路径
1=,"DemoPath",,"C:\Program Files\Windows CE Tool\wce420\POCKET PC 2003\Tools\ParcelClient"
2= ……
[SourceDisksFiles] ; 源文件信息
db2sync_db2e.properties = 1 ; 使用 SourceDisksNames disk_id_num 为 1 的配置
ParcelClient.lnk = 1
ParcelClient.jar = 1
…… ; 列出其他清单,省略
[DefaultInstall] ; 安装目录
CopyFiles = CopyToDemoPath,CopyToImage,CopyToJar,CopyToLib
[DestinationDirs] ; 拷贝文件夹目标目录
CopyToDemoPath = 0,\%AppName%
CopyToImage = 0,\%AppName%\img
CopyToLib = 0,\%AppName%\lib
CopyToJar = 0,%CE2%
; 以下为具体文件列表
[CopyToDemoPath]
db2sync_db2e.properties,,,0
ParcelClient.lnk,,,0
ParcelClient.jar,,,0
[CopyToImage]
gray-log.png ,,,0
…… ; 列出其他图片清单,省略
[CopyToLib]
eSWT-converged.jar ,,,0
…… ; 列出其他 lib 文件清单,省略
[CopyToJar]
eSWT-converged.jar ,,,0
|
最后在 Cabwiz.exe 所在目录下调用控制台命令 cabwiz.exe “cab.inf”即可在上述同一文件夹中获得一个应用程序的 .cab 包。
如果要在其他移动设备的 WinCE 系统中安装这个应用程序,只需要将 .cab 包拷贝到移动设备的任意目录下,双击即可完成安装。
服务器端打包和发布
我们可以在 RSDP 中通过右键点击动态 Web 项目 ->导出 ->WAR 文件来将 Web 项目导出,导出的 WAR 包可以部署在类似如 Tomcat 服务器上。
我们也可以通过右键点击企业应用程序 ->导出 ->EAR 文件来将整个企业应用程序导出为 EAR 包,它可以部署在 WebSphere Application Server 上。
小结
DB2 Everyplace 是 IBM 公司的一款优秀的移动数据库中间件产品,它为无线数据应用开发提供了强大的支持。本文通过引入一个快递公司的常见工作场景,提出了一个基于 DB2 Everyplace 的解决方案,并从客户端和服务器端详细介绍了这个解决方案的实现方法。客户端和服务器端的开发分别使用了基于 Eclipse 的集成开发环境,可以看到在这些集成环境下的开发非常方便,它们的功能也十分强大。在完成了解决方案的开发过程之后,还介绍了应用程序的打包和发布过程。相信在阅读完本文之后,读者能自行完成一个基于 DB2 Everyplace 产品的解决方案从设计、开发到发布的整个过程,体会到在移动设备上开发解决方案的乐趣。
本文中提到的解决方案 Mobile Parcel Demo 可以从以下链接中下载:
http://www-01.ibm.com/support/docview.wss?rs=212&context=SSKTG6&dc=D400&uid=swg24016626&loc=en_US&cs=utf-8&lang=en
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | 王飞鹏是 IBM 中国软件开发中心的软件工程师。 |
 | |  | 何磊是中科院软件所硕士研究生 , 从事互联网方面的研究和开发工作 , 曾在国内外期刊和学术会议发表学术论文、文章、专利若干。现在 DB2 Everyplace 部门从事解决方案的研发工作。 |
 | |  | 蔡旭斌是 IBM CDL 资深软件工程师 , 曾在国内外期刊和学术会议发表学术论文、文章若干。现从事 DB2 Everyplace 产品的发布管理工作。 |
对本文的评价
|