PHP连接数据库学习手册

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 范例 1: Select 指令 任务:连结到 Access 的 Northwind DSN,然后在每一列显示头2个字段。(Northwind 北风数据库,在ODBC设定的DSN,是Access的标准范例数据库) 在这个范例中,我们建立一个 ADOConnection 对象,它代表了和数据库的连结。连结是以 PConnect 函数来初始化的,然后会持续的连结着。任何时候我们要

范例 1: Select 指令

任务:连结到 Access  Northwind DSN,然后在每一列显示头2个字段。(Northwind 北风数据库,在ODBC设定的DSN,是Access的标准范例数据库)

在这个范例中,我们建立一个 ADOConnection 对象,它代表了和数据库的连结。连结是以 PConnect 函数来初始化的,然后会持续的连结着。任何时候我们要查询数据库时,我们就呼叫 ADOConnection.Execute() 函数,这将会回传一个ADORecordSet对象。事实上它只是一个指向在fields[]数组中,目前记录的指针,我们使用MoveNext()来在记录间移动。

注意:另一个很有用的函数 SelectLimit 并没有在这个范例里使用,这个函数允许我们去限制显示的数据笔数。

<?
include('ADOdb.inc.php');       # 加载ADODB
$conn = &ADONewConnection('access');    # 建立一个连结
$conn->PConnect('northwind');   # 连结到 MS-Access 北风数据库
$recordSet = &$conn->Execute('select * from products');
if (!$recordSet) 
        print $conn->ErrorMsg();
else
while (!$recordSet->EOF) {
        print $recordSet->fields[0].' '.$recordSet->fields[1].'<BR>';
        $recordSet->MoveNext();
}
 
$recordSet->Close(); # 选择性执行
$conn->Close(); # 选择性执行
 
?>

在这个例子中,$recordSet回传了存在$recordSet->fields数组里,目前所指向的记录。以字段编号为索引,起始值为0。我们使用MoveNext()函数来移动到下一笔记录,当到了最后一笔时,EOF属性会被设定为true。当Execute()函数执行有错误时,会回传一个false值,而不是一个recordset对象。

$recordSet->fields[]数组是由PHP数据库扩充函数库所产生的。有一些数据库扩充函数库仅支持以编号来进行索引,而不支持以字段名为索引。要强迫使用字段名索引,也就是要使用关连式数组,请使用 $ADODB_FETCH_MODE 全域变量来设定。当一个数据集被Execute()或是SelectLimit()函数建立时,都会储存而且使用储如此类的设定模式。

       $ADODB_FETCH_MODE = ADODB_FETCH_NUM;
        $rs1 = $db->Execute('select * from table');
        $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
        $rs2 = $db->Execute('select * from table');
        print_r($rs1->fields); # shows array([0]=>'v0',[1] =>'v1')
        print_r($rs2->fields); # shows array(['col1']=>'v0',['col2'] =>'v1')

上面的范例说明,如果要以顺序来存取字段,就将 $ADODB_FETCH_MODE 的值设为 ADODB_FETCH_NUM,要以关连式数组(以字段名)存取字段,就要将值设为 ADODB_FETCH_ASSOC

要取得在被选到的记录笔数,你可以使用$recordSet->RecordCount()方法。注意,如果不能确定得到的记录笔数,会回传 -1 

范例 2: 进阶的 Select 指令(使用 Field 对象)

任务:选取一个资料表,显示最前面的二栏。如果第二栏是一个日期或时间型态字段,将它格式化成US格式。

<?
include('ADOdb.inc.php');       
$conn = &ADONewConnection('access');    
$conn->PConnect('northwind');   
$recordSet = &$conn->Execute('select CustomerID,OrderDate from Orders');
if (!$recordSet) 
        print $conn->ErrorMsg();
