Rails:Web API接口实作

话说,好记性不如烂笔头「当然,主要是自己笨」,趁着假期,实作了一个简单的API接口,主要是CRUD + auth 认证部分,以加强对API接口部分的理解。

正文

以用户对一个event活动进行CRUD操作的功能实现为例:

主要按如下步骤走:

  • 建model event,含字段title, description

  • 设置路由,实作event的get, post, patch ,delete接口

  • 添加认证API

最后的效果是:

用户可以通过API接口来新建,修改,查看,删除event

用户在发出新建,修改,删除请求时,需要认证

P.S:其实可以加个 rspec测试,不过这里我用postman直接测了,省去rspec部分,以免篇幅过长「主要还是因为,呃,你懂的,懒……」

好,开搞吧。

一、新建一个project event_api
rails new event_api
cd event_api
git init

生成如下文件:

二、建event model,实作CRUD接口
  • 建event model, 终端输入:

    git checkout -b event # 切换分支,做烂了可以再开个
    rails g model event title:string description:text uuid:string
    rake db:migrate
    

    「为了安全性,给event加上一个uuid的字段」

    app/models/event.rb中,给title加上有效性验证,并在创建时自动生成uuid:

    class Event < ApplicationRecord
      validates_presence_of :title
      before_validation :generate_uuid, :only => :create
      def to_param
          self.uuid
      end
    
      protected
    
      def generate_uuid
        self.uuid = SecureRandom.uuid
      end
    end
    
  • 设置路由,在config/routes.rb中添加如下路由:

      namespace :api, :defaults => {:format => :json } do
        namespace :v1 do
          resources :events, :only => [:index, :show, :create, :update, :destroy]
        end
      end
    
  • 产生API controller,终端输入:

    rails g controller api --no-assets
    

    修改app/controllers/api_controller.rb:

    - class ApiController < ApplicationController
    + class ApiController < ActionController::Base
    end
    

    改成继承自ActionController::Base,因为API 不需要 protect_from_forgery with: :exception 这一行的 CSRF 浏览器安全检查。

  • 实作event的get, post, patch, delete

    终端执行:

    rails g controller api::v1::events --no-assets
    

    app/controllers/api/v1/events_controller.rb中,填入以下内容:

    class Api::V1::EventsController < ApiController
    
      def index
        @events = Event.all
        render :json => {
            :data => @events.map {|event|
            {
                :id => event.id,
                :uuid => event.uuid,
                :title => event.title,
                :event_url => api_v1_event_url(event.id)
            }}
        }
      end
    
      def create
        @event = Event.new(
                            :title => params[:title],
                            :description => params[:description])
        if @event.save
          render :json => {
              :id => @event.id,
              :uuid => @event.uuid,
              :title => @event.title,
              :description => @event.description
          }
        else
          render :json => {
              :message => "创建event失败", :errors => @event.errors
          }, :status => 400
        end
      end
    
      def show
        @event = Event.find_by_uuid!(params[:id])
        render :json => {
            :id => @event.id,
            :uuid => @event.uuid,
            :title => @event.title,
            :description => @event.description
        }
      end
    
      def update
        @event = Event.find_by_uuid!(params[:id])
        @event.update(:title => params[:title],
                      :description => params[:description])
        render :json => {:message => "更新event成功"}
      end
    
      def destroy
        @event = Event.find_by_uuid!(params[:id])
        @event.destroy
        render :json => {:message => "删除event成功"}
      end
    end
    

    注意,将EventsController 修改为继承自ApiController

    启服务器,rails s, 用postman测试看看:

    create

    index

    show:

    Update:

    发出get请求,看到event的详情中已经修改了title:

    destroy:

    看event的list, 发现event2018_updated已经删除:

    OK!!

三、添加认证API

