1.12.1-postalpha.28 • Published 5 years ago

@fxjs/webx v1.12.1-postalpha.28

Weekly downloads
29
License
ISC
Repository
-
Last release
5 years ago

@fxjs/webx

Build Status NPM version

fibjs webx 应用程序 api/视图和扩展资源框架

Install

npm install @fxjs/webx [--save]

Test

npm test

建立基础脚本

const http = require('http');
const util = require('util')
const Session = require('@fxjs/session')
const App = require('../');

var app = new App('sqlite:test.db', {
  uuid: true
});
app.db.use(require('./defs/person'));

var session = new Session(new util.LruCache(20000), {
  timeout: 60 * 1000
});

var svr = new http.Server(8080, [
  session.cookie_filter,
  {
    '/1.0': app
  }
]);
svr.run();

其中 person 是 Model 定义模块,内容如下:

module.exports = db => {
  db.define('person', {
    name: String,
    sex: ["male", "female"],
    age: Number
  });
};

这是一个标准的 orm 定义,同样可以使用 orm 的其它功能,比如类型检查,事件等。

API 数据格式

对于 POST 和 PUT 请求,请求的主体必须是 JSON 格式,而且 HTTP header 的 Content-Type 需要设置为 application/json。

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"name": "tom","sex":"male","age":23}' \
  http://localhost/1.0/person/57fbbdb0a2400000

对于所有的请求,响应格式都是一个 JSON 对象。

一个请求是否成功是由 HTTP 状态码标明的。一个 2XX 的状态码表示成功,而一个 4XX 表示请求失败。当一个请求失败时响应的主体仍然是一个 JSON 对象,但是总是会包含 code 和 message 这两个字段,你可以用它们来进行调试。举个例子,如果一个请求权限认证失败,会返回以下信息:

{
  "code": 4030501,
  "message": "The operation isn’t allowed for clients due to class-level permissions."
}

code 编码分为三个部分,前三位 403 表示错误类型,05 表示数据表编号,01 表示详细错误编码。

对于 GET 请求,通常会返回对象数据,根据 GET 请求的地址不同,可能会返回一个对象,也可能会返回一个数组。比如:

{
  "name": "tom",
  "sex": "male",
  "age": 23
}

或者:

[
  {
    "name": "tom",
    "sex": "male",
    "age": 23
  },
  {
    "name": "lily",
    "sex": "female",
    "age": 22
  }
]

特殊字段

对象数据中,有四个特殊含义的字段,是不允许通过 API 更改的。分别是 id, updatedAt, createdAt, createdBy.

其中 id, updatedAt, createdAt 单个字段会自动创建和修改。createdBy 则需要自行指定类型。

基础对象访问 API

完成这样的数据定义,便直接拥有了一整套符合 REST api 规范的接口调用:

urlmethodaction
/1.0/:classNamePOST创建新对象
/1.0/:className/:idGET读取对象
/1.0/:className/:idPUT修改对象
/1.0/:className/:idDELETE删除对象
/1.0/:classNameGET查询对象列表

创建新对象

为了创建一个新的对象,应该向 class 的 URL 发送一个 POST 请求,其中应该包含对象本身。例如,要创建如上所说的对象:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "tom","sex":"male","age":23}' \
  http://localhost/1.0/person

当创建成功时,HTTP 的返回是 201 Created,响应的主体是一个 JSON 对象,包含新的对象的 objectId 和 createdAt 时间戳:

{
  "createdAt": "2017-11-25T01:39:35.931Z",
  "id": "57fbbdb0a2400000"
}

读取对象

当你创建了一个对象时,你可以通过发送一个 GET 请求到返回的 header 的 Location 以获取它的内容。例如,为了得到我们上面创建的对象:

curl -X GET http://localhost/1.0/person/57fbbdb0a2400000

返回的主体是一个 JSON 对象包含所有用户提供的 field 加上 createdAtupdatedAtid 字段:

{
  "name": "tom",
  "sex": "male",
  "age": 23,
  "createdAt": "2017-11-25T01:39:35.931Z",
  "updatedAt": "2017-11-25T01:39:35.931Z",
  "id": "57fbbdb0a2400000"
}

通过设置返回字段 keys,可以定制返回的内容,keys 的内容是一个以 , 分割的字段名称字符串,:

curl -X GET http://localhost/1.0/person/57fbbdb0a2400000?keys=name%2Csex

将返回:

{
  "name": "tom",
  "sex": "male"
}

修改对象

