Rails2.0第一篇完全教程,第一部分

转自:http://www.cnblogs.com/x116/articles/1035589.html

    非常高兴我的Rails 2.0 视频有很好的收视率,超过1,500名有独立IP的浏览者收看。那个想法是展现Rails2.0非常快的优点,展示了在不到30分钟的时间里能做些什么。

现在,我把视频分解成几个主要模块,然后做成了围绕Rails2.0非常快特征的第一个的完整教程。像其他的教程一样,这也没有完全100%的覆盖Rails2.0的知识点,仅仅是一些主要特征粘在一起的应用。 我推荐检查下Peepcode's Rails2 PDF 和 Ryan Bates 的 Railscasts.com 以获得更多详细信息。

这教程分为两部分,查看第二部分,点击这里。

现在我们开始。

1.认识一下环境

这个教程是连接前面那些已经有一些Rails1.2基础的人。请查阅一些关于Rails1.2有用的好的教程在互联网上(没有也没关系,Step by step 嘛)。

首先你得更新你的Gems:

Z:/ruby>gem install rails --include-dependencies

你可能最好还要升级一下你的RubyGems。

关于安装Ruby,Gem管理器,Rails等网上有非常多的教程,不多叙述。

首先要做的第一件事,让我建立一个新的Rails 应用程序:

切换到你的Ruby on Rails 工作目录,如果基于MySql数据库就键入以下命令:

G:/rlab>rails -d mysql blog

如果使用默认的sqlite3数据库则键入:

G:/rlab>rails blog

这将创建我们的通用Rails项目目录结构.搭建这个环境后要注意到的第一件事情,我们现在有了这个主结构:

.config/environment.rb
.config/initializers/inflections.rb
.config/initializers/mime_types.rb

在blog/config/initializers目录内的所有事情载入的时候都会在同一时间初始化environment.rb,因此当你在你的项目里用了一些不同的plugins 和 gems后,去维护这个environment.rb文件的护理工作会变得混乱和困难。现在我们有一个简单的办法来模块化我们的配置。

2.数据库

第二件事,我们要配置一下我们的数据库。首先用同样的方法配置blog/config/database.yml:(应注意yml文件中,“:”后面的空格)

 
 
development:
  adapter: mysql
  encoding: utf8
  database: blog_development
  username: root
  password: 123
  host: localhost

test:
  adapter: mysql
  encoding: utf8
  database: blog_test
  username: root
  password: 123
  host: localhost

production:
  adapter: mysql
  encoding: utf8
  database: blog_production
  username: root
  password: 123
  host: localhost

注意这里,你有个‘encoding’选项默认它设置成了 utf8 。Rails 应用程序最好是用它自己默认装载的KCODE = true , 意思是说在它默认启动的的时候就已经支持Unicode(无符号字符)了,这会非常好。但是这个'encoding' 配置最好是有一个新的用法,每当Rails连接数据库的时候它将告诉它设置'encoding',像说'SET NAMES UTF8'.