这里做event的API时候,都不需要认证的,让我们加上认证部分。

  • 装上devise

    编辑 Gemfile 加上 gem "devise"

    终端运行:bundle, 重启服务器,终端执行:

    rails g devise:install
    rails g devise user
    rake db:migrate
    
  • 给event和user挂上关系

    app/models/user.rb加上:

    has_many :events
    

    app/models/event.rb 加上:

    belongs_to :user
    

    将user_id添加到event中,终端执行:

    rails g migration add_user_id_to_events user_id:integer
    rake db:migrate
    

    修改app/controllers/api/v1/events_controller.rb, 在create部分填上@event.user = current_user

    ......
      def create
        @event = Event.new(
                            :title => params[:title],
                            :description => params[:description])
        @event.user = current_user
        if @event.save
          render :json => {
              :id => @event.id,
              :uuid => @event.uuid,
              :title => @event.title,
              :description => @event.description
          }
        else
          render :json => {
              :message => "活动创建失败", :errors => @event.errors
          }, :status => 400
        end
      end
      ......
    

    这里也可以设置一下show,index部分,添加上creator的字段,显示是谁创建这个event的。「此处略去,不是重点」

  • 给user加上token字段

    终端运行:

    rails g migration add_auth_token_to_users
    

    编辑文件XXXX_add_auth_token_to_users.rb文件:

    class AddAuthTokenToUsers < ActiveRecord::Migration[5.1]
      def change
        + add_column :users, :auth_token, :string
        + add_index :users, :auth_token, :unique => true
      end
    end
    

    终端运行:

    rake db:migrate
    

    编辑app/models/user.rb, 给user加上token:

    class User < ApplicationRecord
      # Include default devise modules. Others available are:
      # :confirmable, :lockable, :timeoutable and :omniauthable
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :trackable, :validatable
    
      has_many :events
    
      before_create :generate_authentication_token
    
      def generate_authentication_token
        self.auth_token = Devise.friendly_token
      end
    
    end
    
  • 实作注册,登录,登出的API

    config/routes.rb 加上路由:

    ......
      namespace :api, :defaults => {:format => :json } do
        namespace :v1 do
          resources :events, :only => [:index, :show, :create, :update, :destroy]
         + post "/signup" => "auth#signup"
         + post "/login" => "auth#login"
         + post "/logout" => "auth#logout"
        end
      end
    ......  
    

    生成auth的controller:

    rails g controller api::v1::auth --no-assets
    

    编辑它:

    class Api::V1::AuthController < ApiController
      before_action :authenticate_user!, :only => [:logout]
    
      def signup
        user = User.new(:email => params[:email], :password => params[:password])
    
        if user.save
          render :json => {:user_id => user.id }
        else
          render :json => {:message => "Failed", :errors => user.errors}, :status => 400
        end
      end
    
      def login
        if params[:email] && params[:password]
          user = User.find_by_email(params[:email])
        end
        if user && user.valid_password?(params[:password])
          render :json => {
              :message => "ok",
              :auth_token => user.auth_token,
              :user_id => user.id
          }
        else
          render :json => {:message => "Email or Password is wrong"}, :status => 400
        end
      end
    
      def logout
        current_user.generate_authentication_token
        current_user.save!
        render :json => {:message => "logout successful"}
      end
    end
    

    编辑app/controllers/api_controller.rb, 添加如下验证:

      before_action :authenticate_user_from_token!
    
      def authenticate_user_from_token!
        if params[:auth_token]
          user = User.find_by_auth_token(params[:auth_token])
          sign_in(user, store: false) if user
        end
      end
    

    这里可以改成将auth_token放在了header中传递,看这篇如何将auth token放入headers进行传递?

    重启服务器,用postman测试看看:

    login ,获取auth_token

    logout, 传递auth_token

  • 最后,给event添加上认证,修改app/controllers/api/v1/events_controller.rb中,添加:

    ......
    before_action :authenticate_user!, :only => [:create, :update, :destroy]
    ....
    

    用postman测试看看,现在新建event需要传递一个auth_token才行:

    添加上auth_token, 创建成功:

    Patch, delete同理,可以用postman 进行测试看看,这里就不细看了。

    The End

    我这边列出来的都是非常基础的API接口,其他复杂点的都是可以按照这个方式来做的,比如你可以在这个的基础上,添加comment,让用户通过接口给event做评论,点赞,自定义like method等等。

    对于routes的写法,上面我用了rails自身的resource方式:resources :events, :only => [:index, :show, :create, :update, :destroy], 也用了较易于理解的逐条列出路径的方式:post "/signup" => "auth#signup", 可根据实际情况自行选择.

    Happy coding! : )