别说爱就一个字,你数数你说过几次; 别说程序员好干,你看看你年薪几万!~ 别说你爱的太真,伤的却太深——只要真心的投入就无悔、无怨恨 别说失去的太多,那是不懂得收获;别说爱了又错过,那是你没有去收获住那么硕果!~ 请真心你现在所拥有的一切——守侯住属于你的爱情!FOR ever

详解Ajax中XMLHttp请求及利弊分析

上一篇 / 下一篇  2008-02-10 23:05:22 / 个人分类:报子箱!~

查看( 45 ) / 评论( 7 )
请注意此处的第一个参数,也就是请求类型,即使在技术层面要求请求类型全部为大写字符,但此处使用小写也是没关系的。 接下来,你需要定义一个onreadystatechange事件处理函数,XMLHttp对象有一个名为readyState的属性,当发送请求或收到响应时,此属性值会改变。IXPUB技术博客;Sm0W7oSx4C$p?6Y9F
以下是属性readyState可取的五个值:
E-bUyV4N00(未初始化):对象已创建,但还未调用open()方法。IXPUB技术博客r)v'FE[m-~)I
1(正在加载):已调用open()方法,但请求还未发送。
(x0`-A:v{X02(已加载):请求已经发送。
:mmc(y3B+d$}%@r2[03(相交互):已接收到一部分响应。IXPUB技术博客 \`2d1_h#aI;h-]4BqJ
4(完成):所有数据已收到,连接已经关闭。
)d{,k'[4B*E L0在每次readystate属性值变化时,都会引发readystatechange事件,并调用onreadystatechange事件处理函数。因为各浏览器实现之间的差异,在跨浏览器开发时唯一可靠的readystate值为0、1和4。在大多数情况下,当请求被返回时,只需检查此值是否为4:
#b4Y(}GS m z0IXPUB技术博客_d4@8}YFf
IXPUB技术博客'lmNtT{
IXPUB技术博客dG_I L2_)b
var XmlHttp = zXmlHttp.createRequest();oXmlHttp.open("get", "info.txt", true);oXmlHttp.onreadystatechange = function () {    if (oXmlHttp.readyState == 4) {        alert("得到响应!");    }};IXPUB技术博客 UYb&qA$Xf&yT
最后一步是调用send()方法,实际上是由它来发送请求的。这个方法只接受单一的参数——一个代表请求体的字符串。如果请求不需要一个请求体(比如说:GET请求就不需要),就必须传递给它一个null值:
vPX@ |@0IXPUB技术博客 [$e9\*hf9E f

Vz _w;j[,o'h(c:v0var XmlHttp = zXmlHttp.createRequest(); oXmlHttp.open("get", "info.txt", true); oXmlHttp.onreadystatechange = function () { if (oXmlHttp.readyState == 4) { alert("得到响应!"); } }; oXmlHttp.send(null);IXPUB技术博客)~6Mi(bT Rm^ P4`
IXPUB技术博客iQl4q$[&Ni1e
大体流程是,发送请求后,当收到响应,将会显示一个提示框,但仅仅显示一条请求已被接收的消息是远远不够的,XMLHttp的真正威力在于,不但可以让你访问到返回的数据,还可以访问到响应状态和响应头部信息。IXPUB技术博客T%`.|SE'}2b9]Vp

?C Gx%}\1SK*X0为得到从请求返回的数据,可使用responseText和responseXML属性;属性responseText返回一个包含请求体的字符串,而responseXML则是一个XML文档对象,只用于返回数据中包含有text/xml内容的情况。因此,为取得info.txt中的文本,必须进行以下调用:IXPUB技术博客,`g?^1i ~ \1YRW

M1Z kMlB2n1ph0var sData = oXmlHttp.responseText;
%IUp0z"[E C:S GI@1R0注意,只有文件存在,并且没有错误发生时,才会返回info.txt中相应的文本。但如果info.txt文件不存在,则responseText将返回一个服务器404错误信息,幸好,还有一个方法可以确定是否有错误发生。IXPUB技术博客4ex R*r;kW|0b~
属性status中包含有响应返回的HTTP状态代码,而statusText是关于状态的文本描述(如:OK或者Not Found)。使用这两个属性,你可以确认所接收到的数据是实际想要的数据,或告诉用户为什么数据没有收到:
8r8XU(LEkn0IXPUB技术博客+bk7YI)n(A{!f
if (oXmlHttp.status == 200) { alert("返回的数据为:" + oXmlHttp.responseText; } else { alert("错误为:" + oXmlHttp.statusText; }
O T/L,OBc/H4xlz2KYe0通常来说,必须保证响应的status(状态码)为200IXPUB技术博客"F&V}IK@NM#`
IXPUB技术博客7z5fG)` x
,以表明请求完全成功,有时即使服务端有错误发生,属性readState仍会被设为4,所以仅仅检查此值还是不够的。在这个例子中,如果status值为200,将显示responseText的内容,否则,就显示一个出错信息。
C)?e^/X Rb!I0在Opera中,仍未实现statusText属性,在其他的浏览器上,有时还会返回一个不精确的描述信息,所以,不能仅仅依靠statusText来判断是否有错误发生。 正如前面所提到的,我们还可以访问到响应头部信息,在此使用getResponseHeader()方法,并传递给它一个你想要的头部名,来得到一个特定的头部值。而当中最有用的一个响应头部信息是Content-Type,它将告诉你正在被发送的数据类型:IXPUB技术博客!Q8h-dx.Zk^

lRSl`GN)\0var sContentType = oXmlHttp.getResponseHeader("Content-Type"); if (sContentType == "text/xml") { alert("接收到的内容为XML!"); } else if (sContentType == "text/plain") { alert("接收到的内容为纯文本!"); } else { alert("无法判断的内容!"); }IXPUB技术博客&N Q&XV%Y\&l7~@
这段代码检查响应中的内容类型,并用一个对话框来显示返回的数据类型。一般来说,从服务器接收到的数据,只会是XML(内容类型为text/xml)或者纯文本(内容类型为text/plain),因为这些类型最容易与JavaScript协同工作。IXPUB技术博客(J4Cr$l,M z0J
如果你更想看到从服务器返回的所有头部信息,那么可用getAllResponseHeaders()方法,其将返回一个包含所有头部信息的字符串,由换行符(JavaScript中表示为\n)或回车换行符(JavaScript中表示为\r\n)进行分隔,可以像如下所示对个别的头部信息进行处理:
WvKC3g e0IXPUB技术博客B }4M[K#`OX
var sHeaders = oXmlHttp.getAllResponseHeaders(); var aHeaders = sHeaders.split(/\r?\n/); for (var i=0; i < aHeaders.length; i++) { alert(aHeaders); }IXPUB技术博客"t"eJ;Tl J
这个例子使用JavaScript的split()字符串方法,并传递给它一个正则表达式(由它来匹配回车换行或仅仅是换行),把包含头部信息的字符串分离到一个数组中。现在,你可以遍历这些头部信息,做一些想做的事了。别忘了,数组aHeaders中每一个字符串的形式为:(头部名:值)。IXPUB技术博客_Z*]!Jk1[
在头部信息被发送出去之前,也可以对头部信息进行修改。你也许想要表明你正在发送的数据内容是什么类型,或者你想要发送一些服务器可能需要与请求一并处理的其他额外数据,要这样做的话,在调用send()之前,必须先调用setRequestHeader()方法:IXPUB技术博客F x'C ?BfPX\

lTg5h)^,m0IXPUB技术博客+D,hq T7P
var XmlHttp = zXmlHttp.createRequest(); oXmlHttp.open("get", "info.txt", true); oXmlHttp.onreadystatechange = function () { if (oXmlHttp.readyState == 4) { alert("得到响应!"); } }; oXmlHttp.setRequestHeader("myheader", "myvalue"); oXmlHttp.send(null);IXPUB技术博客1C kxP(H!g
在以上代码中,一个名为myheader的头部名在发送之前,被加入到请求中,在此是以myheader:myvalue的形式加入到默认头部信息中的。
Lp,~+kn%r4A:F&T0由此,就可以处理异步请求了,而异步请求对大多数情况都是适用的。发送异步请求意味着你不需要分配一个onreadstatechange事件处理函数,因为当send()方法返回时,就会收到响应。所以,代码可以这样写:
9f9Z!Ok)bwt d0var XmlHttp = zXmlHttp.createRequest(); oXmlHttp.open("get", "info.txt", false); oXmlHttp.send(null); if (oXmlHttp.status == 200) { alert("返回的数据为:" + oXmlHttp.responseText; } else { alert("错误为:" + oXmlHttp.statusText; }
$aG1xICsbVx0异步发送请求(设置open()的第三个参数为false),可以使你在调用send()之后,就立即开始评估响应,如果你想让用户等待响应,或者你只期望收到一小点数据(例如,小于1k),那就另当别论了,但在一般数据量或大数据量的情况中,最好都使用异步调用。

TAG:

我们都是只有一只翅膀的天使,无法割舍彼此 grjboy30 发布于2008-02-10 23:05:01
--><?php header("Content-Type: text/plain"); $sID = $_GET["id"]; $sInfo = ""; $sDBServer = "your.databaser.server"; $sDBName = "your_db_name"; $sDBUsername = "your_db_username"; $sDBPassword = "your_db_password"; $sQuery = "Select * from Customers where CustomerId=".$sID; $oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword); @mysql_select_db($sDBName) or $sInfo="不能打开数据库!"; if($oResult = mysql_query($sQuery) and mysql_num_rows($oResult) > 0) { $aValues = mysql_fetch_array($oResult,MYSQL_ASSOC); $sInfo = $aValues['Name']."<br />".$aValues['Address']."<br />". $aValues['City']."<br />".$aValues['State']."<br />". $aValues['Zip']."<br /><br />Phone: ".$aValues['Phone']."<br />". "<a href=\"[email=".$aValues[]mailto:".$aValues['E-mail']."\[/email]">". $aValues['E-mail']."</a>"; } else{ $sInfo = "ID为$sID的客户不存在!"; } mysql_close($oLink); echo $sInfo; ?>
z9ni,pg"|blog.ixpub.net正如大家所看到的,在页面中没有明显可见的HTML或JavaScript调用,所有的主逻辑保持一致,但加入了两行额外的PHP代码。第一处是在开始处,也就是调用header函数来设置页面的内容类型,即使页面将返回一个HTML代码段,最好也把内容类型设为纯文本(text/plain),因为它不完全是一个HTML页面(因此也不能验证为HTML)。在发送非HTML页面给浏览器时,都应设置好内容类型;第二处是在结尾处,使用echo命令把$sinfo输出到流中。 IXPUB技术博客"~D(Zp        @@(o^`
IXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。,w[c3WZ6^2{*T
病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理YH G#L]!E
在主页中——也就是用户访问的第一页,基本设置如下所示:
7l2t"h
D;WIXPUB技术博客
<p>Enter customer ID number to retrieve information:</p> <p>Customer ID: <input type="text" id="txtCustomerId" value="" /></p> <p><input type="button" value="Get Customer Info"  /></p> <div id="divCustomerInfo"></div>
t{4l-REV j!R病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理而之前的requestCustomerInfo()函数创建了一个隐藏的iframe,但现在作出修改以使用XMLHttp:
(aCHR3kc\z病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理function requestCustomerInfo() ...{ var sId = document.getElementById("txtCustomerId").value; var oXmlHttp = zXmlHttp.createRequest(); oXmlHttp.open("get", "GetCustomerData.php?id=" + sId, true); oXmlHttp.onreadystatechange = function () ...{ if (oXmlHttp.readyState == 4) ...{ if (oXmlHttp.status == 200) ...{ displayCustomerInfo(oXmlHttp.responseText); } else ...{ displayCustomerInfo("错误为:" +oXmlHttp.statusText); } } }; oXmlHttp.send(null); } IXPUB技术博客%SoEu%xD;F ^
请注意,函数以同样的方式开始,即取回用户输入的ID,接着,使用zXml库创建了一个XMLHttp对象,调用open()方法之后,为GetCustomerData.php指定了一个异步的GET请求(其在查询字符串中加入了前述的ID);之后,赋值了一个事件处理函数,除了检查readyState是否为4之外,还要检查请求的status。如果请求成功(status为200),将传递响应体(通过responseText得到)调用displayCustomerInfo()函数;如果有错误发生(status不是200),将会把错误信息传递给displayCustomerInfo()。 -V Ob,X2Z8[TP
在此例和隐藏框架(frame/iframe)示例之间,有几个不同点。首先,在主页之外,不需要JavaScript代码,这点尤其重要,因为当你需要把代码保存在两个不同的地方时,有可能造成兼容性问题,如在基于框架的示例中,是依靠显示页与隐藏框架中各自不同的脚本来相互通讯的,而通过修改GetCustomerInfo.php来返回感兴趣的数据,就已经消除了用JavaScript在两者之间调用的潜在问题。第二个不同之处在于,现在更容易知道,在执行请求期间,是否有问题发生;在前一个例子中,没有在请求处理过程中识别和响应服务器错误的机制,而使用XMLHttp后,所有的服务器错误信息都将展现给作为开发者的你,使你能够只把有意义的错误信息回馈给用户,在大多数时候,对于页内的HTTP请求,XMLHttp都是比隐藏框架更“优雅”的解决方案。
Pwgp)j:J0wIXPUB技术博客现在,在看过XMLHttp是怎样简化GET请求之后,再来看一下POST请求吧。首先,需要对SaveCustomer.php作出与GetCustomerInfo.php同样的修改,也就是说需要删除无关的HTML与JavaScript代码,加入内容类型信息,并输出相应的文本:
I        OT#v2?MIXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。<?php header("Content-Type: text/plain"); $sName = $_POST["txtName"]; $sAddress = $_POST["txtAddress"]; $sCity = $_POST["txtCity"]; $sState = $_POST["txtState"]; $sZipCode = $_POST["txtZipCode"]; $sPhone = $_POST["txtPhone"]; $sEmail = $_POST["txtEmail"]; $sStatus = ""; $sDBServer = "your.database.server"; $sDBName = "your_db_name"; $sDBUsername = "your_db_username"; $sDBPassword = "your_db_password"; $sSQL = "Insert into Customers (Name,Address,City,State,Zip,Phone,`E-mail`) ". " values ('$sName','$sAddress','$sCity','$sState', '$sZipCode'". ", '$sPhone', '$sEmail')"; $oLink = mysql_connect($sDBServer,$sDBUsername,$sDBPassword); @mysql_select_db($sDBName) or $sStatus = "不能打开数据库!"; if($oResult = mysql_query($sSQL)) ...{ $sStatus = "加入的客户;客户ID为".mysql_insert_id(); } else ...{ $sStatus = "在插入操作时有错误发生;客户未保存!"; } mysql_close($oLink); echo $sStatus; ?> "p
Qr:j*[
M

blog.ixpub.netu|6v`nc fkp
--><form method="post" action="SaveCustomer.php" > <p>Enter customer information to be saved:</p> <p>Customer Name: <input type="text" name="txtName" value="" /><br /> Address: <input type="text" name="txtAddress" value="" /><br /> City: <input type="text" name="txtCity" value="" /><br /> State: <input type="text" name="txtState" value="" /><br /> Zip Code: <input type="text" name="txtZipCode" value="" /><br /> Phone: <input type="text" name="txtPhone" value="" /><br /> E-mail: <input type="text" name="txtEmail" value="" /></p> <p><input type="submit" value="Save Customer Info" /></p> </form> <div id="divStatus"></div> blog.ixpub.net/g2h$R-V4i0csjEe

$}gw)O9Iz#JIXPUB技术博客你可能已经留意到了,onsubmit事件处理函数现在改为调用sendRequest()函数(尽管事件处理函数仍返回false以防止实际的页面提交)。这个方法首先聚集POST请求所需的数据,接着创建XMLHttp对象并发送出去。数据必须作为一个查询字符串,以如下格式的发送:
}]?
c5u        L]4D
IXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。 \ G_yvs

!S#Fkqz0rname1=value1&name2=value2&name3=value3 IXPUB技术博客7dA"l&g#qW@
为防止传输过程中出现数据丢失,其中每一个参数的name和value必须经过URL编码,而JavaScript提供了一个名为encodeURLComponent()的内置函数,可用于进行这种形式的编码。为创建这个字符串,还需要遍历所有的页面字段,取出name和value并进行编码,如下的getRequestBody()函数便是进行这种操作: 病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理*P.?XG9U2m
G%n

function getRequestBody(oForm) { var aParams = new Array(); for (var i=0 ; i < oForm.elements.length; i++){ var sParam = encodeURIComponent(oForm.elements.name); sParam += "="; sParam += encodeURIComponent(oForm.elements.value); aParams.push(sParam); } return aParams.join("&"); } 病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理 A.{bl(N$n
函数假定你用一个页面的引用作为参数,创建了一个数组(aParams)以保存每一对单独的name-value,接下来,遍历完页面中的元素,构建一个字符串,并把它存入sParam,而sParam将会在随后添加到数组中;这样做的目的是为了防止多个字符串之间的级联,而其将会在某些浏览器中导致代码执行缓慢。最后一步是在数组上调用join(),并传递给它一个与符号(&),通过这个与符号,有效地结合了所有的name-value,以正确的格式创建出一个单一的字符串。 病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理x+D?1` m9\7e

yq}8v Q
W$Hv|6^IXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。
在大多数浏览器中,字符串的级联是一个开销巨大的处理过程,因为字符串是不可变的,这意味着一旦创建,它们不能改变自身的值,因此,级联两个字符串将导致创建一个新的字符串,并把这两个字符串的内容复制进去,而此过程的周而复始将严重拖慢系统的速度,因为这个原因,最好少用字符串的级联操作,而使用数组的join()方法去处理较长的字符串。以下的sendRequest()函数调用getRequestBody()并设置好请求: blog.ixpub.net%j's,y{v@9{
function sendRequest() { var oForm = document.forms[0]; var sBody = getRequestBody(oForm); var oXmlHttp = zXmlHttp.createRequest(); oXmlHttp.open("post", oForm.action, true); oXmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); oXmlHttp.onreadystatechange = function (){ if (oXmlHttp.readyState == 4){ if (oXmlHttp.status == 200){ saveResult(oXmlHttp.responseText); } else { saveResult("An error occurred: " + oXmlHttp.statusText); } } }; oXmlHttp.send(sBody); } blog.ixpub.net9vB|/aUQ
如同前一个示例一样,此函数中的第一步是得到对页面的一个引用,并把它存到一个变量中(oForm);接着,生成请求体存在sBody中,并创建和设置好XMLHttp对象。注意此处open()中现在已用post代替了get,且第二个参数设置为oForm.action(正是它,所以此脚本才可用于多页面中)。你可能也注意到了,此处也设置了一个请求信息头,当一个页面从浏览器post到服务器时,由它将请求的内容类型设置为application/x-www-form-urlencoded,而大多数的服务器端语言都会查找这条编码,以正确解析传入的POST数据,所以设置它是非常重要的。 P"MxAek^
而onreadystatechange事件处理函数与GET示例中的是非常类似的,唯一的不同是调用saveResult()而不是displayCustomerInfo();上例的最后一行非常重要,当字符串sBody传递给send()时,它也将成为请求体的一部分,这实际上模仿了浏览器行为,因此服务器端逻辑也会工作正常。 病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理
[y8@
KC
VCbtt

XMLHttp的有利与不利之处
iU.Sy![EBIXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。毫无疑问,用于“客户——服务器”通讯的XMLHttp,相比隐藏框架,优势非常明显,编写的代码更加“干净”,另外也不像隐藏框架那样使用大量的回调函数,现在,代码表现的意图清晰易懂。还可以像访问HTTP状态码一样,访问到请求和响应头部信息,让你自己来判断请求是否成功。
)tg.k/I"?0V病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理不利的方面是,不同于隐藏框架,现在没有函数调用的浏览器历史记录,而“前进”与“后退”按钮也不再与XMLHttp请求有关联,等于是抛弃了这两个按钮,正因为此,许多的Ajax程序都会混合使用XMLHttp与隐藏框架来创建真正实用的界面。 IXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。        F(B`kv0@yo
另一个不利方面——在此只关系到Internet Explorer,现在是必须依靠ActiveX控件,如果用户设置了一个特定的安全区域,不允许执行ActiveX控件,你就不可能访问到XMLHttp对象,如果这样,那就不得不使用隐藏框架了。
2gua的个人空间 2gua 发布于2008-02-11 13:23:55
看得好乱啊咯,代码没换行。。。
我们都是只有一只翅膀的天使,无法割舍彼此 grjboy30 发布于2008-02-11 13:26:51
回复 #3 lujunxiang 的帖子
我也不想  编辑的时候都可以看见的  但是谁知道上传后就变格式了啊
u2O%IY9RyE0R5LIXPUB技术社区,交流各种操作系统、服务器、网管技术,网管资源下载,企业网管的天堂,网吧网管的乐园。
!j~c7w}2O}"G]2^&i病毒,木马,下载,服务器,操作系统,数据库,路由器,交换机,防火墙,网络工程,布线,网络编程,游戏,网络基础,硬件,网页制作,网管,网管论坛,网管软件,网管下载,网管技术,网络技术,网管社区,网管博客,企业网管,网吧网管,网吧管理请斑竹协助修改 !~
BlueWoWo的网络小窝 BlueWoWo 发布于2008-02-25 17:54:58
回复 #2 grjboy30 的帖子
哈哈,快下班了,先收藏了再说!
默默飞翔的个人空间 默默飞翔 发布于2008-02-28 11:39:39
小锅也是搞JAVA的?
我们都是只有一只翅膀的天使,无法割舍彼此 grjboy30 发布于2008-02-28 21:21:18
小飞 做虾米的 教教偶
zhxnc发布于2008-03-21 21:02:59
近来溜达溜达顺便支持下楼主
我来说两句

(可选)