我们能这样DRY(Don't Repeat Yoursel) database.yml

 
 
defaults:  & defaults
  adapter: mysql
  encoding: utf8
  username: root
  password:  123
  host: localhost

development:
  database: blog_development
   << :  * defaults

test:
  database: blog_test
   << :  * defaults

production:
  database: blog_production
   << :  * defaults
 

看,多好。我们最好做一个新的Rake工作。他们中一些与数据库有关的:

  • db:charset 检索当前环境下数据库的字符设置
  • db:collation 检索当前环境下数据库的校对
  • db:create 用config/database.yml中的定义创建当前 RAILS_ENV 项目环境下的数据库
  • db:create:all 用config/database.yml中的定义创建所有数据库
  • db:drop 删除当前 RAILS_ENV项目环境中的数据库
  • db:drop:all 删除所有在 config/database.yml中定义的数据库
  • db:reset 从db/schema.rb中为当前环境重建数据库(先删后建).
  • db:rollback 回滚(清华出版社一本SQLSERVER书的名词[很奇怪为什么不直接用滚回])数据库到前一个版本. 指定回滚到哪一步要用 STEP=n 参数
  • db:version 检索当前模式下的版本

我们有一个很好的远程管理支持,在老版本的Rails里,现在我们就要登录到我们的数据库管理员控制台去手工创建数据库。但现在,我们能这样简单的做:

G:/rlab/blog>rake db:create:all

注意切换到blog目录下,以后我们的命令操作基本上是基于这个目录,所以下面的文章中就不带目录直接写命令了。如果我们要从刻痕处开始,我们能用 db:drop:all。之后在开发中我们能用db:rooback 到没有操作的最近的移植(migration)文件。

3.(性感|苗条)?(Sexyness)

当数据库设置和准备开始后,我们就能建立我们的第一笔资料。记住现在Rails 2.0 默认是RESTful(GOOGLE有解释)。

ruby script/generate scaffold Post title:string body:text

这儿仅仅不同的是"scaffold"行为,像前面我们用的"scaffold_resource",老的非RESTful的scaffold已经不用了。在ActionController类里面没有了"scaffold"方法,它已经默认动态组装到你的空控制器里了。所以,现在每个脚手架scaffold我们已经RESTful啦。

它将创建"嫌疑惯犯"('The Usual Suspects' 著名美国电影,译名有《非常嫌疑犯》《嫌疑惯犯》)们:Controller , Helper, Model , Migration ,Unit Test, Functional Test.

主要的不同是在 Migration 文件里:

 0  # db/migrate/001_create_posts.rb
 1 class CreatePosts  <  ActiveRecord::Migration
 2   def self.up
 3     create_table :posts  do   | t |
 4       t.string :title
 5       t.text :body
 6
 7       t.timestamps
 8     end
 9   end
10
11   def self.down
12     drop_table :posts
13   end
14 end

我们把这个叫 Sexy Migrations ,是"Err the Blog"发明的,首先只是作为一个插件,后来这方法很多人都觉得不错就加进了核心。理解了这个好的方法后,再看下和Rails1.2的migration哪里不同:

 1 class CreatePosts  <  ActiveRecord::Migration
 2   def self.up
 3     create_table :posts  do   | t |
 4       t.column :title, :string
 5       t.column :body, :text
 6       t.column :created_at, :datetime
 7       t.column :updated_at, :datetime
 8     end
 9   end
10
11   def self.down
12     drop_table :posts
13   end
14 end

摆脱了't.column'的重复,现在用't.column_type'格式和把自动时间列集中在了一个't.timestamps'语句里。没有改变任何行为,仅仅是把代码搞苗条了。
好,现在我们像前面一样的运行migration:

rake db:migrate

以前我们要回滚一个migration需要这样做:

rake db:migrate VERSION=xxx

XXX就是我们要返回的那个版本号,现在我们能简单发号指令:

rake db:rollback

那确实,很好,很优美!都设置好后,我们就能像前面做的一样启动我们的服务器看下产生的页面:

ruby script/server 

 

这将装载到 Mongrel, Webrick 或者 Lightpd等任一WEB服务器的3000端口, 和以前一样我们有同样的首页,显现出index.html页。在视频教程里的一则小道信息我没展出来的在这:

# config/routes.rb
  ActionController::Routing::Routes.draw  do   | map |
  map.root :controller  =>   ' posts '
  map.resources :posts
  
end

 这里有条新语句:'map.root' ,和 map.connect ,:controller => 'posts' 的效果一样。这语句没做什么其他的事仅仅是把Routes变的精练了点。当你设置好了这个后,别忘记了删除 blog/public/index.html文件。现在根URL将总是定位到 Posts 控制器。

注意这里图片里显示的那些记录是点New post发布之后的。
你看,和以前相比每件事情感觉都简单多了。所有的脚手架模板都一样,你能浏览,创建新行等等。

4.路由嵌套

好,让我们来建一个发表同步评论的资源(表格Table)。这将完成我们的资料库(数据库Database)创建。

ruby script/generate scaffold Comment post:references body:text
rake db:migrate

同样的事情:在命令行里创建脚手架资源(scaffold the resource),配置字段名和数据格式,migration文件就被设置了。注意另一个小添加:关键字“references” 。当我的朋友Arthur 提醒我,这会让migrations更苗条。比较一下,以前的方法我们是这样做的:
ruby script/generate scaffold Comment post_id_:integer body:text
外键仅仅是实现了些细节,没关系,看一下新的migration文件(blog/db/migrate/002_create_comments.rb):

def self.up
    create_table :comments  do   | t |
      t.references :post
      t.text :body

      t.timestamps
    end
end
看一下新关键字references下的细节:运行rake db:migrate 建立好数据库中的数据表后,我们来配置它涉及到的每一个ActiveRecord 模型(model)
# app/models/post.rb
class Post  <  ActiveRecord::Base
  has_many :comments
end

# app/models/comment.rb
class Comment  <  ActiveRecord::Base
  belongs_to :post
end
好,这里并没有什么新东西,我们已经知道如何用几个ActiveRecord联合在一起做了。但我们也能用RESTful做,新的Rails方法里,我们将有如下的一些URL:

http: // localhost:3000/posts/1/comments
http: // localhost:3000/posts/1/comments/new
http: // localhost:3000/posts/1/comments/3

意味着:我们能看到详细POST下面的Comments,但scaffold生产器只能去生产下面的这些URL:

http: // localhost:3000/posts/1
http: // localhost:3000/comments/new
http: // localhost:3000/comments/3

这是因为在路由文件(config/routes.rb)里面有:

ActionController::Routing::Routes.draw  do  |map|
  map.resources :comments

  map.root :controller  =>   ' posts'
  map.resources :posts
  
end

让我们把他们拧一下,就像是在一个model里面一样,我们能创建一种嵌套路由(Nested Route):
#config / routes.rb
ActionController::Routing::Routes.draw  do  |map|
  map.root :controller  =>   ' posts'
  map.resources :posts, :has_many  =>  :comments  
end

像这样我们就能看到前面说的嵌套的URL。
http: // localhost: 3000 / posts / 1 / comments

Rails 就会这样分析它:
    · 载入CommentsController
    · 设置参数[:post_id] = 1 
    · 既然情况这样,叫‘index’行动
我们已经准备好让Comments controller被嵌套了,如此一来我们将要做下面的这样一些改变:

#app / controllers / comments_controller.rb
class  CommentsController  <  ApplicationController
  before_filter :load_post
  ......
  def load_post 
    @post = Post.find(params[:post_id])
   end
end

这将设置好@post的所有行为准备好做 Comments controller。现在我们就还要做下面的这些替换(app/controllers/comments_controller.rb):
改变前:替换为:
Comment.find@post.comments.find
Comment.new@post.comments.build
redirect_to(@comment)redirect_to(@post, @comment)
redirect_to(comments_url)redirect_to(post_comments_url)

这样就会让Comments controller准备好。现在我们更改4个视图(view)文件,如果你打开 new.html.erb 和 edit.html.erb 你将发现以下这些新特征:

new  edit.html.erb  and   new .html.erb
form_for(@comment)  do  |f|
  
end

这是我们以前1.2里面做这些事的旧语句:
# old  new .rhtml
form_for(:comment, :url  =>  comments_url)  do  |f|
  
end

# old edit.rhtml
form_for(:comment, :url  =>  comment_url(@comment), 
  :html  =>  { :method  =>  :put })  do  |f|
  
end

注意在new.rhtml和edit.rhtml 中一样的语句form_for。这样是因为Rails能推断根据名为@comment类的实例做什么,但现在用了嵌套路由,comments依靠Post了,所以我们要做:

new  edit.html.erb  and   new .html.erb
form_for([@post, @comment])  do  |f|
 
end

Rails 会很快明白嵌套路由里面的这些数组表达的内容,将会检查routes.rb文件,计算出名为
post_comment_url(@post, @comment) 的路由。
让我们解释下叫这些名字的路由先,当我们设置
route.rb文件里的路由资源的时候,我们获得了这些名字的路由:
 

路由名

HTTP 传送方式

控制器行为 

comments

GET

index

comments

POST

create

comment(:id)

GET

show

comment(:id)

PUT

update

comment(:id)

DELETE

destroy

new_comment

GET

new

edit_comment(:id)

GET

edit


"7种行为统治他们所有人" :-)