为了更改一个对象已经有的数据,你可以发送一个 PUT 请求到对象相应的 URL 上,任何你未指定的 key 都不会更改,所以你可以只更新对象数据的一个子集。例如,我们来更改我们对象的一个 age 字段:

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"age": 25}' \
  http://localhost/1.0/person/57fbbdb0a2400000

返回的 JSON 对象会包含 updatedAtid 字段,表明更新发生的时间:

{
  "updatedAt": "2017-11-25T01:39:35.931Z",
  "id": "57fbbdb0a2400000"
}

删除对象

为了删除一个对象,可以发送一个 DELETE 请求到指定的对象的 URL,比如:

curl -X DELETE http://localhost/1.0/person/57fbbdb0a2400000

查询对象列表

通过发送一个 GET 请求到类的 URL 上,不需要任何 URL 参数,你就可以一次获取多个对象。下面就是简单地获取所有用户:

curl -X GET http://localhost/1.0/person

返回的值就是一个 JSON 对象包含了 results 字段,它的值就是对象的列表:

[
  {
    "name": "tom",
    "sex": "male",
    "age": 23,
    "createdAt": "2017-11-25T01:39:35.931Z",
    "updatedAt": "2017-11-25T01:39:35.931Z",
    "id": "57fbbdb0a2400000"
  },
  {
    "name": "lily",
    "sex": "female",
    "age": 22,
    "createdAt": "2017-11-25T01:39:35.931Z",
    "updatedAt": "2017-11-25T01:39:35.931Z",
    "id": "57fbbdb0a2400001"
  }
]

keys 字段定制

与对象查询一样,查询列表时可以通过设定 keys 来定制返回结果所包含的字段。keys 的内容是一个以 , 分割的字段名称字符串,例如:

curl -X GET http://localhost/1.0/person?keys=name%2Cage

将指定只返回 nameage 两个字段。

where 过滤条件

通过 where 参数的形式可以对查询对象做出约束。

where 参数的值应该是 JSON 编码过的。就是说,如果你查看真正被发出的 URL 请求,它应该是先被 JSON 编码过,然后又被 URL 编码过。最简单的使用 where 参数的方式就是包含应有的 key 和 value。例如,如果我们想要搜索名字为 tom 的用户,我们应该这样构造查询:

curl -X GET http://localhost/1.0/person?where=%7B%22name%22%3A%22tom%22%7D

where 的值为一个 urlencode 后的 JSON 字符串,内容为:{"name":"tom"}

除了完全匹配一个给定的值以外,where 也支持比较的方式,比如包含。where 参数支持如下选项:

keyoperationsample
eq等于{"name":{"eq":"tom"}} 或者 {"name":"tom"}
ne不等于{"name":{"ne":"tom"}}
gt大于{"age":{"gt":"24"}}
gte大于等于{"age":{"gte":"24"}}
lt小于{"age":{"lt":"24"}}
lte小于等于{"age":{"lte":"24"}}
like模糊查询{"name":{"like":"%m"}}
not_like模糊查询{"name":{"not_like":"%m"}}
between区间比较{"age":{"between":22,25}}
not_between区间比较{"age":{"not_between":22,25}}
in枚举{"name":{"in":"tom","lily"}}
not_in枚举{"name":{"not_in":"tom","lily"}}
or或运算{"or":{"name":"tom"},{"age":24}}

skip 跳过记录

通过 skip 选项,可以跳过指定的记录数,达到翻页的效果。

curl -X GET http://localhost/1.0/person?skip=100

limit 返回记录限制

通过 limit 选项,可以限制返回记录数,limit 的有效数字为 1-1000,缺省为 100。

curl -X GET http://localhost/1.0/person?limit=100

order 指定排序方式

通过 order 选项,设定返回结果集的排序方式,字段名前包含 - 时为倒序。

curl -X GET http://localhost/1.0/person?order=-id

count 返回结果总数

在请求时增加 count 可以在返回指定内容的同时返回结果集的总数。

curl -X GET http://localhost/1.0/person?count=1&limit=1

此时返回结果将包含 countresults 两个字段,分别包含总数和结果:

{
  "count": 2,
  "results": [
    {
      "name": "tom",
      "sex": "male",
      "age": 23,
      "createdAt": "2017-11-25T01:39:35.931Z",
      "updatedAt": "2017-11-25T01:39:35.931Z",
      "id": "57fbbdb0a2400000"
    }
  ]
}

建立扩展对象

