【山竹记账后端】RESTful API 是什么


大纲链接 §

[toc]


1. REST风格是什么

REST 是什么?

  • Representational State Transfer
  • 一种网络软件 架构风格
    • 不是标准、不是协议、不是接口,只是一种风格
    • Roy于2000年在自己博士论文中提到此术语
    • Roy曾参与撰写HTTP规格文档

怎么做?

  1. 资源为中心,一旦此为名字一般就是资源,如果为动词就不是资源:用户、创建用户
  2. 充分利用 HTTP现有功能,如动词、状态码、头部字段
  3. Github API 就比较符合 REST

参考


REST风格举例

请求1:创建 item

1
2
3
4
POST /api/v1/items
Content-Type: application/json
消息体 {"amount": 99, "kind": "income"}
响应 {"resource": {...},} 或者 {"errors": {...}}
  • 注意货币的最小单位,避免浮点数的精度问题,99

请求2:创建 item

1
2
3
POST /api/v1/items
Content-Type: application/x-www-form-urlencoded
消息体 amount-99&kind=income

请求3:更新 item

1
2
3
PATCH /api/v1/items/1
Content-Type: application/json
消息体 {"amount": 11, "kind": "expense"}

反风格:

1
POST /api/v1/modify_items?id=1
  • 反方观点:全用 POST 多省事儿
  • 我方观点:自己想路径,多费事儿

请求4:删除 item

1
DELETE /api/v1/items/1

反风格:

1
POST /api/v1/remove_items?id=1
  • 反方观点:POST 多省事儿
  • 我方观点:有 DELETE 不用非要自己想,多费事儿

请求5:获取 一个或多个 item

1
2
3
4
5
6
7
8
9
GET /api/v1/items/1
GET /api/v1/items?page=1&per_page=10
GET /api/v1/user/2/items
GET /api/v1/items?user_id=2
GET /api/v1/items?tags_id[]=1&tag_id[]=2
GET /api/v1/items?tags_id=1,2
GET /api/v1/items?sort_by[]=id+asc&sort_by[]=name+desc
GET /api/v1/items?keyword=hi
GET /api/v1/items/search/hi
  • 单条或多条记录
    • 单条 GET /api/v1/items/1 省略了 id
    • 多条 GET /api/v1/items?page=1&per_page=10 获取分页数据
    • 按用户获取通过传id获取多条 GET /api/v1/user/2/items?... 从属关系,资源嵌套
    • 无资源嵌套,拍平 GET /api/v1/items?user_id=2
  • 数组
    • 属于两个tag的记录 GET /api/v1/items?tags_id[]=1&tag_id[]=2
    • 表示资源数组 tags_id[],后端框架自动拼接,比如 rails
    • 其他框架的反风格:GET /api/v1/items?tags_id=1,2
    • 如果 url 太长就不得不改为 POST
  • 排序,二重排序 GET /api/v1/items?sort_by[]=id+asc&sort_by[]=name+desc
    • + 就是空格
    • 排序条件相同则按创建时间排序
  • 搜索
    • GET /api/v1/items?keyword=hi
    • 反风格 GET /api/v1/items/search/hi

REST风格总结

  1. 尽量以资源为中心:url里的items就是资源
  2. 尽量使用HTTP现有功能:其实响应头里也可以包含内容,但目前的例子都没有用到
  3. 可以适当违反规则:比如 /api/v1/items/search/hi

人话版REST风格总结

  • 看见 路径 就知道请求什么东西
  • 看见 动词 就知道是什么操作
  • 看见 状态码 就知道结果是什么
    • 200 - 成功
    • 201 - 创建成功
    • 400 - 其他所有错误,详细原因可以放在 body
    • 404 - 未找到
    • 403 - 没有权限
    • 401 - 未登录
    • 422 - 无法处理的实体,参数有问题
    • 402 - 需付费
    • 412 - 不满足前提条件,流程中常用
    • 429 - 请求太频繁