你能给他们加后缀 " path"或者" url" ,不同点就是:
 

comments_url

http://localhost:3000/comments

comments_path

/comments


最后你能给他们加前缀“ formatted” ,会给你:
 

formatted_comments_url(:atom)

http://localhost:3000/comments.atom

formatted_comment_path(@comment, :atom)

/comments/1.atom


现在,Comments嵌套到Post里面去了,我们有责任添加前缀'post' 。在Rails1.2里,这个前缀是可选的,他能通过数字和参数告诉你通往不同的post helper,但这会搞得有些含糊,所以现在必须添加前缀,像这样:
 

route

HTTP verb

URL

post_comments(@post)

GET

/posts/:post_id/comments

post_comments(@post)

POST

/posts/:post_id/comments

post_comment(@post, :id)

GET

/posts/:post_id/comments/:id

post_comment(@post, :id)

PUT

/posts/:post_id/comments/:id

post_comment(@post, :id)

DELETE

/posts/:post_id/comments/:id

new_post_comment(@post)

GET

/posts/:post_id/comments/new

edit_post_comment(@post, :id)

GET

/posts/:post_id/comments/edit


总的来说,这样一做,我们就能使comments视图运转起来像嵌套在post里面一样。所以我们需要改变一下默认的脚手架生产代码去适应嵌套:
< ! --  app / views / comments / _comment.html.erb(如果没这个文件就新建)  -->
< % form_for([@post, @comment])  do  |f| % >
   < p >
     < b > Body </ b >< br  />
     < % =  f.text_area :body % >
   </ p >

   < p >
     < % =  f.submit button_name % >
   </ p >
