中间件与鉴权
添加AutoJwt中间件
中间件可以存在于路由和控制器之间,也可以是在响应返回给用户之间。
添加自己的中间件,详细见Rail官网。也可以使用官网的中间件,执行bin/rails middleware
就可以看到rails自己的中间件。若是要自己创建中间件则需要在config/application.rb
中添加config.middleware.use AutoJwt
,并导入中间件文件位置require_relative '../lib/auto_jwt'
。
创建自己的中间件,创建文件lib/auto_jwt.rb
,使用中间件后修改mes_controller.rb
。详细内容见链接。
class AutoJwt
def initialize(app)
@app = app
end
def call(env)
header = env['HTTP_AUTHORIZATION']
jwt = header.split(' ')[1] rescue ''
payload = JWT.decode jwt, Rails.application.credentials.hmac_secret, true, { algorithm: 'HS256' } rescue nil
env['current_user_id'] = payload[0]['user_id'] rescue nil
@status, @headers, @response = @app.call(env)
[@status, @headers, @response]
end
end
class Api::V1::MesController < ApplicationController
def show
user_id = request.env['current_user_id'] rescue nil
# get user from user_id
user = User.find user_id
return head 404 if user.nil?
# render user
render json: { resource: user }
end
end
重启服务bin/rails server
,curl一下端口curl http://127.0.0.1:3000
,可以发现返回的和之前一样。
创建获取账目接口
单独执行文件的第几行测试,rspec spec/requests/item_spec.r:5
,详细内容见链接。
创建测试用例(按时间筛选)。
require 'rails_helper'
RSpec.describe "Items", type: :request do
describe "获取账目" do
it "分页" do
11.times { Item.create amount: 100 }
expect(Item.count).to eq 11
get '/api/v1/items'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 10
get '/api/v1/items?page=2'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
end
it "按时间筛选" do
item1 = Item.create amount: 100, created_at: Time.new(2018, 1, 2)
item2 = Item.create amount: 100, created_at: Time.new(2018, 1, 2)
item3 = Item.create amount: 100, created_at: Time.new(2019, 1, 1)
get '/api/v1/items?created_after=2018-01-01&created_before=2018-01-03'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 2
expect(json['resources'][0]['id']).to eq item1.id
expect(json['resources'][1]['id']).to eq item2.id
end
end
describe "create" do
it "can create an item" do
expect {
post '/api/v1/items', params: { amount: 99 }
}.to change { Item.count }.by 1
# by是否增1
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources']['id']).to be_an(Numeric)
expect(json['resources']['amount']).to eq 99
end
end
end
修改获取账目接口,带入查询时间的范围,但是有边界值的问题,还需要处理。
class Api::V1::ItemsController < ApplicationController
def index
items = Item.where({created_at: params[:created_after]..params[:created_before]})
.page(params[:page])
render json: { resources: items, pager: {
page: params[:page],
per_page: 100,
count: Item.count
} }
end
def create
item = Item.new amount: params[:amount]
if item.save
render json: { resources: item }
else
render json: { errors: item.errors }
end
end
end
时区导致测试失败
主要是因为有时区这个问题,创建时间的时候可以设置为Time.new(2018, 1, 1, 0, 0, 0, "Z")
或Time.new(2018, 1, 1, 0, 0, 0, "+00:00")
或直接写死时间'2018-01-01'
。修改时区的问题后在新增其他测试用例。详细内容见链接。
require 'rails_helper'
RSpec.describe "Items", type: :request do
describe "获取账目" do
it "分页" do
11.times { Item.create amount: 100 }
expect(Item.count).to eq 11
get '/api/v1/items'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 10
get '/api/v1/items?page=2'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
end
it "按时间筛选" do
item1 = Item.create amount: 100, created_at: '2018-01-02'
item2 = Item.create amount: 100, created_at: '2018-01-02'
item3 = Item.create amount: 100, created_at: '2019-01-01'
get '/api/v1/items?created_after=2018-01-01&created_before=2018-01-03'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 2
expect(json['resources'][0]['id']).to eq item1.id
expect(json['resources'][1]['id']).to eq item2.id
end
end
it "按时间筛选(边界条件)" do
item1 = Item.create amount: 100, created_at: Time.new(2018, 1, 1, 0, 0, 0, "Z")
get '/api/v1/items?created_after=2018-01-01&created_before=2018-01-02'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
it "按时间筛选(边界条件2)" do
item1 = Item.create amount: 100, created_at: '2018-01-01'
item2 = Item.create amount: 100, created_at: '2017-01-01'
get '/api/v1/items?created_after=2018-01-01'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
it "按时间筛选(边界条件2)" do
item1 = Item.create amount: 100, created_at: '2018-01-01'
item2 = Item.create amount: 100, created_at: '2019-01-01'
get '/api/v1/items?created_before=2018-01-02'
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
describe "create" do
it "can create an item" do
expect {
post '/api/v1/items', params: { amount: 99 }
}.to change { Item.count }.by 1
# by是否增1
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources']['id']).to be_an(Numeric)
expect(json['resources']['amount']).to eq 99
end
end
end
完善get/api/v1/items的文档
修改控制器的当前页面的默认值。
class Api::V1::ItemsController < ApplicationController
def index
items = Item.where({created_at: params[:created_after]..params[:created_before]})
.page(params[:page])
render json: { resources: items, pager: {
page: params[:page] || 1,
per_page: Item.default_per_page,
count: Item.count
} }
end
def create
item = Item.new amount: params[:amount]
if item.save
render json: { resources: item }
else
render json: { errors: item.errors }
end
end
end
编写接口测试。
require 'rails_helper'
require 'rspec_api_documentation/dsl'
resource "验证码" do
get "/api/v1/items" do
parameter :page, '页码'
parameter :created_after, '创建时间起点(筛选条件)'
parameter :created_before, '创建时间终点(筛选条件)'
with_options :scope => :resources do
response_field :id, 'ID'
response_field :amount, '金额(单位:分)'
end
let(:created_after) { '2020-10-10' }
let(:created_before) { '2020-11-11' }
example "获取账目" do
# 执行10次创建item的例子
11.times do Item.create amount: 100, created_at: '2020-10-30' end
do_request
expect(status).to eq 200
json = JSON.parse response_body
expect(json['resources'].size).to eq 10
end
end
end
鉴权
由于目前没有将用户之前进行区分,需要进行分离。在user的model层上定义两个方法,存取jwt,详细内容见链接。
class User < ApplicationRecord
validates :email, presence: true
def generate_jwt
payload = { user_id: self.id }
JWT.encode payload, Rails.application.credentials.hmac_secret, 'HS256'
end
def generate_auth_header
{Authorization: "Bearer #{self.generate_jwt}"}
end
end
在session_controller
中就可以直接使用方法,直接挂在user上。
require 'jwt'
class Api::V1::SessionsController < ApplicationController
def create
# 如果测试环境
if Rails.env.test?
return render status: :unauthorized if params[:code] != '123456'
else
canSignin = ValidationCode.exists? email: params[:email], code: params[:code], used_at: nil
return render status: :unauthorized unless canSignin
end
user = User.find_by_email params[:email]
if user.nil?
render status: :not_found, json: {error: '用户不存在'}
else
render status: :ok, json: { jwt: user.generate_jwt }
end
end
end
并在items_controller.rb
中加上鉴权判断,若请求头参数没有带上当前的user_id则返回401。
class Api::V1::ItemsController < ApplicationController
def index
current_user_id = request.env['current_user_id']
return head :unauthorized if current_user_id.nil?
items = Item.where({user_id: current_user_id})
.where({created_at: params[:created_after]..params[:created_before]})
.page(params[:page])
render json: { resources: items, pager: {
page: params[:page] || 1,
per_page: Item.default_per_page,
count: Item.count
} }
end
def create
item = Item.new amount: params[:amount]
if item.save
render json: { resources: item }
else
render json: { errors: item.errors }
end
end
end
重新编写测试用例,然后在终端执行rspec spec/requests/items_spec.rb
require 'rails_helper'
RSpec.describe "Items", type: :request do
describe "获取账目" do
it "分页,未登录" do
user1 = User.create email: '1@qq.com'
user2 = User.create email: '2@qq.com'
11.times { Item.create amount: 100, user_id: user1 }
11.times { Item.create amount: 100, user_id: user2 }
get '/api/v1/items'
expect(response).to have_http_status 401
end
it "分页" do
user1 = User.create email: '1@qq.com'
user2 = User.create email: '2@qq.com'
11.times { Item.create amount: 100, user_id: user1.id }
11.times { Item.create amount: 100, user_id: user2.id }
get '/api/v1/items', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 10
get '/api/v1/items?page=2', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
end
it "按时间筛选" do
user1 = User.create email: '1@qq.com'
item1 = Item.create amount: 100, created_at: '2018-01-02', user_id: user1.id
item2 = Item.create amount: 100, created_at: '2018-01-02', user_id: user1.id
item3 = Item.create amount: 100, created_at: '2019-01-01', user_id: user1.id
get '/api/v1/items?created_after=2018-01-01&created_before=2018-01-03', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 2
expect(json['resources'][0]['id']).to eq item1.id
expect(json['resources'][1]['id']).to eq item2.id
end
it "按时间筛选(边界条件)" do
user1 = User.create email: '1@qq.com'
item1 = Item.create amount: 100, created_at: Time.new(2018, 1, 1, 0, 0, 0, "Z"), user_id: user1.id
get '/api/v1/items?created_after=2018-01-01&created_before=2018-01-02', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
it "按时间筛选(边界条件2)" do
user1 = User.create email: '1@qq.com'
item1 = Item.create amount: 100, created_at: '2018-01-01', user_id: user1.id
item2 = Item.create amount: 100, created_at: '2017-01-01', user_id: user1.id
get '/api/v1/items?created_after=2018-01-01', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
it "按时间筛选(边界条件3)" do
user1 = User.create email: '1@qq.com'
item1 = Item.create amount: 100, created_at: '2018-01-01', user_id: user1.id
item2 = Item.create amount: 100, created_at: '2019-01-01', user_id: user1.id
get '/api/v1/items?created_before=2018-01-02', headers: user1.generate_auth_header
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources'].size).to eq 1
expect(json['resources'][0]['id']).to eq item1.id
end
end
describe "create" do
it "can create an item" do
expect {
post '/api/v1/items', params: { amount: 99 }
}.to change { Item.count }.by 1
# by是否增1
expect(response).to have_http_status 200
json = JSON.parse(response.body)
expect(json['resources']['id']).to be_an(Numeric)
expect(json['resources']['amount']).to eq 99
end
end
end
重构文档测试代码
重构API文档后,可以执行bin/rake docs:generate
,然后启动http-server查看文档,执行http-server doc/api
,详细内容见链接。
require 'rails_helper'
require 'rspec_api_documentation/dsl'
resource "验证码" do
get "/api/v1/items" do
# 验证方式:使用基础验证, 值为auth
authentication :basic, :auth
parameter :page, '页码'
parameter :created_after, '创建时间起点(筛选条件)'
parameter :created_before, '创建时间终点(筛选条件)'
with_options :scope => :resources do
response_field :id, 'ID'
response_field :amount, '金额(单位:分)'
end
let(:created_after) { '2020-10-10' }
let(:created_before) { '2020-11-11' }
let(:current_user) { User.create email: '1@qq.com' }
let(:auth) { "Bearer #{current_user.generate_jwt}" }
example "获取账目" do
11.times do Item.create amount: 100, created_at: '2020-10-30',user_id: current_user.id end
do_request
expect(status).to eq 200
json = JSON.parse response_body
expect(json['resources'].size).to eq 10
end
end
end