zoukankan      html  css  js  c++  java
  • 后端实现的主要考虑

    当api设计确定以后,剩下的工作就是实现了。我跳过架构和设计这两个环节,因为用了Rails框架之后,这两个环节的工作简而又简。加之项目本身在前期并不庞大,还不需要过多的设计。

    首先介绍一下Rails框架的基本结构吧。Rails框架是一款用Ruby开发的后端MVC框架,由于我只是开发API应用,其中的View层就被我无端地简化了。我的重点工作只剩下定义Model层和实现Controller层即可,最终是在Controller层返回JSON数据,没有显示地定义视图。一个Rails应用的目录结构如下:

      app/    - 应用主目录

        controllers/  - 控制器所在目录

        models/       - 模型所在目录

      config/ - 配置所在目录

        routes.rb     - 路由配置文件

        mongoid.rb - MongoDB数据库配置文件

    其实还有其他的目录,只是我没说而已。Rails框架很自然地为你规定好项目的基本结构,我剩下的工作就是往里面填自己的代码。这一过程大体是:

    1. 在app/models下面定义自己的Model(我之前说过,我用的是Mongoid为MongoDB数据库的collection定义模式)

    2. 在app/controlles下面定义自己的Controller。这里面,Controller的任何方法都可以配置为API接口的处理方法。Controller的方法大致就是处理前端的API请求逻辑,有时不可避免地要调用Model层的一些东西。

    3. 在routes.rb中配置API与Controller中的方法的映射关系。

    不过,虽说是填代码,但还是要有很多要考虑的地方。

    DRY:Don't Repeat Yourself

    我把它视为首要的考虑。如果项目中存在重复代码,就可以看成存在设计缺陷。

    一个典型的例子是这样的:我之前说过,我的API设计中存在分页(from,size),sort,include,with_total,just_total等控制,其中from,size,sort是Model查询时的控制参数,include,with_total, just_total是作为返回JSON数据时的控制参数。我的所有List接口,即形如GET /users这样的接口都是有这些控制参数的。这其中要进行如下处理:

    1 from, size = 合适的值,用于分页
    2 sort = 将params[:sort]转化成能被Model识别的形式
    3 cond = 将params[:cond]转化为能被Model识别的形式
    4 users = User.where(cond).order(sort).limit(size).offset(from)
    5 includes = 将params[:include]转化为能够被as_json识别的形式
    6 list = 基本就是users.as_json(includes), 但还要处理with_total,just_total的情况
    7 render json: list

    基本来说,前3行代码和后3行代码会在其他诸如GET /tickets这样的接口的处理方法中调用,它们就是重复代码。不能像这样一个一个都重复写到每一个方法中去,要把它们提取到公共的方法中来。我采取的做法是,把前三行代码封装到一个叫做query_ready的方法中去,后3行代码提取到名为respond的方法中去,这两个方法都被定义在ApplicationController中。由于项目中的每一个Controller都继承自ApplicationController,所以可以直接调用这两个方法。如上,上面的7行调用就转化成下面的形式:

    respond query_ready(User)

    对于处理GET /tickets的接口,可以实现成

    respond query_ready(Ticket)

    单纯的代码 - 把异常分离

    这里主要介绍的是Rails的拦截器技巧。我的想法是,Controller中的每一个处理方法都尽量只考虑正常的情况,而把异常分离到其他的回调代码中去。一个很简单的例子:

    /tickets/:id/refund 用户退票的接口。用户退票,首先需要是一个已登录的用户;其次,这个已登录用户持有该票,因为你不能修改他人的票的状态。一个典型的实现如下:

    if not login:
        render_error '未登录'
    else
        ticket = Ticket.find(params[:id])
        if ticket.owner != login_user
            render_error '无权限'
        else
            ticket.state = :refund
            render_success 'OK'

    上面的代码杂糅了正常的逻辑和出错的逻辑,我在其中做了分离。出错的逻辑分离到两个方法分别是require_login和require_permission中去,然后用before_action回调进行控制。大致就是下面的样子了:

    #这个方法和下一个方法可以上升到ApplicationController中去,从而让所有Controller都能用
    def require_login:
        if user_not_login
            render status: 401, json: {error: '未登录'}
        end
    end
    
    def require_permission:
        if not resource.permit?(login_user)
            render status: 403, json: {error: '无权限'}
        end
    end
    
    before_action :require_login
    before_action :require_permission
    
    def refund
        Ticket.find(params[:id]).refund = :refund
        render json: {success: 'OK'}
    end

    在Rails中before_action的逻辑是这样的,首先执行before_action声明的逻辑(即require_loginrequire_permission),如果其中render了status非2xx系列(可能也包含3xx系列,不详)的状态码,就不会执行正常的逻辑(即refund)。

    这样做的好处是代码结构更加清晰,如今refund只需要考虑正常情况下的处理逻辑即可,简短的两行代码清晰异常。更为重要的是,它遵循了DRY原则。因为未登录和无权限两项异常是很多接口都要处理的。

    在上面的处理中,还有个小插曲,反应了Rails中元编程的便利性。在上面的resource.permit?(login_user)的这行代码中,permit?方法倒还好,在每个Model中直接定义就好。但是resource方法就有很多技巧可言了。在接口/users/:id当中就应该是User.find(:id),在/tickets/:id中就应该是Ticket.find(:id),这还要包括诸如/tickets/:id/refund, /users/:user_id/tickets等情形。同时处理这么多种情况,就要充分利用Rails提供的元编程特性,这里主要涉及到单复数转换,帕斯卡命名法向驼峰式命名法的转换以及Ruby类其实就是常量这些技巧。我把实际项目中的代码贴在下方。

    def resource
    return @resource if @resource

    path_param_names = request.path_parameters.keys
    if path_param_names.include? :id
    id_param_name = :id
    resource_name = params[:controller].singularize
    else
    id_param_name = path_param_names.find {|name| name.to_s.end_with? 'id'}
    resource_name = id_param_name.to_s.sub(/_id$/, '')
    end
    model_class = resource_name.camelize.constantize
    id = params[id_param_name]
    @resource = model_class.find(id)
    end

    另一个异常拦截是使用rescue_from. 这是一个真正的异常拦截器,它拦截的是真正的Ruby异常对象。一个典型的例子是访问/tickets/123的时候找不到id为123的Ticket。这时应该抛出404Error。原本的逻辑是:

    def show_ticket
        begin
            ticket = Ticket.find(params[:id])
            render json: ticket
        rescue Mongoid::Errors::DocumentNotFound
            render status: 404, json: {error: '您访问的资源不存在'}
        end
    end

    实际中我做了分离,像下面:

    #可以提到ApplicationController中以让所有Controller都可以用到
    rescue_from Mongoid::Errors::DocumentNotFound do
        render status: 404, json: {error: '您访问的资源不存在'}
    end
    
    def show_ticket
        ticket = Ticket.find(params[:id])
        render json: ticket #只用考虑正常的逻辑即可
    end

    我在项目中很多地方使用before_action, rescue_from做了类似的处理。before_action, after_action, round_action和rescue_from联合构成了Rails的切面编程体系。

  • 相关阅读:
    POJ 3468 A Simple Problem with Integers
    BZOJ 4430 Guessing Camels
    POJ 2309 BST
    POJ 1990 MooFest
    cf 822B Crossword solving
    cf B. Black Square
    cf 828 A. Restaurant Tables
    Codefroces 822C Hacker, pack your bags!
    [HDU 2255] 奔小康赚大钱
    [BZOJ 1735] Muddy Fields
  • 原文地址:https://www.cnblogs.com/starstone/p/4656187.html
Copyright © 2011-2022 走看看