现在的位置: 首页 > 综合 > 正文

Rails进行REST开发

2012年04月22日 ⁄ 综合 ⁄ 共 14058字 ⁄ 字号 评论关闭

应用Rails进行REST开发

这篇文档翻译自《RESTful Rails Development》,只为了交流和经验分享而用.

前言

    Http协议除了 get 和 post ,还可以做更多的事情,一直以来,很多的开发人员都忘了这一点。

    但是,如果你知道浏览器其实只支持 get 和 post, 那么你就不会感到惊奇了。

    get 和 post 是http请求从客户端传到服务器端的两个方法。除了这两个,http协议还知道 put和 delete 方法,这两个方法告诉服务器创建或者删除一个WEB的资源。

    这个教程的目的,就是扩展开发人员的视线,去了解http协议的 put 和 delete 方法。我们常说的 REST 这个术语,精华就是 http 协议中 get, post, put, delete 四个方法。Rails 从1.2版本开始,就支持 REST 这个技术了。

    这个教程一开始会简短的介绍REST的背景和概念,接着介绍为什么要开发 REST风格的Rails应用。

    使用 scaffolding,这是可以帮助我们产生 controller 和 model 细节的工具,对我们的进行REST应用的开发很有帮助。REST的应用中作用非常重大的路由技术,将会在接下来的章节介绍。“嵌套的资源”这个章节,会介绍一下REST的高级应用,告诉大家资源如何以 父 - 子(继承关系)的关系组合在一起,同时不违反的REST 风格的路由设计。教程的后面,还会介绍一些REST的内容,AJAX,REST风格的应用的测试方法,还有“ActiveResource”-- REST的客户端部分。

    在我们开始之前,再啰嗦最后一句:要读这个教程,最少你要懂一点Rails开发的基本知识,否则的话,先去学习学习吧:)

 

1.1 什么是REST?

REST这个术语,是Roy Fielding在Ph.D.论文中提出来的,它的全称是“Representational State Transfer.”

REST描述了这么一个架构:利用标准的http 协议中的 get, post, put, delete 来请求和操作网络上的资源。

    在REST中,资源的意思就是一个 基于URL实体,客户端可以通过 http协议来和它进行交互。这个资源可以用各种形式来展示给客户端,如 HTML,XML,RSS,主要依赖于客户端的调用方式。并不像以往的Rails开发那样,用REST方式,一个 url 不是指定一个 model 或者 action, 一个 url 仅仅是资源的本身而已。

 

 

 

   

 

 

 

 

    在图1.1中,三个资源的URL的开头都是相同的,通过后面的不同的数字来保证这三个是不同的资源。

注意:URL并没有表明要对这三个资源进行什么操作。

 

    在 Rails 应用中,一个资源是由 controller 和 model 组成的。那么从技术的角度来看,图 1.1中的3个资源"project",就是针对3个请求,而表现出来的 Project model(也就是ActiveRecord类了)的3个实例形式。

 

1.2 为什么使用REST?

    问得好!我们已经使用MVC模式开发Rails应用2年了,为什么要使用REST?

    REST所带给我们的,是Rails 的理论上的提升,下面的一些特性,将会使我们清晰地了解这一点。

   

    a) 简明的Url. REST 风格的URL是为了定位一个资源,而并不是调用一个action. 在REST中,URL经常以这种方式出现:先是controller的名称,然后是资源的id。请求的操作隐藏在URL里,并且通过 http 协议来表示(get, post, put, delete 四个动作)。

   b) 传回给客户端不同格式的内容。我们通过一种方式来编写 controller,其中的action应该都可以返回给客户端不同的格式的结果。对于同一个 action, 即可以返回给客户端 html, 也可以返回给xml,也可以返回给 RSS,这依赖于客户端想要什么。REST应用就好像变得能处理客户端好多的命令。

    c) 更少的代码。因为一个action 可以返回客户端的各种需要格式,这就减少了(DRY don't repeat yourself),这也就让 controller 里的代码减少了。

 

    d) 面向 CRUD 的controller. 所谓CRUD 就是Create,Retrieve,Update,Delete.

       controller 和 model 融合到一起,每个controller都会响应某个model的操作。

    e) 让系统设计更简单。REST风格的开发方式,会使系统结构更加清晰,而且容易维护。

   

    接下来的章节中,我们会用例子来逐步的让您明白上面所描述的这些特性。

 