< %  end  % >

< ! --  app / views / comments / edit.html.erb  -->
< h1 > Editing comment </ h1 >

< % =  error_messages_for :comment % >

< % =  render : partial   =>  @comment, 
  :locals  =>  { :button_name  =>   " Update " } % >

< % =  link_to  ' Show', [@post, @comment] %> |
< % =  link_to  ' Back', post_comments_path(@post) %>

< ! --  app / views / comments / new .html.erb  -->
< h1 > New  comment </ h1 >

< % =  error_messages_for :comment % >

< % =  render : partial   =>  @comment, 
  :locals  =>  { :button_name  =>   " Create " } % >

< % =  link_to  ' Back', post_comments_path(@post) %>

< ! --  app / views / comments / show.html.erb  -->
< p >
   < b > Body: </ b >
   < % = h @comment.body % >
</ p >


< % =  link_to  ' Edit', [:edit, @post, @comment] %> |
< % =  link_to  ' Back', post_comments_path(@post) %>

< ! --  app / views / comments / index.html.erb  -->
< h1 > Listing comments </ h1 >

< table >
   < tr >
     < th > Post </ th >
     < th > Body </ th >
   </ tr >

< %  for  comment  in  @comments % >
   < tr >
     < td >< % = h comment.post_id % ></ td >
     < td >< % = h comment.body % ></ td >
     < td >< % =  link_to  ' Show', [@post, comment] %></td>
     < td >< % =  link_to  ' Edit', [:edit, @post, comment] %></td>
     < td >< % =  link_to  ' Destroy', [@post, comment], 
      :confirm  =>   ' Are you sure?', :method => :delete %></td>
   </ tr >
< %  end  % >
</ table >

< br  />

< % =  link_to  ' New comment', 
  new_post_comment_path(@post) % >
一些注释:
        ·注意这里我创建了一个 partial  DRY的new和edit表单,但重视替换 :partial => ‘comment’,我做了":partial => @comment ",他们将从类名里推断partial,如果我们通过了一个集合,他将做相当于旧语句":partial, :collection"的事。
        ·我能用 post_comment_path(@post, @comment)或者简单的[@post, @comment]
        ·更要重视的是别忘记了路由背后的任何名字。

最后,链接Comments列表页面到Post页面,我需要做:

< ! --  app / views / posts / show.html.erb  -->
< % =  link_to  ' Comments', post_comments_path(@post) %>
< % =  link_to  ' Edit', edit_post_path(@post) %> |
< % =  link_to  ' Back', posts_path %>

我仅仅添加了一个链接,让我再来看看它看起来会像什么:


 

完成视图

 好,看起来不错,但现在它还不太像一个将要运行的博客,post页面最好还应该显示已经有的comments列表和一个提交新注解的表单,所以我们现在还需要进行一些小小改编,和传统的 Rails相比没有什么新改变,让我们开始改视图:

< ! --  app / views / posts / show.html.erb  -->
< p >
   < b > Title: </ b >
   < % = h @post.title % >
</ p >

< p >
   < b > Body: </ b >
   < % = h @post.body % >
</ p >