通过 orm 定义 hasOne 和 hasMany,可以定义对象之间的关联关系,并在 API 上体现出来,例如:

module.exports = db => {
  var Person = db.models.person;

  var Pet = db.define('pet', {
    name: String
  });

  Person.hasMany('pets', Pet);
};

扩展对象访问 API

下面是扩展对象的 API 定义:

urlmethodaction
/1.0/:className/:id/:extendNamePUT设置扩展对象
/1.0/:className/:id/:extendNamePOST创建扩展对象
/1.0/:className/:id/:extendName/:ridGET读取扩展对象
/1.0/:className/:id/:extendName/:ridPUT修改扩展对象
/1.0/:className/:id/:extendName/:ridDELETE删除扩展对象
/1.0/:className/:id/:extendNameGET查询扩展对象列表

设置扩展对象

设置扩展对象是将两个独立的对象建立联系。比如 tom 领养了一只叫 cat 的宠物,可以用下面的操作实现:

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"id": "57fbbdb0a2400007"}' \
  http://localhost/1.0/person/57fbbdb0a2400000/pets

在调用里需要在 body 内指定 cat 的 id。

创建扩展对象

直接创建扩展对象,可以在创建对象的同时,建立对象之间的联系。比如:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"name": "cat"}' \
  http://localhost/1.0/person/57fbbdb0a2400000/pets

将创建一只名叫 cat 的宠物,并建立与 tom 的关联关系。

读取扩展对象

读取扩展对象与读取基础对象很相似,也同样支持 keys 选项:

curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

修改扩展对象

读取扩展对象与读取基础对象很相似:

curl -X PUT \
  -H "Content-Type: application/json" \
  -d '{"name": "cat 1"}' \
  http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

删除扩展对象

删除扩展对象不会删除对象本身,只会解除对象之间的关系:

curl -X DETELE http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

注意 在设定了 reverse 名的 hasOne 关系中, revesed 的一方无法对发起方做删除操作, @fxjs/orm 底层认为该操作不合理. 尽管 @fxjs/webx 的 ACL/OACL 机制在此场景下可以正确计算出用户预期的 ACL 值, 但限于 @fxjs/orm 的支持不足, 我们无法进行这样的操作.

查询扩展对象列表

查询扩展对象列表与查询基础对象列表很相似,也同样支持 keys 以及条件过滤等选项:

curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets

ACL

可以通过定义 Model 的 ACL 控制数据权限。比如:

const orm = require('@fxjs/orm');

module.exports = db => {
  db.define('blog', {
    title: String,
    detail: String,
    note: String
  }, {
    ACL: function(session) {
      return {
        "*": {
          "*": false
        },
        "57fbbdb0a2400000": {
          "*": true
        },
        "roles": {
          "user": {
            "read": true
          }
        }
      };
    }
  });
};

如果定义 Model 时未指定 ACL,则等同于设定了缺省权限:

{
  "*": {
    "*": true
  }
}

主体

ACL 主体描述有三种,用户 id,用户 role*, id 表示一个具体的用户,role 表示具有某个角色的用户,* 表示所有用户:

主体描述优先级
id具体用户的 id1
role用户组名2
*全体3

在检查权限时,首先会匹配 id 对应的权限,如果未指定,则匹配用户 role 对应的权限,如果仍为指定,则查看是否指定了 * 的权限,如果 * 也未指定,则没有权限。

比如上面的权限配置,指定了 user 用户组可以阅读,用户 57fbbdb0a2400000 拥有全部权限,而其它用户没有任何权限。

权限

ACL 根据 API 行为将权限分类五种:

权限描述允许类型
create创建对象true / false / array
read读取对象true / false / array
write修改对象true / false / array
delete删除对象true / false
find查询对象列表true / false
*匹配所有权限true / false / array

权限制定 true 为允许访问,为 false 为禁止访问,为 array 为只允许指定的字段的访问。deletefind 不接受 array,如果设置了 array 则视同为 true。如果指定的权限不存在,则匹配同主体下的 * 权限。若都不存在,再一次查询下一优先级的主体。

比如以上例子,如果需要设定 user 只允许读取 titledetail,其他人可以读取 title,则可以这样设定:

{
  "*": {
    "*": false,
    "read": ['title']
  },
  "57fbbdb0a2400000": {
    "*": true
  },
  "roles": {
    "user": {
      "read": ['title', 'detail']
    }
  }
}

对象权限

在 Model 上设定的是整个类的权限,如果需要对具体的对象设定权限,可以通过设置 OACL 来实现:

module.exports = db => {
  db.define('person', {
    name: String,
    sex: ["male", "female"],
    age: Number
  }, {
    ACL: function(session) {
      return {
        "*": {
          "*": false
        }
      }
    },
    OACL: function(session) {
      var _acl = {};
      if(this.id === session.id)
        _acl[session.id] = {
          "*": true
        };

      return _acl;
    }
  });
};

在这个例子中,当访问者是对象本人时,将被允许全部操作,否则禁止一切访问。会按照以下步骤检查权限:

  • person[57fbbdb0a2400000] => OACL
  • person => ACL

扩展对象权限

扩展对象的访问权限控制和基础对象权限相似,唯一不同的是在 ACL 需要单独指定:

module.exports = db => {
  var Person = db.define('person', {
    name: String,
    sex: ["male", "female"],
    age: Number
  },{
    ACL: function(session) {
      return {
        "*": {
          "read": ['name', 'sex'],
          "extends": {
            "pets": {
              "read": true,
              "find": true
            }
          }
        }
      }
    },
    OACL: function(session) {
      var _acl = {};
      if(this.id === session.id)
        _acl[session.id] = {
          "*": true,
          "extends": {
            "pets": {
              "*": true
            }
          }
        };

      return _acl;
    }
  });

  var Pet = db.define('pet', {
    name: String
  });

  Person.hasMany('pets', Pet);
};

这个定义中,任何人都可以查阅个人信息的 namesex,并自由查阅和搜索他的 pets,用户本人可以操作自己的所有数据,以及拥有自己宠物信息的全部权限。

在检查扩展对象的访问权限时,会分别检查对象权限和扩展对象权限。比如以下请求:

curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

会按照以下步骤检查权限:

  • pets[57fbbdb0a2400007] => OACL
  • person[57fbbdb0a2400000] => OACL => extends => pets
  • person => ACL => extends => pets
  • pets => ACL

Utils

类型

如果要使用 @fxjs/webx 的高级特性, 你需要了解至少以下类型. 更多的类型可参见 @types/app.d.ts

interface FibAppORMModelFunction {
    (req: FibAppReq, data: FibAppReqData): FibAppModelFunctionResponse
}

interface FibAppSetupChainFn {
    (origReq: FibAppHttpRequest, classname: string, func: FibAppORMModelFunction): void;
}

interface FibAppReq {
    session: FibAppSession
    query: FibAppReqQuery
    request?: FibAppHttpRequest
    error?: APPError
}

interface FibAppReqData {
    [key: string]: any;
}

Model Function

可以为 Model 定义 api,对于复杂数据操作, 可以通过自定义 Function 来完成,

绝大多数权限可以通过 ACL 控制完成,不需要通过 Function 来完成基于对象的权限. Function 可用于完成一些复杂的操作,

  • 基于数据的权限, 比如根据审批状态,赋予不同用户组权限
  • 多项修改, 比如需要修改多条数据库记录
  • 基础 Rest 操作的组合
  • 其它任何你认为需要的操作

Model Function 都是 FibAppORMModelFunction 类型, 需通过 POST /:classname/:func 的方式进行调用, 在调用到 func 之前, request 已通过 app.filterRequest 进行过滤.

  • app.diagram

绘制数据模型

在完成数据定义以后,可以使用 app.diagram() 绘制数据模型的 svg 格式类图,保存至文件会得到类似下面的图像: diagram

  • app.dbPool

app.dbapp.dbPool 的别名, 它本质上是一个 fib-pool 对象

  • app.api

使用 app.api 定制高级操作

通过内部方法, 直接进行 rest 风格的操作, 详情可参考 @types/app.d.ts 中的 FibAppInternalApis.

  • app.api.post: FibAppIneternalApiFunction__Post
  • app.api.get: FibAppIneternalApiFunction__Get
  • app.api.find: FibAppIneternalApiFunction__Find
  • app.api.put: FibAppIneternalApiFunction__Put
  • app.api.del: FibAppIneternalApiFunction__Del
  • app.api.eget: FibAppIneternalApiFunction__Eget
  • app.api.efind: FibAppIneternalApiFunction__Efind
  • app.api.epost: FibAppIneternalApiFunction__Epost
  • app.api.eput: FibAppIneternalApiFunction__Eput
  • app.api.edel: FibAppIneternalApiFunction__Edel
  • app.api.elink: FibAppIneternalApiFunction__Elink