1.3 有什么新玩意?

 

    如果你觉得 REST 让你之前所有的开发经验变得毫无作用,别担心,那是不可能的~ 因为REST仍然是基于 MVC 风格的。从技术角度来看,REST中的“新玩意”可以归纳为以下几点:

    a) 在 controller 中的 respond_to 的用法。

    b) 对于 link 和 form 的新的 helper 方法。

    c) 在 controller redirect 中的 url 方法。

    d) 在 routes.rb 文件中,新的路由定义方法。

 

一旦你了解了 REST ,而且经常使用它,那么自然而然,你就会设计一个REST的应用了!

 

 

1.4 准备

      接下来,我们要以我们的一本书《RapidWeb Development mit Ruby on Rails》中的一个例子“项目管理应用”来描述Rails的REST方面的特性。我们并不会编写整个应用,但是我们会使用相同的技术去描述REST的内容。

      下面,我们开始吧!首先创建一个rails的应用:

> rails ontrack

然后,我们来创建 开发 和 测试 的数据库。

> mysql -u rails -p

Enter password: *****

mysql> create database ontrack_development;

mysql> create database ontrack_test;

mysql> quit

 

 

1.4.1 Rails 1.2

 

      假设并不是所有的人都希望在系统里安装rails 1.2――因为那样的话,所有的应用都必须跑在rails 1.2 之下了。所以我们建议只是让这个例子使用 rails 1.2。参考文档,rails 1.2 的 tag 是 rel _1-2-1。所以我们使用 rake 命令来让 ontrack 项目使用 1.2 的版本。

> cd ontrack

> rake rails:freeze:edge TAG=rel_1-2-1

 

1.5 Resource Scaffolding

 

      基于REST的rails 应用可以使用新的脚手架(scaffold)命令 scaffold_resource 来方便的创建。可以传递给生成器这样一些参数:在本例中,就是资源的名字 “project”, 和 这个model 的字段名字以及类型。字段的类型是必须的,因为我们要使用migration,以及在视图中显示这些字段。

 

> cd ontrack

> ruby script/generate scaffold_resource project name:string desc:text

exists app/models/

exists app/controllers/

exists app/helpers/

create app/views/projects

exists test/functional/

exists test/unit/

 

create app/views/projects/index.rhtml

create app/views/projects/show.rhtml

create app/views/projects/new.rhtml

create app/views/projects/edit.rhtml

create app/views/layouts/projects.rhtml

create public/stylesheets/scaffold.css

create app/models/project.rb

create app/controllers/projects_controller.rb

create test/functional/projects_controller_test.rb

create app/helpers/projects_helper.rb

create test/unit/project_test.rb

create test/fixtures/projects.yml

create db/migrate

create db/migrate/001_create_projects.rb

route map.resources :projects

 

      生成器不仅生成了 model, controller, view, 还生成了完整的migration 脚本,以及在 routes.rb 里生成了一个映射:map.resources :projects   ,对于新生成controller来说,这是用于REST方面的最后一个映射项目。我们现在先不深入研究 routes.rb, 还是先一步一步地看看我们刚才用生成器说生成的东西吧!

1.6 Model

      就好像我们之前提过的,在Rails 里,REST的资源是由 controller 和 model来组成的。所谓model,就是一个普通的 ActiveReocrd类,继承自 ActiveRecord::Base:

 

class Project < ActiveRecord::Base

end

 

所以,对于model 这部分来说,没什么新鲜的东西。不过,别忘了创建这个表:

> rake db:migrate

 

1.7 Controller

 

      生成的controller “ProjectsController” 是一个具有 CRUD行为的controller,它可以操作 “Project” 这个资源(还记得刚才创建的 projects 表吧?)。

      这就表明,一个controller 属于某一个特定的资源,并且具有一些符合标准的动作来执行 CRUD的操作。例如:

 

Listing 1.1: ontrack/app/controllers/projects controller.rb

class ProjectsController < ApplicationController

# GET /projects

# GET /projects.xml

def index...

# GET /projects/1

# GET /projects/1.xml

def show...

# GET /projects/new

def new...

# GET /projects/1;edit

def edit...

# POST /projects

# POST /projects.xml

def create...

# PUT /projects/1