< ! --  # 1   -->
< % unless @post.comments.empty? % >
   < h3 > Comments </ h3 >
   < % @post.comments.each  do  |comment| % >
   < p >< % =  h comment.body % ></ p >
   < %  end  % >
< %  end  % >

< ! --  # 2   -->
< h3 > New  Comment </ h3 >
< % =  render : partial   =>  @comment  =  Comment.new, 
   :locals  =>  { :button_name  =>   ' Create'}%>

< % =  link_to  ' Comments', post_comments_path(@post) %>
< % =  link_to  ' Edit', edit_post_path(@post) %> |
< % =  link_to  ' Back', posts_path %>

更多注释:
    · 这个迭代程序里面没有任何新东西,仅仅是把注解列出来
    · 这里通过@comment变量对partial语句
最后的一点调整:当提交一个新注解之后我们希望回到它的POST页面,这就需要改写下comments_controller.rb中的路由
    把app/controllers/comments_controller.rb中的旧路由:
    redirect_to(@post, @comment)
    替换成新路由:
    redirect_to(@post)

路由命名空间:

好,我们现在已经有一个露骨的迷你博客了,有点15分钟视频里面的大卫在2005年做的经典博客的仿制品的味道了。现在,让我们更进一步,公布的文章(post)不可以被所有人编辑,我们需要一个Administration在我们的网站里面进行管理工作。让我们创建一个新的控制器:
    ruby script/generate controller Admin::Posts