为什么不喜欢REST

需求:批量创建 items

  • POST /api/v1/items 只能创建一个 item 返回一个结果

可以适当改造 REST

  • POST /api/v1/items/batch
  • 请求消息体 [{"amount": 1}, {"amount": -2}, {"amount": 3}]
  • 响应内容 {"resources: [{...}, null, {...}]"}, {"errors": [null, {...}, null]}
  • 更多批处理
    • POST /api/v1/items/batch
    • DELETE /api/v1/items/batch
    • UPDATE /api/v1/items/batch

符合 REST 风格的 API 就叫 RESTful API


2. API概要设计

  • 使用 Rails 提供的强大工具

2.1 发送验证码

sms_code

  • 资源: validation_codes
  • 动作: 只有一个 create POST
  • 状态码: 200 | 201 | 422 | 429成功 | 创建成功 | 请求参数验证错误 | 请求太频繁

2.2 登入登出

  • 资源: session (注意没有 s,单点登录)
  • 动作: create | destroy POST | DELETE
  • 状态码: 200 | 422

2.3 当前用户

  • 资源: me
  • 动作: show GET
  • 状态码: 200 | 429

2.4 记账数据

  • 资源: items
  • 动作: create | update | show | index | destroy
    • update 对应 PATCH 表示部分字段更新
    • show 对应 GET /items/:id 用来表示一条记账记录
    • index 对应 GET /items?since=2026-01-01&before=2026-02-01
    • destroy 对应 DELETE 表示删除,一般为软删除
  • 状态码: 200 | 201 | 422 | 429

2.5 标签

  • 资源: tags
  • 动作: create | update | show | index | destroy
  • 状态码: 200 | 201 | 422 | 429

2.6 打标签* 暂不实现

记录用户和标签的关系

  • 资源: taggings (动词的名词形式)
  • 动作: create | index | destroy
  • 状态码: 200 | 201 | 422 | 429

API 概要设计已完成

  • 接下来有一些细节要注意

3. 开始实现-路由 api

手动添加路由 config/routes.rb

1
2
3
4
Rails.application.routes.draw do
  post "/validation_codes", to: "validationCodes#create"
  #...
end

对比使用 namespaces 简略写路径,自动生成路由

1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      # 写在这里都是以 /api/v1 开头的路由,自动生成资源对应的6个方法
      resources :validation_codes
    end
  end

end
  • namespace 自动添加前缀(命名空间)
  • 运行命令 bin/rails routes 查看路由:URI Pattern 对应 Controller#Action
    • GET /api/v1/validation_codes(.:format) 对应 api/v1/validation_codes#index
    • POST /api/v1/validation_codes(.:format) 对应 api/v1/validation_codes#create
    • GET /api/v1/validation_codes/:id(.:format) 对应 api/v1/validation_codes#show
    • PATCH /api/v1/validation_codes/:id(.:format) 对应 api/v1/validation_codes#update
    • DELETE /api/v1/validation_codes/:id(.:format) 对应 api/v1/validation_codes#destroy
  • resources :xxx 自动生成资源对应的6个方法
  • 手动实现这些api对应的方法

rails routes


配置路由缺省方法

1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      # 写在这里都是以 /api/v1 开头的路由,仅生成配置的方法
      resources :validation_codes, only: [:create]
    end
  end

end
  • resources :validation_codes, only: [:create] 仅生成配置的路由、方法

配置好其他所有路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :validation_codes, only: [:create]
      resources :session, only: [:create, :destroy]
      resources :me, only: [:show]
      resources :items, exclude: [:put]
      resources :tags, exclude: [:put]
    end
  end

end

rails routes all

具体对应方法实现先暂时不写


参考文档


3. 实现 validation_codescreate 的路由

创建数据表