# PUT /projects/1.xml

def update...

end

# DELETE /projects/1

# DELETE /projects/1.xml

def destroy...

end

 

      如果我们来看看刚才生成的ProjectController,会发现其实并没有什么新鲜的东西,无非也就是这么一些操作:创建(create),读取(retrieve),更新(update),删除(delete)这些操作。需要强调注意:这些操作都是针对Project 这个资源的。Controller 和 Action 看起来都很普通,但是仔细看一下,每个 Action 都会有一些注释,这些注释表明了 url 和 http 所使用的动作。这些注释所体现的,就是 REST风格的 URL。接下来的章节,我们会仔细分析一下这些URL的内容。

 

1.7.1 REST 风格的 URL

 

      我们之前已经十分强调过,REST风格的URL,并不像以往的Rails 应用一样,是由 controller/action/model id 所组成的,例如 /projects/show/1 。相反,REST风格的URL仅仅由 controller 和资源的id 所组成,例如 /projects/1。

      注意:我们一直再强调“资源”这个词。

      URL中没有了 action,我们也就看不到该对资源进行什么操作了。“/projects/1”这个URL到底应该是显示一个资源,还是应该删除一个资源?答案来自我们之前提到的 http 协议的4个动作。

      下面的列表可以展示 http 协议的4个动作是如何和REST风格的URL所关联的,并且什么样的组合,对应什么样的action:

     

     

      我们可以看出来,除了 POST 动作,其他三个URL都是相同的,原因很简单,因为要创建的那个资源还不存在呢。既然有三个URL都是相同的,那该怎么区分呢?其实是 http 协议的4个动作决定该调用哪个action。我们没有使用action,这就使得 我们不会写多余的 URL 和资源了。

      现在我们只需要2个URL: /projects/1 和 /projects ,如果是传统的方式,我们需要 /projects/new , /projects/show/1 , /projects/delete/1, /projects/update/1 4个URL.

 

      有一点需要注意,之前我们也提到过,浏览器只能理解 POST 和 Get 两个动作,所以,当输入 http://localhost:3000/projects/1 的时候,会调用 show 这个Action.

所以,Rails 提供了一个辅助的方案来声称一个用于删除一个资源的链接:Delete 这个动作被放在一个隐藏的提交字段里(hidden field)提交给服务器;在创建新的资源的时候,也是适用相同的方法。这些内容都会在以下的章节里介绍。

 

1.7.2 Action 中使用 respond_to

 

      我们已经知道,我们可以通过一个指定资源id的URL和http协议的动作的组合,来调用一个 action。这使得一个URL看起来非常简洁:一个URL就指定了哪个资源要被操作,而不像以往那样去指定一个Action。

      那么到底有什么样的需求,会让我们去使用这种风格的URL呢?

      一个 REST的action可以应付不同的客户端所需要的不同的信息格式。对于一个WEB迎来说,典型的客户端当然就是浏览器了,但是别忘了,对于一个web service 来说,它需要的则是 xml 格式的信息;对于一个RSS阅读器来说,它需要的则是 RSS格式的信息。

      对于客户端的请求,我们已经使用 scaffold 生成器生成了 CRUD 4个方法来处理。下面的代码片断展示了 “show” 这个 action 中 respond_to 的使用方法:

 

Listing 1.2: ontrack/app/controllers/projects controller.rb

# GET /projects/1

# GET /projects/1.xml

def show

@project = Project.find(params[:id])

respond_to do |format|

format.html # show.rhtml

format.xml { render :xml => @project.to_xml }

end

end

 

respond_to 方法是用了代码块(block)技术,在这个例子中,代码块(block)部分处理了2种格式的信息:html 和 xml。针对客户端不同的请求,会执行代码块(block)中不同的部分。例如如果客户端请求的是html 信息,那么会执行 “format.html”,如果客户端请求的是xml 信息,那么会执行“format.xml”部分。

      如果 format.html 代码块里是空的,那么默认就显示 show.rhtml。

      控制 respond_to 可以通过2种方式:一是在 http-header 里面;二是在URL后面追加一些东西,也就是改变URL的样式。

      这两种方式我们都会介绍的。

 

1.7.3 http-header 中使用 accept 变量。

 

      我们先来看看控制respond_to 的第一种方式。设置 http-header 的方式很简单,你可以使用一个工具“curl”。好,我们首先启动 webrick:

 

 

