坚持在RubyonRails 5上重构类

wqsoz72f  于 2021-09-29  发布在  Java
关注(0)|答案(1)|浏览(322)

我有一个具有不同方法的类,但在这些方法上,我需要在执行一些调用之前检查访问令牌

class SomeClass
    def initialize
        @client = SomeModule::Client.new
    end
    def get_intervention_chart(subId:, projectId:, interventionId:)
        @client.check_presence_of_access_token()
        SomeModule::Service::Project.new(@client).get_intervention_chart(subId: subId, projectId: projectId, interventionId: interventionId)
    end

    def get_intervention_documents(subId:, projectId:, interventionId:)
        @client.check_presence_of_access_token()
        SomeModule::Service::Project.new(@client).get_intervention_documents(subId: subId, projectId: projectId, interventionId: interventionId)
    end
end

如您所见,我调用了“check_presence_of_access_token”方法,该方法检查访问令牌是否存在,如果不存在,则获取另一个令牌并将其存储在文件中。
这是我的客户机类:

class Client
        class Configuration
            attr_accessor :access_token 
            attr_reader :access_token_path, :endpoint, :client_id, :client_secret, :subId

            def initialize
                @access_token = ''
                @access_token_path = Rails.root.join('tmp/connection_response.json')
                @endpoint = ENV['TOKEN_ENDPOINT']
                @client_id    = ENV['CLIENT_ID']
                @client_secret = ENV['CLIENT_SECRET']
                @subId = "SOME_ID"
            end
        end
        def initialize
            @configuration = Configuration.new
        end

        # Check if the file 'connection_response' is present and if the token provided is still valid (only 30 min)
        def check_presence_of_access_token          
            if File.exist?(self.configuration.access_token_path.to_s)
                access_token = JSON.parse(File.read(self.configuration.access_token_path.to_s))["access_token"]
                if access_token
                    jwt_decoded = JWT.decode(access_token, nil, false).first
                    # we want to check if the token will be valid in 60s to avoid making calls with expired token
                    if jwt_decoded["exp"] > (DateTime.now.to_i + 60)
                        self.configuration.access_token = access_token
                        return
                    end
                end
            end
            get_token()
        end
        def get_token
            config_hash = Hash.new {}
            config_hash["grant_type"] = "client_credentials"
            config_hash["client_id"] = self.configuration.client_id
            config_hash["client_secret"] = self.configuration.client_secret

            response = RestClient.post(self.configuration.endpoint, config_hash, headers: { 'Content-Type' => 'application/x-www-form-urlencoded' })
            response_body = JSON.parse(response.body)
            self.configuration.access_token = response_body["access_token"]

            stock_jwt(response_body.to_json)
        end

        def stock_jwt(response_body)
            File.open(self.configuration.access_token_path.to_s, 'w+') do |file|
                file.write(response_body)
            end
        end
end

我不知道如何重构这个,你能帮我吗?

e5nszbig

e5nszbig1#

oo语言的一般原则是“懒惰”,并尽可能推迟决策。我们将使用该原则重构代码,使令牌在到期时自动刷新自身,和/或在尚未刷新时自动获取自身。
还有一组原则统称为固体。我们也将使用这些原则。
关于方法和模块,我要提到的最后一个原则是“越小越好”。再加上solid中的“s”(单一职责),您将看到重构包含更多但更小的方法。
撇开原则不谈,从问题陈述中不清楚代币是短期的(只持续一个“会话”)还是长期的(例如:持续时间比单个会话更长)。
如果令牌是长寿命的,那么将其存储到文件中是可以的,只要使用它的进程在同一个系统上。
如果多个web服务器将使用此代码,那么除非每个服务器都有自己的令牌,否则应使用某种数据存储(如redis、mysql或postgres)在所有系统中共享令牌。
由于您的代码使用的是一个文件,我们将假定同一系统上的多个进程可能正在共享该令牌。
根据这些原则和假设,下面是对代码的重构,使用文件存储令牌,使用“惰性”延迟模块化逻辑。

class Client
  class Configuration
    attr_accessor :access_token
    attr_reader :access_token_path, :endpoint, :client_id, :client_secret, :subId

    def initialize
      @access_token      = nil
      @access_token_path = Rails.root.join('tmp/connection_response.json')
      @endpoint          = ENV['TOKEN_ENDPOINT']
      @client_id         = ENV['CLIENT_ID']
      @client_secret     = ENV['CLIENT_SECRET']
      @sub_id            = "SOME_ID"
    end
  end

  attr_accessor :configuration
  delegate :access_token, :access_token_path, :endpoint, :client_id, :client_secret, :sub_id,
           to: :configuration

  TOKEN_EXPIRATION_TIME = 60 # seconds

  def initialize
    @configuration = Configuration.new
  end

  # returns a token, possibly refreshed or fetched for the first time
  def token
    unexpired_token || new_token
  end

  # returns an expired token
  def unexpired_token
    access_token unless token_expired?
  end

  def access_token
    # cache the result until it expires
    @access_token ||= JSON.parse(read_token)&.fetch("access_token", nil)
  end

  def read_token
    File.read(token_path)
  end

  def token_path
    access_token_path&.to_s || raise("No access token path configured!")
  end

  def token_expired?
    # the token expiration time should be in the *future*
    token_expiration_time.nil? || 
      token_expiration_time < (DateTime.now.to_i + TOKEN_EXPIRATION_TIME)
  end

  def token_expiration_time
    # cache the token expiration time; it won't change
    @token_expiration_time ||= decoded_token&.fetch("exp", nil)
  end

  def decoded_token
    @decoded_token ||= JWT.decode(access_token, nil, false).first
  end

  def new_token
    @access_token = store_token(new_access_token)
  end

  def store_token(token)
    @token_expiration_time = nil # reset cached values
    @decoded_token = nil
    IO.write(token_path, token)
    token
  end

  def new_access_token
    parse_token(request_token_response)
  end

  def parse_token(response)
    JSON.parse(response.body)&.fetch("access_token", nil)
  end

  def request_token_response
    RestClient.post(
      endpoint,
      credentials_hash,
      headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
    )
  end

  def credentials_hash
    {
      grant_type:    "client_credentials",
      client_id:     client_id || raise("Client ID not configured!"),
      client_secret: client_secret || raise("Client secret not configured!")
    }
  end
end

那么,这是如何工作的呢?
假设客户端代码使用 token ,只是评估 token 方法将导致检索或刷新未过期的令牌(如果存在),或获取新令牌(如果不存在)。
因此,在使用前不需要“检查”令牌 @client 连接。当 @client 连接使用令牌,正确的事情就会发生。
不会更改的值被缓存,以避免重做生成它们的逻辑。例如:不需要反复解码jwt字符串。
当当前令牌时间到期时 token_expired? 将变为true,导致其调用者返回nil,导致该调用者获取 new_token ,然后将其存储。
这些小方法的最大优点是,每个方法都可以独立测试,因为它们都有一个非常简单的目的。
祝你的项目好运!

相关问题