如果你对它们的实现感兴趣, 可以参考 src/classes 目录下中的实现, 其中单实体的操作(post, get, find, put, del)在 src/classes/base.ts 中; 对实体的扩展示例的操作(eget, efind, epost, eput, edel, elink)的实现在 src/classes/extend.ts

你可以在 Model Function 中调用 app.api 上的 rest 风格函数, 来定制属于你的函数, 比如

module.exports = db => {
  var Person = db.define('person', {
    name: String,
    sex: ["male", "female"],
    age: Number
  }, {
    functions: {
      /**
       * getUserByNicknames 为 FibAppORMModelFunction 类型
       * 
       * @param fibAppReq
       *    fibAppReq 包含了
       *    - session
       *    - query object
       *    - 原生的 request 信息
       *        - 如果 request 在传给 @fxjs/webx 之前经过过滤被挂载了别的属性, 这些属性也有效, 比如 @fxjs/session 对 request 添加的字段
       *          - request.session, 与上一层的 session 为同一对象
       *          - request.sessionid
       * 
       * @param fibAppReqData
       *    来自 POST 请求, `request.json()`, 默认为 {}
       */
      getUserByNicknames (fibAppReq, fibAppReqData) {
        // rest get 操作
        var data = app.api.get(fibAppReq, db, Person, fibAppReqData.id)
        // rest post 操作
        var postRes = app.api.post(fibAppReq, db, Person, fibAppReqData.id, {
          name: 'test',
          sex: 'male',
          age: 18
        })

        if (postRes.error)
          throw postRes.error

        // 方法的返回值必须为 FibAppModelFunctionResponse 类型
        return {
          success: postRes.success
        }
      }
    }
  });
};

FibAppInternalApis 中的所有所有 rest 操作函数, 内部都经过了 app.filterRequest 过滤.

  • app.filterRequest: FibAppSetupChainFn

注意该函数无返回值, 而是以最后一个参数作为回调函数. 更多详情可参考 @types/app.d.ts

使用 app.filterRequest 为 app 定制个性化的路由

app.filterRequest 是上述所有的实体相关的操作函数(所有的 rest 操作函数, 和可定制的 Model Function)的前置条件, 它主要做了两件事情:

  1. 过滤原生的 HttpRequest 对象 request 为 FibAppReq 对象, 并传给 func 作为第一个参数;
  2. request.json() 的结果作为 FibAppReqData 类型对象, 并传给 func 作为最后一个参数;

由于 @fxjs/webx 本质上是一个 mq.Routing 对象, 你可以用其 API 定制更多的个性化的路由, 比如

// 挂载一个静态目录
app.get('/static', http.fileHandler(path.resolve(__dirname, './static'), {}, true))
// 定制特别的 API
app.post('/__with_cls', (request) => {
  /**
   * 第二个参数传模型名, 表示寻找内置 model
   */
  app.filterRequest(request, 'person',
    /** 
     * @param req: FibAppReq
     * @param db: FibAppDb
     * @param __internal_model__: FxOrmNS.FibOrmFixedModel
     * @param data: FibAppData
     */
    (req, db, __internal_model__, data) => {
      // Do what you want to do
    }
  )
})
// 定制无关内置模型的 API
app.get('/__null_cls', (request) => {
  /**
   * 第二个参数传 '', 表示不寻找内置 model, 此时, 回调函数类型为 FibAppFilterableApiFunction__NullModel, 即第三个参数 __null_cls__ 为 null;
   */
  app.filterRequest(request, '',
    /** 
     * @param req: FibAppReq
     * @param db: FibAppDb
     * @param __null_cls__: null
     * @param data: FibAppData
     */
    (req, db, __null_cls__, data) => {
      // Do what you want to do
    }
  )
})

实际上, Model Function 正是通过 app.filterRequest 实现的, 详情可参考 src/classes/index.ts 中关于 app.post(':classname/:func', ...) 的实现.

View Functions

通过在 orm 的 opts 中添加 viewFunctions , 可以定义该模型相关的视图处理函数; 基于此, 你可以使得 webx 具有直接输出 html 的能力: 当来自客户端的 http 请求头是 Accept: text/html 时, webx 会尝试使用 viewFunctions 定义的函数处理视图. 参考下例:

const fpug = require('fib-pug')
const ejs = require('ejs')

