Awesome 社區模塊

fib-app

fibjs 應用程序基礎api 框架

Install

1
npm install fib-app [--save]

Test

1
npm test

建立基礎腳本

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
const http = require('http'); const util = require('util') const Session = require('fib-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定義模塊,內容如下:

1 2 3 4 5 6 7
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。

1 2 3 4
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 這兩個字段,你可以用它們來進行調試。舉個例子,如果一個請求權限認證失敗,會返回以下信息:

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

code 編碼分為三個部分,前三位403 表示錯誤類型,05 表示數據表編號,01 表示詳細錯誤編碼。

對於GET 請求,通常會返回對像數據,根據GET 請求的地址不同,可能會返回一個對象,也可能會返回一個數組。比如:

1 2 3 4 5
{ "name": "tom", "sex": "male", "age": 23 }

或者:

1 2 3 4 5 6 7 8 9 10 11 12
[ { "name": "tom", "sex": "male", "age": 23 }, { "name": "lily", "sex": "female", "age": 22 } ]

特殊字段

對像數據中,有四個特殊含義的字段,是不允許通過API更改的。分別是id, updatedAt, createdAt, createdBy

其中id, updatedAt,createdAt單個字段會自動創建和修改。createdBy則需要自行指定類型。

基礎對象訪問API

完成這樣的數據定義,便直接擁有了一整套符合REST api 規範的接口調用:

url method action
/1.0/:className POST 創建新對象
/1.0/:className/:id GET 讀取對象
/1.0/:className/:id PUT 修改對象
/1.0/:className/:id DELETE 刪除對象
/1.0/:className GET 查詢對象列表

創建新對象

為了創建一個新的對象,應該向class 的URL 發送一個POST 請求,其中應該包含對象本身。例如,要創建如上所說的對象:

1 2 3 4
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 時間戳:

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

讀取對象

當你創建了一個對象時,你可以通過發送一個GET 請求到返回的header 的Location 以獲取它的內容。例如,為了得到我們上面創建的對象:

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

返回的主體是一個JSON對象包含所有用戶提供的field加上createdAtupdatedAtid字段:

1 2 3 4 5 6 7 8
{ "name": "tom", "sex": "male", "age": 23, "createdAt": "2017-11-25T01:39:35.931Z", "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" }

通過設置返回字段keys,可以定制返回的內容,keys的內容是一個以,分割的字段名稱字符串,:

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

將返回:

1 2 3 4
{ "name": "tom", "sex": "male" }

修改對象

為了更改一個對像已經有的數據,你可以發送一個PUT 請求到對象相應的URL 上,任何你未指定的key 都不會更改,所以你可以只更新對像數據的一個子集。例如,我們來更改我們對象的一個age 字段:

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

返回的JSON對象會包含updatedAtid字段,表明更新發生的時間:

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

刪除對象

為了刪除一個對象,可以發送一個DELETE 請求到指定的對象的URL,比如:

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

查詢對象列表

通過發送一個GET 請求到類的URL 上,不需要任何URL 參數,你就可以一次獲取多個對象。下面就是簡單地獲取所有用戶:

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

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[ { "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的內容是一個以,分割的字段名稱字符串,例如:

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

將指定只返回nameage兩個字段。

where 過濾條件

通過where參數的形式可以對查詢對像做出約束。

where參數的值應該是JSON編碼過的。就是說,如果你查看真正被發出的URL請求,它應該是先被JSON編碼過,然後又被URL編碼過。最簡單的使用where參數的方式就是包含應有的key和value。例如,如果我們想要搜索名字為tom的用戶,我們應該這樣構造查詢:

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

where 的值為一個urlencode 後的JSON 字符串,內容為:{"name":"tom"}

除了完全匹配一個給定的值以外,where也支持比較的方式,比如包含。where參數支持如下選項:

key operation sample
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選項,可以跳過指定的記錄數,達到翻頁的效果。

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

limit 返回記錄限制

通過limit選項,可以限制返回記錄數,limit的有效數字為1-1000,缺省為100。

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

order 指定排序方式

通過order選項,設定返回結果集的排序方式,字段名前包含-時為倒序。

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

count 返回結果總數

在請求時增加count可以在返回指定內容的同時返回結果集的總數。

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

此時返回結果將包含countresults兩個字段,分別包含總數和結果:

1 2 3 4 5 6 7 8 9 10 11 12 13
{ "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 上體現出來,例如:

1 2 3 4 5 6 7 8 9
module.exports = db => { var Person = db.models.person; var Pet = db.define('pet', { name: String }); Person.hasMany('pets', Pet); };

擴展對象訪問API

下面是擴展對象的API 定義:

url method action
/1.0/:className/:id/:extendName PUT 設置擴展對象
/1.0/:className/:id/:extendName POST 創建擴展對象
/1.0/:className/:id/:extendName/:rid GET 讀取擴展對象
/1.0/:className/:id/:extendName/:rid PUT 修改擴展對象
/1.0/:className/:id/:extendName/:rid DELETE 刪除擴展對象
/1.0/:className/:id/:extendName GET 查詢擴展對象列表

設置擴展對象

設置擴展對像是將兩個獨立的對象建立聯繫。比如tom 領養了一隻叫cat 的寵物,可以用下面的操作實現:

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

在調用裡需要在body 內指定cat 的id。

創建擴展對象

直接創建擴展對象,可以在創建對象的同時,建立對象之間的聯繫。比如:

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

將創建一隻名叫cat 的寵物,並建立與tom 的關聯關係。

讀取擴展對象

讀取擴展對象與讀取基礎對像很相似,也同樣支持keys 選項:

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

修改擴展對象

讀取擴展對象與讀取基礎對像很相似:

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

刪除擴展對象

刪除擴展對像不會刪除對象本身,只會解除對象之間的關係:

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

查詢擴展對象列表

查詢擴展對象列表與查詢基礎對象列表很相似,也同樣支持keys 以及條件過濾等選項:

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

ACL

可以通過定義Model 的ACL 控制數據權限。比如:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
const orm = require('fib-orm'); module.exports = db => { db.define('blog', { title: String, detail: Stringnote: String }, { ACL: function(session) { return { "*": { "*": false }, "57fbbdb0a2400000": { "*": true }, "roles": { "user": { "read": true } } }; } }); };

如果定義Model 時未指定ACL,則等同於設定了缺省權限:

1 2 3 4 5
{ "*": { "*": true } }

主體

ACL主體描述有三種,用戶id,用戶role*id表示一個具體的用戶,role表示具有某個角色的用戶,*表示所有用戶:

主體 描述 優先級
id 具體用戶的id 1
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,則可以這樣設定:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
{ "*": { "*": false, "read": ['title'] }, "57fbbdb0a2400000": { "*": true }, "roles": { "user": { "read": ['title', 'detail'] } } }

對象權限

在Model 上設定的是整個類的權限,如果需要對具體的對象設定權限,可以通過設置OACL 來實現:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
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 需要單獨指定:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
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,用戶本人可以操作自己的所有數據,以及擁有自己寵物信息的全部權限。

在檢查擴展對象的訪問權限時,會分別檢查對象權限和擴展對象權限。比如以下請求:

1
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

Function

可以為Model 定義api,對於復雜數據操作,可以通過自定義Function 來完成。

絕大多數權限可以通過ACL 控製完成,不需要通過Function 來完成基於對象的權限。Function 可用於完成基於數據的權限,比如根據審批狀態,賦予不同用戶組權限。以及多項修改,比如需要修改多條數據庫記錄。

繪製數據模型

在完成數據定義以後,可以使用app.diagram()繪製數據模型的svg格式類圖,保存至文件會得到類似下面的圖像: diagram