else
while (!$recordSet->EOF) {
        $fld = $recordSet->FetchField(1);
        $type = $recordSet->MetaType($fld->type);
 
        if ( $type == 'D' || $type == 'T')
                print $recordSet->fields[0].' '.
                        $recordSet->UserDate($recordSet->fields[1],'m/d/Y').'<BR>';
        else 
                print $recordSet->fields[0].' '.$recordSet->fields[1].'<BR>';
 
        $recordSet->MoveNext();
}
$recordSet->Close(); # optional
$conn->Close(); # optional
 
?>

在这个例子中,我们使用 FetchField() 函数来检查第二个字段的资料型别。这将会回传一个至少有三个字段的对象,字段说明如下:

·         name字段名

·         type字段的资料原生型别native field type of column

·         max_length字段的最大长度,部份数据库像MySQL,并不回传字段的正确值,以这个例子而言,就会回传 -1 

然后我们使用 MetaType() 去转换原生型别成通用型别,目前通用型别定义如下:

·         C:  character 字段,应该使用 <input type="text"> 标记来取值。

·         X文字字段(Text) , 长文字字段,使用 <textarea> 标记来显示资料。

·         B: Blob 字段或者大型的二位对象(像程序,图文件等)

·         D日期字段

·         T时间字段

·         L逻辑字段(真假值)或位字段

·         N数字字段,包含自动进位、编号、整数、浮点数、实数等。

·         R序列字段,包含了序列、自动增进整数,只对被选择的数据库作用。

如果对应型别是日期或时间,那你可以使用 UserDate() 函数来设定输出的日期格式。这个函数会转换 PHP SQL 日期字符串格式为使用者定义的格式。 另一个使用MetaType()的时机是在进行SQL新增或更新指令时,资料格式验证用。

范例 3: 新增

新增一笔记录到订单资料表,里面包含了日期和字符串,为了能被数据库正常存取,字符串必需校正,以避免部份标记字符。例如:有单引号的字符串,John's

<?
include('ADOdb.inc.php');       # load code common to ADOdb
$conn = &ADONewConnection('access');    # create a connection
 
$conn->PConnect('northwind');   # connect to MS-Access, northwind dsn
$shipto = $conn->qstr("John's Old Shoppe");
 
$sql = "insert into orders (customerID,EmployeeID,OrderDate,ShipName) ";
$sql .= "values ('ANATR',2,".$conn->DBDate(time()).",$shipto)";
 
if ($conn->Execute($sql) === false) {
        print 'error inserting: '.$conn->ErrorMsg().'<BR>';
}
?>

在这个范例中,我们看见了ADODB更进一步的日期及标点符号的处理方式。Unix 日期时间标示(长整数)DBDate()格式化成Access可以接受的格式,而带了缩写符号的 John's Old Shoppe 则被 qstr() 函数处理成 John''s Old Shoppe 字符串,以被数据库合法存取。

观察 Execute 指令的错误处理。如果 Execute() 执行有错误发生时,会传回 False 值。而最后的错误讯息可以由  ErrorMsg() 来显示。

附记:php_track_errors旗标可以被激活,以便将错误讯息储存起来。

范例 4: 除错

<?
include('ADOdb.inc.php');       # load code common to ADOdb
$conn = &ADONewConnection('access');    # create a connection
$conn->PConnect('northwind');   # connect to MS-Access, northwind dsn
$shipto = $conn->qstr("John's Old Shoppe");
$sql = "insert into orders (customerID,EmployeeID,OrderDate,ShipName) ";
$sql .= "values ('ANATR',2,".$conn->FormatDate(time()).",$shipto)";
$conn->debug = true;
if ($conn->Execute($sql) === false) print 'error inserting';
?>

在上面的例子中,我们藉由设定 debug=true 来激活除错模式。这将会在执行指令时会先将SQL指令显示,并且会显示所有的错误讯息,而不需要去呼叫 ErrorMsg() 。显示资料集的部份,可以参考 rs2html() 范例。

其它的请参考自定错误处理的说明。

范例 5: MySQL及选单

连结到MySQL数据库 agora ,并且从SQL命令中建立一个 <select> 选单,<option>的标题是第一个字段,回传值是第二个字段。