> ruby script/server webrick

=> Booting WEBrick...

=> Rails application started on http://0.0.0.0:3000

=> Ctrl-C to shutdown server; call with --help for options

[2006-12-30 18:10:50] INFO WEBrick 1.3.1

[2006-12-30 18:10:50] INFO ruby 1.8.4 (2005-12-24) [i686-darwin8.6.1]

[2006-12-30 18:10:50] INFO WEBrick::HTTPServer#start: pid=4709 port=3000

 

在浏览器中会看到如下的效果:

 

然后,我们使用 curl 这个工具来请求 project 这个资源,并且想要得到xml 格式的信息。

 

> curl -H "Accept: application/xml" \

-i -X GET http://localhost:3000/projects/1

=>

HTTP/1.1 200 OK

Connection: close

Date: Sat, 30 Dec 2006 17:31:50 GMT

Set-Cookie: _session_id=4545eabd9d1bebde367ecbadf015bcc2; path=/

Status: 200 OK

Cache-Control: no-cache

Server: Mongrel 0.3.13.4

Content-Type: application/xml; charset=utf-8

Content-Length: 160

<?xml version="1.0" encoding="UTF-8"?>

<project>

<desc>Future of Online Marketing</desc>

<id type="integer">1</id>

<name>Wunderloop</name>

</project>

 

Rails的处理器调用了 show 这个action。因为我们已经在 http-header 里通过 “Accept: application/xml”指定了需要xml的信息,所以,“show” 这个 action 就调用了 format.xml,然后返回给我们xml的信息。

 

Curl 这个工具不仅可以方便的测试返回不同格式的情况,我们还可以用它来测试一些浏览器所不支持的命令,例如,我们要删除 id = 1 的这个资源:

 

 

> curl -X DELETE http://localhost:3000/projects/1

=>

<html><body>You are being

<a href="http://localhost:3000/projects">redirected</a>.

</body></html>

 

这次发送的请求使用了http协议的 DELETE方法。Rails 得到了http的这个动作,然后就会调用 destroy 方法。注意 URL看起来没什么不同,不同的是 http 包含的动作。

 

1.7.4 改变URL的样式

 

      第二种控制 action 返回不同格式的信息的方法,就是改变URL的样式。假设我们没有删除 id=1 这个资源project,那么我们通过以下这个方式来在浏览器里显示这个资源:

http://localhost:3000/projects/1.xml

 

这里MAC的用户要注意,这种情况下 firefox 表现的不错,但是Safari 就差些,因为Safari 会忽略xml 格式的信息。甚至 firefox 会把xml 显示的非常漂亮!

 

      到这里,我们已经知道一个controller 和 URL是如何工作的,在接下来的2个章节里,我们会学习如何在 controller 和 view 里使用和构造这种REST风格的URL。

 

 

1.8 REST风格的URL和View

 

      View是系统界面和用户之间的一个表现,用户通过链接和按钮来和系统进行交互。传统上Rails的开发人员使用 link_to 这个helper 方法来构造一个链接,这个方法需要一个 hashmap, hashmap 由 controller 和 action 组成;此外,还可以传递一些其他的参数。例如:

link_to :controller => "projects", :action => "show", :id => project

=>

<a href="/projects/show/1">Show</a>

 

      我们马上就意思到,这个link_to 方法并不能很好的用于我们的REST思想:REST不会在URL里包含action。

      那么重要的就是通过链接和按钮,我们要把 http 协议的4个动作和URL一起传递给服务器。

      所以,我们会看到Rails的改进之处:我们仍然使用 link_to 去创建链接,但是,我们不会再使用hashmap, 而是使用一个“path”的方法。首先用一个例子,来说明如何创建一个链接去调用 controller 的 show action。请看好,我们不会再使用 controller, action, 和 id 了:

 

 

link_to "Show", project_path(project)

=>

<a href="/projects/1">Show</a>

     

      关于 path 和下面要说的url 这两类helper方法,请大家千万不要疑惑他们是从哪来的。我们可以这么认为,是Rails 动态地创造了他们。我们只要使用就可以了!

     

传统的link_to 所生成的链接中包含了controller和action,相对比,使用新的 “project_path” 所创建的链接,只包含了controller 和 资源的id – 毫无疑问,这是一个 REST风格的URL。因为链接默认的是一个“Get”请求,Rails 能够知道这一点,所以就会去调用 show action。