创建表

  • 运行命令 bin/rails g model ValidationCode email:string kind:integer used_at:datetime

    • 注意 ValidationCode 没有 s

      1
      2
      3
      4
      5
      
      bin/rails g model ValidationCode email:string kind:integer used_at:datetime
      
      #      invoke  active_record
      #      create    db/migrate/20260909153656_create_validation_codes.rb
      #      create    app/models/validation_code.rb

db/migrate/20260909153656_create_validation_codes.rb 修改部分配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class CreateValidationCodes < ActiveRecord::Migration[7.2]
  def change
    create_table :validation_codes do |t|
      t.string :email
      t.integer :kind, default: 1, null: false
      t.datetime :used_at

      t.timestamps
    end
  end
end

迁移数据库

  • 运行命令 bin/rails db:migrate

    1
    2
    3
    4
    
    == 20260909153656 CreateValidationCodes: migrating ============================
    -- create_table(:validation_codes)
    -> 0.0273s
    == 20260909153656 CreateValidationCodes: migrated (0.0274s) ===================

反悔迁移数据库

由于 validation_codescode 字段未添加,需要撤销迁移

  • 运行命令 bin/rails db:rollback 撤销迁移

    1
    2
    3
    4
    
    == 20260909153656 CreateValidationCodes: reverting ============================
    -- drop_table(:validation_codes)
    -> 0.0048s
    == 20260909153656 CreateValidationCodes: reverted (0.0082s) ===================

再次修改 db/migrate/20260909153656_create_validation_codes.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class CreateValidationCodes < ActiveRecord::Migration[7.2]
  def change
    create_table :validation_codes do |t|
      t.string :email
      t.integer :kind, default: 1, null: false
      t.string :code, limit: 100
      t.datetime :used_at

      t.timestamps
    end
  end
end
  • 再次运行 bin/rails db:migrate

    1
    2
    3
    4
    
    == 20260909153656 CreateValidationCodes: migrating ============================
    -- create_table(:validation_codes)
    -> 0.0135s
    == 20260909153656 CreateValidationCodes: migrated (0.0136s) ===================

创建 Controller

  • 运行命令 bin/rails g controller validation_codes create

    1
    2
    
      create  app/controllers/validation_codes_controller.rb
       route  get "validation_codes/create"
  • 删除自动添加生成的路由

修改前缀 添加目录 api/v1

  • app/controllers 中添加目录 api/v1
  • 将生成的 validation_codes_controller.rb 移到 app/controllers/api/v1 目录下
  • 修改 validation_codes_controller.rb 中类名,添加命名空间前缀 Api::V1::ValidationCodesController

    • 注意命名空间需要添加两个冒号隔开 Api::V1::

      1
      2
      3
      4
      5
      
      class Api::V1::ValidationCodesController < ApplicationController
      def create
      head 201
      end
      end
  • 启动服务 bin/rails s

  • 查看是否可以访问到 /api/v1/validation_codes

    • 使用命令 curl -X POST http://127.0.0.1:3000/api/v1/validation_codes -v
    • -v 查看返回结果中状态码

      1
      
      < HTTP/1.1 201 Created
  • 修改 head 202 再运行 curl -X POST http://127.0.0.1:3000/api/v1/validation_codes -v

  • 查看返回结果中状态码


5. 实现 items 的分页

实现分页的两种方案

  1. 使用 pageper_page 参数,见 kaminaripagy
  2. 使用 start_idlimit 参数,需要 id 是自增数字

创建 ItemsController

  • 运行命令 bin/rails g controller Api::V1::Items index create

    1
    2
    3
    4
    5
    6
    7
    
    create  app/controllers/api/v1/items_controller.rb
    route  namespace :api do
          namespace :v1 do
            get "items/index"
            post "items/create"
          end
        end
  • 删除自动生成的路由


6. $3


7. $3


8. $3


·未完待续·

参考文章

相关文章


  • 作者: Joel
  • 文章链接:
  • 版权声明
  • 非自由转载-非商用-非衍生-保持署名