<?
include('ADOdb.inc.php'); # load code common to ADOdb
$conn = &ADONewConnection('mysql');  # create a connection
$conn->PConnect('localhost','userid','','agora');# connect to MySQL, agora db
$sql = 'select CustomerName, CustomerID from customers';
$rs = $conn->Execute($sql);
print $rs->GetMenu('GetCust','Mary Rosli');
?>

Here we define a menu named GetCust, with the menu option 'Mary Rosli' selected. See GetMenu(). We also have functions that return the recordset as an array: GetArray(), and as an associative array with the key being the first column:GetAssoc().

这里,我们定义了一个名为GetCust的选单,默认值是'Mary Rosli'。相关说明请参考 GetMenu() 。我们也将资料集以数组回传的方式写在 GetArray()方法里。而另外回传关系型数组的方法则使用 GetAssoc() ,其中第一个字段是这个字段的键值。

 1.50 版以后的 ADODB 里,是使用公共变量 $ADODB_FETCH_MODE 来设定回传的数组是以编号或是关连式字符串做索引。

范例 6: 一次连结两个数据库

<?
include('ADOdb.inc.php');     # 加载 ADOdb
$conn1 = &ADONewConnection('mysql');  # 建立一个 mysql 连结
$conn2 = &ADONewConnection('oracle');  # 建立一个 oracle 连结
 
$conn1->PConnect($server, $userid, $password, $database);
$conn2->PConnect(false, $ora_userid, $ora_pwd, $tnsname);
 
$conn1->Execute('insert ...');
$conn2->Execute('update ...');
?>

范例 7: 产生 Update  Insert SQL指令

ADODB 1.31版起,新增了两个数据集函数:GetUpdateSQL()GetInsertSQL()。这允许你在执行了像"SELECT * FROM table query WHERE..."这样的查询函数后,建立一个 $rs->fields复本,改变这些字段,然后自动产生出更新或是新增的SQL指令。

以下我们展示如何运用这些函数,我们将存取一个资料表,带有下列字段:(ID,FirstName,LastName,Created)。在这些函数被执行前,你需要藉由一个对资料表的查询指令(select)来初始化一个数据集。

<?
#==============================================
#  GetUpdateSQL()  GetInsertSQL() 范例码
#==============================================
include('ADOdb.inc.php');
include('tohtml.inc.php');

#==========================
以下的程序代码测试新增状态

$sql = "SELECT * FROM ADOXYZ WHERE id = -1"; 
从数据库中查询出一个空的资料集

$conn = &ADONewConnection("mysql");  # 建立一个连结
$conn->debug=1;
$conn->PConnect("localhost", "admin", "", "test"); # 连结到 MySQL, 数据库名称为 test
$rs = $conn->Execute($sql); # 执行查询,并取得一个空的资料集

$record = array(); # 初始化一个数组,以便存放记录资料供新增用

设定记录中的字段值
$record["firstname"] = "Bob";
$record["lastname"] = "Smith";
$record["created"] = time();

传入空的资料集及字段资料数组到GetInsertSQL函数中,以执行功能
这个函数将会依传入的资料,回传一个全格式的 INSERT SQL指令

$insertSQL = $conn->GetInsertSQL($rs, $record);

$conn->Execute($insertSQL); # 将记录挿入数据库中

#==========================
以下的程序代码测试更新状态

$sql = "SELECT * FROM ADOXYZ WHERE id = 1"; 
选择一笔记录以便更新

$rs = $conn->Execute($sql); # 执行这个查询,并取得一个存在的记录来更新

$record = array(); # 初始化一个数组,以存放要更新的数据

设定字段里的值
$record["firstname"] = "Caroline";
$record["lastname"] = "Smith"; # 更新 Caroline的姓由 Miranda 变成 Smith

传入这个只有单一记录的资料集以及含有资料的数组到 GetUpdateSQL函数里
函数将会回传一个具有正确 WHERE 条件的 UPDATE(更新) SQL 指令
$updateSQL = $conn->GetUpdateSQL($rs, $record);

$conn->Execute($updateSQL); # 更新数据库中的记录
$conn->Close();
?>

