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