Rails2.0现在支持命名空间,这将创建一个叫"app/controllers/admin"的子目录。
我们要做这些事:
    1,创建一个新的路由
    2,拷贝所有旧Posts controller里面的actions到新的Admin::posts,只留下"index和"show"这两个。
    3,拷贝所有旧Posts views到app/views/admin/* 只留下旧Posts views的"index"和"show"这两个,意思是最好把旧posts views下面new和edit视图都删除掉。
    4,改编actions和views我们仅仅拷贝过来它们还不能明白应该怎么和admin controller一起工作。
首先的首先,让我们再编辑路由文件(config/routes.rb):

map.namespace :admin  do  |admin|
  admin.resources :posts
end

实际上这意味着我们现在有的作为posts的路由名字带了个前缀"admin",这将消除新添加的的admin posts路由和旧的posts路由之间的歧义,像这样:
 

posts_path

/posts

post_path(@post)

/posts/:post_id

admin_posts_path

/admin/posts

admin_post_path(@post)

/admin/posts/:post_id


现在让我们把actions从旧post controller拷贝到admin postcontroller里并改变使之适应新的namespace:

# app / controllers / admin / posts_controller.rb

def create
  # old:
   format .html { redirect_to(@post) }
  #  new :
   format .html { redirect_to([:admin, @post]) }
end

def update
  # old:
   format .html { redirect_to(@post) }
  #  new :
   format .html { redirect_to([:admin, @post]) }
end

def destroy
  # old:
   format .html { redirect_to(posts_url) }
  #  new :
   format .html { redirect_to(admin_posts_url) }
end


别忘记了删掉 app/controllers/posts_controller.rb里面的所有方法,只留下index 和show这两个。
现在,让我们来拷贝这些视图(也可以直接在windows里面拖放):
copy app/views/posts/*.erb app/views/admin/posts
del app/views/posts/new.html.erb
del app/views/posts/edit.html.erb
现在,让我们来编辑app/views/admin/posts下面的那些视图(views):
< ! --  app / views / admin / posts / edit.html.erb  -->
< h1 > Editing post </ h1 >

< % =  error_messages_for :post % >

< % form_for([:admin, @post])  do  |f| % >
 
< %  end  % >

< % =  link_to  ' Show', [:admin, @post] %> |
< % =  link_to  ' Back', admin_posts_path %>

  
< ! --  app / views / admin / posts / new .html.erb  -->
< h1 > New  post </ h1 >
< % =  error_messages_for :post % >

< % form_for([:admin, @post])  do  |f| % >
  
< %  end  % >

< % =  link_to  ' Back', admin_posts_path %>


< ! --  app / views / admin / posts / show.html.erb  -->
< p >
   < b > Title: </ b >
   < % = h @post.title % >
</ p >

< p >
   < b > Body: </ b >
   < % = h @post.body % >
</ p >

< % =  link_to  ' Edit', edit_admin_post_path(@post) %> |
< % =  link_to  ' Back', admin_posts_path %>

< ! --  app / views / admin / posts / index.html.erb  -->

< %  for  post  in  @posts % >
   < tr >
     < td >< % = h post.title % ></ td >
     < td >< % = h post.body % ></ td >
     < td >< % =  link_to  ' Show', [:admin, post] %></td>
     < td >< % =  link_to  ' Edit', edit_admin_post_path(post) %></td>
     < td >< % =  link_to  ' Destroy', [:admin, post], 
      :confirm  =>   ' Are you sure?', :method => :delete %></td>
   </ tr >
< %  end  % >
</ table >

< br  />

< % =  link_to  ' New post', new_admin_post_path %>

差不多做好了,如果你现在测试 http://localhost:3000/admin/posts 它将完全能好好工作。但是,它的样子看起来将是有点丑,这是因为我们没有全局的应用程序外观(app layout),当我们做第一个脚手架的时候,Rails产生器单独给Post和Comment做了个外观,所以,我们现在删除他们然后建立现在这样的一个通用的外观:
    把 app/views/layouts/posts.html.erb文件改名为:application.html.erb,再删掉app/views/layouts/comments.html.erb
这样仅仅保留了从早先的旧Posts controllers的 index 和 show 页面,它们仍然有链接方法,我们要删掉,所以让我们把这些链接撕断:
< ! --  app / views / posts / index.html.erb  -->
< h1 > My Great Blog </ h1 >

< table >
   < tr >
     < th > Title </ th >
     < th > Body </ th >
   </ tr >

< %  for  post  in  @posts % >
   < tr >
     < td >< % = h post.title % ></ td >
     < td >< % = h post.body % ></ td >
     < td >< % =  link_to  ' Show', post %></td>
   </ tr >
< %  end  % >
</ table >

< ! --  app / views / posts / show.html.erb  -->

<p>
  <b>Title:</b>
  <%=h @post.title %>
</p>

<p>
  <b>Body:</b>
  <%=h @post.body %>
</p>
<!--#comments list -->
<% unless @post.comments.empty? %>
 <h3>Comments</h3>
   <% @post.comments.each do |comment| %>
    <p><%= h comment.body %></p>
    <% end %>
<% end %>

<h3>New Comment</h3>

<%= render :partial => @comment = Comment.new,
   :locals => { :button_name => 'Create'}%>

<%= link_to 'Back', posts_path %>

我们能从浏览器测试每件事情,打开 http://localhost:3000/admin/posts 看到每样事情都在井井有条的工作着,但是我们仍然有一件事没做,那就是现在管理员功能是所有人都可以用的,如果正好跳进去了就可以编辑每样东西!我们需要身份验证。
基本的HTTP身份验证:
这儿有好几种方式进行身份验证,有个插件叫
restful_authentication 使用得非常广泛。
但是,我们不需要搞得每件事情都异常复杂。Rails2.0给我们提供了一种强大的验证方法。这理念就是,让我们使用HTTP,已经给我们的HTTP Basic Authentication.它的缺点就是你将要使用SSL当成为产品的时候。但是,当然你能使用任何方法做到。HTML 表单验证在没有SSL的时候是没有被保护的。

好,我们现在编辑我们的Admin::Posts controller 添加身份验证:
# app / controllers / admin / posts_controller.rb
class  Admin::PostsController  <  ApplicationController
  before_filter :authenticate
  
  def authenticate
    authenticate_or_request_with_http_basic  do  |name, pass|
      #User.authenticate(name, pass)
      name  ==   'admin ' && pass == 'admin'      
     end
   end
你已经知道 before_filter 做什么:在控制器里,它运行在任何配置的行为之前。如果你在应用程序控制器类(ApplicationController class)里设置它,那么它将运行在所有的其他控制器的任何行为之前,但这里我们仅仅需要保护Admin:Post 。

我们实现这个方法还增加点保密的趣味"authenticate_or_request_with_http-basic" 方法让我们配置好一个块。它在浏览器里给我们这个输入用户名和密码的对话框,我们就要使用用户名和密码才能登录管理博客的数据库,但这是个非常简单的例子,里面的用户名和密码用了明码且无需到数据库验证,真正的产品需要进行其他工作,但是你获得了身份验证这个好主意。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值