db.define('user', {
    name: String,
    sex: ["male", "female"],
    age: Number,
    password: String,
    salt: String
}, {
    ...
    viewFunctions: {
        /**
         * @ctx `Accept: text/html`
         * 
         * 当客户端发起  GET /user/1 时, 会调用此函数;
         * 
         * 如果 id=1 的 user 存在, 则 result = {sucess: ...}; 否则 result = {error: ...}
         */
        get (apiResult) {
            let tpl = fpug.compile(
                    fs.readTextFile(path.resolve(__dirname, './tpl.get.pug'))
                )
            return {
                success: tpl(apiResult && {user: apiResult.success} || {})
            }
        },
        /**
         * @ctx `Accept: text/html`
         * 
         * 当客户端发起  GET /user 时, 会调用此函数
         * 
         * apiResult = {sucess: ...};
         */
        find (apiResult) {
            let tpl = ejs.compile(
                    fs.readTextFile(path.resolve(__dirname, './tpl.find.ejs'))
                )
            return {
                success: tpl(apiResult && {users: apiResult.success} || {})
            }
        },
        /**
         * @ctx `Accept: text/html`
         * 
         * 当客户端发起  GET /user/profile 时, 会调用此函数.
         * 
         * 由于 static: true, 此时 handler 的第一个参数 _ 恒为 null
         * 
         * @note 注意该路由与 /user/1 同属于 /:classname/:id 格式, 但 webx
         * 会优先尝试调用该方法.
         * 
         */
        profile: {
            static: true,
            /** _ is null */
            handler (_) {
                let tpl = ejs.compile(
                        fs.readTextFile(path.resolve(__dirname, './tpl.profile.ejs'))
                    )
                return {
                    success: tpl()
                }
            }
        }
    }
});

调用优先级

当客户端发起 Accept: text/html 的 http 请求时, viewFunctions 的调用优先级是这样的:

  • /:classname/:idOrFunc ==> viewFunctions.idOrFunc > viewFunctions.get
  • /:classname ==> viewFunctions.function

定义选项

viewFunctions: {
  get: {
    // {boolean}, default false
    static: true,
    // static === true, apiResult 为 null; 否则, apiResult 为 {success: ...} 或 {error: ...}
    handler (apiResult) {
      return {
        success: ...
      }
    }
  },
  /**
   * 此时 等价于 
   * {
   *    static: true,
   *    handler: func2
   * }
   */
  func2 () {
    return {
      success: ...
    }
  }
}
  1. 如果 viewFunctionstatic 为 true, viewFunction 函数的第一个参数为 null;
  2. 如果 viewFunctions 没有 viewFunction.get, viewFunction.find, viewFunction.eget, viewFunction.efind 定义时, 所有该导向这些viweFunction的请求都等价于直接请求对应的 webx 内部 API 方法.
  3. 如果 viewFunctionstatic 不为 true, 如果存在对应的 webx 内部 API 方法 , 则 viewFunction 函数的第一个参数是该对应方法的返回值;

关于第 2 点, 比如 Model user 有如下定义

viewFunctions: {
}

此时发起 {Accept: text/html, GET /user/1} 请求, 不会通过 viewFunctions 处理, 该请求等价于 {Accept: applicaton/json, GET /user/1} 请求的结果. 同理, 此时发起 {Accept: text/html, GET /user} 请求等价于发起 {Accept: applicaton/json, GET /user} 请求.

关于第 3 点, 比如 Model user 有如下定义

viewFunctions: {
  func1: {
    static: false,
    handler (apiResult) {
      
    }
  }
}

此时发起 {Accept: text/html, GET /user/func1} 请求, 由于它可以被认为是请求 id=fun1 的 user 的信息, 因此, 如果 id=func1 的 user 真的存在且可以被请求道, 则此时 handler 的第一个参数 apiResult 就是 { success: [userInfo] }; 若 id=fun1 的 user 不存在或者因ACL 权限无法被用户访问到, 则 apiResult 为 { error: ... }

这个特性意味着你可以对某些 Model 的特定对象做特殊处理, 比如, 对 id=888 的 User 设定为 Lucky Dog, 返回特别的 html 给客户端 :)

viewFunction 对比 function

共同点

viewFunctionfunction 很相似

  1. 都要返回符合 FibAppResponse 格式 的对象
  2. 都是 ORM Model 的定义选项

区别

  1. function 处理 webx 中的 POST /:classname/:func 请求; viewFunctions 处理 webx 中的 GET /:classname/:funcAccept 头包含 text/html 的请求
  2. function 函数的返回值, webx 会尝试以 json 的方式写入 HttpResponse; viewFunction 函数的返回值, webx 会尝试以文本的方式写入 HttpResponse