对于每一个资源,rails 都会有7个标准的 path 方法,这些可以从表1.2中看到。

每一个path 方法都关联一个 http 协议的动作,一些请求(例如show, create),可以通过http协议的 Get 或 Post 传递给服务器;但是有一些请求,如 update,delete,则需要一些其他的方式(如使用隐藏的变量)传递给服务器,因为浏览器并不知道 PUT和DELETE动作。接下来的章节我们会仔细的介绍。

 

      进一步看看这个表,我们也会发现4个http动作,并不足以包含全部的CRUD操作。前2个方法使用Get的方式会工作的很好,但是对于 new_project_path 和 edit_project_path 就不同了。

 

1.8.1 New 和 Edit

 

      用户如果点击一个“新建”链接,那么会使用Get动作来对服务器发送一个请求。下面的例子表明,生成的链接是由 controller 和一个“new”action 组成的。

link_to "New", new_project_path

=>

<a href="/projects/new">New</a>

 

这是对REST思想的一种破坏?或许乍看之下确实如此。但是如果你仔细看,那么一切都会清晰,“new”并不是一个CURD的action,它更像一个建立一个新的资源之前的准备的动作。真正的CRUD中的create被调用,是在新的form被提交的以后才执行的。这个链接当然也没有资源的id—因为资源还没有被创建。一个链接如果没有资源的id,那么就不应该被称为REST的URL,因为REST的URL总是会指定一个资源的id。所以,这个 “new” action 仅仅因该用来显示一个新的form的页面而已。

对于 edit_project_path ,也是同样的道理。它引用了一个资源,但是仅仅是在调用 update action 之前的准备工作。真正的update action 是在页面被提交以后才执行的。edit_project_path 和 new_project_path 唯一的区别就是前者需要使用一个资源的id。按照REST的规则,资源的id放在controller 的后面:/project/1 。但是如果仅仅使用Get 动作来提交这个URL,那么Rails将会认为你要调用的是show action。为了防止这一点,edit_project_path 方法扩展了一下生成的链接,例如:

 

link_to "Edit", edit_project_path(project)

=>

<a href="/projects/1;edit">Edit</a>

 

这样,我们就能理解为什么允许 edit_project_path 和 new_project_path 生成的链接里带有 action 了,因为他们两个都不是REST的 CRUD URL,他们仅仅是准备工作。还有其它的一些URL和这两个很相似,我们会在后面的章节介绍。

 

1.8.2 在 form 中使用 path 方法:Create 和 Update

 

      传统的方式上,我们使用 form_tag 或 form_for 来创建一个form:

<% form_for :project, @project, :url => { :action => "create" } do |f| %>

...

<% end %>

 

      在REST应用中,这个 :url hashmap 会被 path 方法给取代:

project_path” 创建新的资源所使用的form

project_path(:id)”编辑一个资源所使用的form

 

a) 创建资源所使用的form

 

form 使用 post 动作向服务器提交信息,“project_path”方法并不会有资源id作为参数,这样,生成的URL就应该是“/projects”这个样子。当提交到服务器以后,就会调用 create action。

form_for(:project, :url => projects_path) do |f| ...

=>

<form action="/projects" method="post">

 

b) 编辑一个资源所使用的form

 

      按照REST的思想,一个更新的操作是使用http协议的PUT动作来发送的。但是,正如我们所知道的,浏览器只明白 Post和Get动作。解决的办法就是使用 form_for 方法里的 :html 参数。

 

 

form_for(:project, :url => project_path(@project),

:html => { :method => :put }) do |f| ...

=>

<form action="/projects/1" method="post">

<div style="margin:0;padding:0">

<input name="_method" type="hidden" value="put" />

</div>

 

Rails 生成了一个隐藏的字段来代替http的put 动作。提交以后,Rails 会检查这个变量,然后判断是否去调用update方法。

 

1.8.3 删除

 

      恐怕我们已经发觉了,用于显示和删除一个资源,所使用的path方法都一样:

link_to "Show", project_path(project)

link_to "Destroy", project_path(project), :method => :delete

 

唯一的不同就是 删除的时候,使用了一个变量 :method,用它来表示http的DELETE动作。因为浏览器不支持DELETE动作,所以,Rails 会生成一些javascript来解决这个问题:

 