范例 8: 使用上一笔及下一笔实作卷动

我们使用HTTP取得 $next_page 变量,以追踪要跳去那一页并且储存目前页码在 session 变量 $curr_page 里。

我们呼叫连结对象的 PageExecute()函收去取得我们要的资料集,然后我们使用数据集的 AtFirstPage()  AtLastPage() 函数去决定是否显示下一页和上一页按钮。

<?php
include_once('ADOdb.inc.php');
include_once('tohtml.inc.php');
session_register('curr_page');
 
$db = NewADOConnection('mysql');
$db->Connect('localhost','root','','xphplens');
$num_of_rows_per_page = 10;
$sql = 'select * from products';
 
if (isset($HTTP_GET_VARS['next_page']))
        $curr_page = $HTTP_GET_VARS['next_page'];
if (empty($curr_page)) $curr_page = 1; ## at first page
 
$rs = $db->PageExecute($sql, $num_of_rows_per_page, $curr_page);
if (!$rs) die('Query Failed');
 
if (!$rs->EOF && (!$rs->AtFirstPage() || !$rs->AtLastPage())) {
        if (!$rs->AtFirstPage()) {
?>
<a href="<?php echo $PHPSELF,'?next_page=',$rs->AbsolutePage() - 1 ?>">Previous page</a>
<?php
        }
        if (!$rs->AtLastPage()) {
?>
<a href="<?php echo $PHPSELF,'?next_page=',$rs->AbsolutePage() + 1 ?>">Next page</a>
<?php
        }
        rs2html($rs);
}
?>

以上的程序代码可以在 testpaging.php 范例里找到。

使用自定错误处理及 PEAR_Error

在之前的版本,你可以使用像 $con->debug=true ; 这样的设定来进行除错。但在 1.50 版后,我们提供了另一种方法来处理错误状态。我们让工程师可以使用 ADODB 的自订错误处理程序功能。

ADODB 提供了两种自订处理方式,你可以配合你的的需要而修订。第一个方法放在 ADOdb-errorhandler.inc.php 档案里。这让你可以使用标准的 PHP 函数 err_reporting 去控制要显示怎样的错误讯息及 trigger_error 去呼叫 PHP 预设的错误处理程序。

引入了上述档案后(ADOdb-errorhandler.inc.php),当发生了下列的错误后,将会使得 trigger_error($errorstring,E_USER_ERROR)被呼叫。

1.       Connect()  PConnect() 执行失败时。

2.       执行 SQL 指令的函数失败时,如 Execute()  SelectLimin() 

3.       GenID() 进入了无限循环时。

这里的 $errorstring 变量是由 ADODB 所产生的。而且会包含了有用的除错讯息,类似于随后会建立的 error.log 资料。所以,为了要能正确提供除错讯息,你要在建立 ADOConnection 对象前,就把 ADOdb-errorhandler.inc.php 引入到程序代码中。

If you define error_reporting(0), no errors will be shown. If you set error_reporting(E_ALL), all errors will be displayed on the screen.

如果你设定了 error_reporting(0) 的话,将不会有任何错误被显示。如果你设定了 error_reporting(E_ALL),那将会显示所有的错误讯息。

以下是一个简单的范例:

<?php
error_reporting(E_ALL); # 显示所有的错误讯息
include('ADOdb-errorhandler.inc.php');
include('ADOdb.inc.php');
include('tohtml.inc.php');
$c = NewADOConnection('mysql');
$c->PConnect('localhost','root','','northwind');
$rs=$c->Execute('select * from productsz'); #不正确的资料表 productsz');
if ($rs) $rs2html($rs);
?>

如果你要把错误讯息记录下来,你可以定义两个选择性常数 ADODB_ERROR_LOG_TYPE, ADODB_ERROR_LOG_DEST。有关于 ADODB_ERROR_LOG_TYPE 的值,你可以去参考 PHP 使用手册中有关于 error_log 的说明。在以下的范例中,我使将它设为 3,意思是指将讯息记录到常数 ADODB_ERROR_LOG_DEST 所设定的档案中。