link_to "Destroy", project_path(project), :method => :delete

=>

<a href="/projects/1"

onclick="var f = document.createElement(’form’);

f.style.display = ’none’; this.parentNode.appendChild(f);

f.method = ’POST’; f.action = this.href;

var m = document.createElement(’input’);

m.setAttribute(’type’, ’hidden’);

m.setAttribute(’name’, ’_method’);

m.setAttribute(’value’, ’delete’); f.appendChild(m);f.submit();

return false;">Destroy</a>

 

这段javascript 会生成一个form,把 http 的DELETE动作放在隐藏变量里传递给服务器,然后,Rails 会判断这个变量,决定是否去调用destroy 方法。

 

 

1.9 Controller里的URL方法

 

      在View中,我们已经使用了一些新的helper方法(也就是path方法)来生成了REST风格的URL,那么controller 自然也需要一些新的东西来处理redirect 等请求。在controller中,我们使用“url”helper 方法,来生成正确的REST风格的URL。

      project_url 对应 project_path

      projects_url 对应 projects_path

      和 “path” 方法向对比,“url” 方法生成了一个完整的URL地址,包括协议,主机,端口,以及路径。

project_url(1)

=>

"http://localhost:3000/projects/1"

projects_url

=>

"http://localhost:3000/projects"

 

      在Rails 应用的controller里,”url” 方法用在redirect_to 方法里,取代传统的 controller/action 的方式。

redirect_to :controller => "projects", :action => "show",

:id => @project.id

REST应用中应该这么写:

redirect_to project_url(@project)

 

      对于这一点,你可以把 destroy action 作为一个例子去看看:在一个资源被删除以后,使用 project_url 去显示全部的资源,而不是像以往一样使用controller,action 作为参数。

Listing 1.3: ontrack/app/controllers/projects controller.rb

def destroy

@project = Project.find(params[:id])

@project.destroy

respond_to do |format|

format.html { redirect_to projects_url }

format.xml { head :ok }

end

end

 

1.10 REST风格的路由

 

      到目前为止,我们介绍了REST的内容,以及在 链接,form,controller中所适用的一些新的helper方法。但是我们没有解释那些helper方法是从哪来的?决定那些方法存在的,以及指定那些方法会调用哪个controller的哪个action,就是一个文件,那就是 /config/routes.rb。

 

map.resources :projects

 

这个配置是由我们上面适用 scaffold 生成器生成的。生成器生成了一个路由,当处理请求时,controller 需要这个路由才能知道调用哪个action。

      此外,resources 生成了path 和 url 的helper方法去操作“project”这个资源。

 

 

 

 

map.resources :projects

=>

Route Generated Helper

-----------------------------------------------------------

projects projects_url, projects_path

project project_url(id), project_path(id)

new_project new_project_url, new_project_path

edit_project edit_project_url(id), edit_project_path(id)

 

1.10.1 习惯

 

      要进行REST风格的开发,就必须遵循REST方式的命名习惯,多针对 CRUD 四个操作而言。下面的link_to 将会产生如下的html:

link_to "Show", project_path(project)

=>

<a href="/projects/1">Show</a>

 

不管是link_to 方法中,还是生成的html中,都没有去指定要调用的action,Rails 会知道,如果使用Get方式来调用这个URL,那么就是去调用show这个action。因此,controller里就必须有一个名字为“show”的action。对于index, update, delete,create, destroy,new,edit,也都是相同的习惯,所以,每一个REST的controller 都必须实现这几个方法。

 

1.10.2 定制路由

 

      通过以下一些选项,REST的路由可以去适应应用的一些特殊需求:

:controller.    指定使用哪一个controller

:path prefix.  生成的URL的前缀。

:name prefix.  Helper方法的前缀。包括 url方法和path方法。

:singular.      对于一个路由,命名一个唯一的名字。

 

下面的例子创建了一个路由,用于新建一个Sprint资源,sprint的信息我们会在下面的章节中介绍。

 

map.resources :sprints,

:controller => "ontrack",

:path_prefix => "/ontrack/:project_id",

:name_prefix => "ontrack_"

 

 

在这个URL中,我们适用了 :path_prefix,意味着每一个URL都

抱歉!评论已关闭.