<?php
error_reporting(0); # 不显示任何的错误讯息
define('ADODB_ERROR_LOG_TYPE',3);
define('ADODB_ERROR_LOG_DEST','C:/errors.log');
include('ADOdb-errorhandler.inc.php');
include('ADOdb.inc.php');
include('tohtml.inc.php');
 
$c = NewADOConnection('mysql');
$c->PConnect('localhost','root','','northwind');
$rs=$c->Execute('select * from productsz'); ## 不正确的资料表 productsz
if ($rs) $rs2html($rs);
?>

以下则是写在 error.log 文件的错误讯息:

(2001-10-28 14:20:38) mysql error: [1146: Table 'northwind.productsz' doesn't exist] in
 EXECUTE("select * from productsz")

第二种错误处理方法是 ADOdb-errorpear.inc.php 。使用这种方式,在错误发生时会产生 PEAR_Error 衍生对象,而最后产生的 PEAR_Error 对象可以被 ADODB_Pear_Errir() 函数取回。

<?php
include('ADOdb-errorpear.inc.php');
include('ADOdb.inc.php');
include('tohtml.inc.php');
$c = NewADOConnection('mysql');
$c->PConnect('localhost','root','','northwind');
$rs=$c->Execute('select * from productsz'); #不正确的资料表 productsz');
if ($rs) $rs2html($rs);
else {
        $e = ADODB_Pear_Error();
        echo '<p>',$e->message(),'</p>';
}
?>

在引入 ADOdb-errorpear.inc.php 档之前,藉由定义 ADODB_PEAR_ERROR_CLASS 常数,你可以使用一个 PEAR_Error 衍生类别。为了方便除错,你可以在 PHP 程序代码的最前面定义预设的错误理方式为 PEAR_ERROR_DIE,这将会使得程序一出错,马上就输出错误讯息,并且停止执行。

include('PEAR.php');
PEAR::setErrorHandling('PEAR_ERROR_DIE');

注意,当错误产生时,ADODB并没有明确的回传一个 PEAR_Error 对象给你。你必需要去呼叫 ADODB_Pear_Error() 函数去取回最后的错误内容。或者,你可以使用 PEAR_ERROR_DIE 这个技巧。

资料集快取

现在,ADODB使用 CacheExecute(),CachePageExecute()CacheSelectLimit()函数来支持数据集快取。用法类似于没有快取的函数,除了要加上一个新的参数 $secs2cache

以下是一个范例 :

include('ADOdb.inc.php'); # 加载ADODB
$ADODB_CACHE_DIR = '/usr/ADODB_cache';
$conn = &ADONewConnection('mysql');  # 建立一个连结
$conn->PConnect('localhost','userid','','agora');# 连结到 MySQL, agora 数据库
$sql = 'select CustomerName, CustomerID from customers';
$rs = $conn->CacheExecute(15,$sql);

第一个参数是设定查询的快取秒数。随后呼叫的查询将会使用存放在由  $ADODB_CACHE_DIR 变量指定的快取数据。要强迫查讯执行,并且更新快取记录,使用 CacheExecute() 函数,并且将第一个参数设为 0 。或者,使用 CacheFlush($sql) 也行。

基于安全的考量,如果你要使用 $ADODB_CACHE_DIR,我们建议你将在 php.ini 里的 register_globals 设成 off 

 ADODB 1.80版以后,在 CacheSelectLimit()  CacheExecute() 中,参数 secs2cache 是选择性的。如果你不填上去,系统将会使用 $connection->cacheSecs 属性的值,它的默认值是 60 分钟。

       $conn->Connect(...);
        $conn->cacheSecs = 3600*24; // 快取24小时
        $rs = $conn->CacheExecute('select * from table');
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
存储 Go API
使用GoFrame连接和操作TDengine时序数据库
通过使用GoFrame框架和TDengine Go驱动,我们可以方便地连接和操作TDengine时序数据库。无论是插入、查询还是分析时序数据,都可以通过简单的API调用来实现。GoFrame提供了强大的Web开发功能,结合TDengine的高性能时序数据存储和查询能力,可以构建高效、可扩展的时序数据应用。
|
1月前
|
NoSQL 关系型数据库 PHP
php连接数据库
要使用PHP连接PolarDB或MongoDB数据库,需先准备连接信息,并编写相应代码。对于PolarDB,需设置主机地址、端口、数据库名及凭据,使用`pg_connect`函数建立连接;而对于MongoDB副本集,需安装MongoDB PHP驱动,通过`MongoDB\Client`连接指定的副本集实例。请确保替换示例代码中的占位符为实际值,并正确配置副本集名称和主机信息。更多详细信息与示例代码,请参考相关链接。
136 72
|
11天前
|
关系型数据库 MySQL 数据库
6-2|测试连接数据库的命令
6-2|测试连接数据库的命令
|
11天前
|
SQL 关系型数据库 MySQL
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
本文介绍了在ThinkPHP6框架中如何连接和使用数据库进行增删改查操作。内容包括配置数据库连接信息、使用Db类进行原生MySQL查询、find方法查询单个数据、select方法查询数据集、save方法添加数据、insertAll方法批量添加数据、insertGetId方法添加数据并返回自增主键、delete方法删除数据和update方法更新数据。此外,还说明了如何通过数据库配置文件进行数据库连接信息的配置,并强调了在使用Db类时需要先将其引入。
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
|
11天前
|
SQL 关系型数据库 数据库连接
php连接数据库之PDO,PDO的简单使用和预定义占位符的使用以及PDOStatement对象的使用,占位符的不同形式,bindValue和bindParam绑定预定义占位符参数的区别
本文介绍了PHP中PDO(PHP Data Objects)扩展的基本概念和使用方法。内容包括PDO类和PDOStatement类的介绍,PDO的简单使用,预定义占位符的使用方法,以及PDOStatement对象的使用。文章还讨论了绑定预定义占位符参数的不同形式,即bindValue和bindParam的区别。通过具体示例,展示了如何使用PDO进行数据库连接、数据查询、数据插入等操作。
php连接数据库之PDO,PDO的简单使用和预定义占位符的使用以及PDOStatement对象的使用,占位符的不同形式,bindValue和bindParam绑定预定义占位符参数的区别
|
11天前
|
SQL 关系型数据库 MySQL
php学习笔记-连接操作mysq数据库(基础)-day08
本文介绍了PHP中连接操作MySQL数据库的常用函数,包括连接服务器、设置字符集、关闭连接、选择数据库、结果集释放、获取影响行数以及遍历结果集等操作。通过书籍查询的实例演示了如何使用这些函数进行数据库操作,并提供了一个PHP操纵MySQL数据库的模板。
php学习笔记-连接操作mysq数据库(基础)-day08
|
14天前
|
SQL JavaScript 关系型数据库
Node服务连接Mysql数据库
本文介绍了如何在Node服务中连接MySQL数据库,并实现心跳包连接机制。
27 0
Node服务连接Mysql数据库
|
1月前
|
IDE 关系型数据库 PHP
php语言学习
【9月更文挑战第1天】php语言学习
30 3
|
1月前
|
SQL 安全 IDE
如何开始学习PHP?
【9月更文挑战第1天】如何开始学习PHP?
32 1
|
2月前
|
API Java 开发框架
【从零到精通】如何用Play Framework快速构建RESTful API?看完这篇你就懂了!
【8月更文挑战第31天】《Play Framework快速入门:从零开始构建RESTful API》介绍了如何使用高性能Web开发框架Play Framework构建简单的RESTful API。从环境搭建到项目创建,再到实现用户列表的增删查功能,本文档详细指导每个步骤,并解释核心概念。适合初学者快速上手。首先确保已安装JDK和sbt,然后通过sbt创建Play项目,接着定义控制器、模型及路由,最后运行应用进行测试。通过本教程,你将掌握构建RESTful API的基础知识,为进一步学习Play Framework打下坚实基础。
32 0
下一篇
无影云桌面