fib-app
fibjs 應用程式基礎api 框架
Install
1npm install fib-app [--save]
Test
1npm test
建立基礎腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const 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
7module.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
4curl -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
4curl -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 以取得它的內容。例如,為了得到我們上面創建的物件:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000
傳回的主體是一個JSON 物件包含所有使用者提供的field 加上createdAt
、updatedAt
和id
欄位:
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
的內容是一個以,
分割的字段名稱字串,:
1curl -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
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"age": 25}' \
http://localhost/1.0/person/57fbbdb0a2400000
傳回的JSON 物件會包含updatedAt
和id
字段,表示更新發生的時間:
1
2
3
4{
"updatedAt": "2017-11-25T01:39:35.931Z",
"id": "57fbbdb0a2400000"
}
刪除對象
為了刪除一個對象,可以傳送一個DELETE 請求到指定的對象的URL,例如:
1curl -X DELETE http://localhost/1.0/person/57fbbdb0a2400000
查詢物件列表
透過發送一個GET 請求到類別的URL 上,不需要任何URL 參數,你就可以一次取得多個物件。以下就是簡單地取得所有用戶:
1curl -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
的內容是一個以,
分割的欄位名稱字串,例如:
1curl -X GET http://localhost/1.0/person?keys=name%2Cage
將指定只返回name
和age
兩個欄位。
where 過濾條件
透過where
參數的形式可以對查詢物件做出約束。
where
參數的值應該是JSON 編碼過的。就是說,如果你查看真正被發出的URL 請求,它應該是先被JSON 編碼過,然後又被URL 編碼過。最簡單的使用where
參數的方式就是包含應有的key 和value。例如,如果我們想要搜尋名字為tom 的用戶,我們應該這樣建構查詢:
1curl -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
選項,可以跳過指定的記錄數,達到翻頁的效果。
1curl -X GET http://localhost/1.0/person?skip=100
limit 回傳記錄限制
透過limit
選項,可以限制傳回記錄數,limit
的有效數字為1-1000,缺省為100。
1curl -X GET http://localhost/1.0/person?limit=100
order 指定排序方式
透過order
選項,設定傳回結果集的排序方式,欄位名稱前包含-
時為倒序。
1curl -X GET http://localhost/1.0/person?order=-id
count 回傳結果總數
在請求時增加count
可以在傳回指定內容的同時傳回結果集的總數。
1curl -X GET http://localhost/1.0/person?count=1&limit=1
此時傳回結果將包含count
和results
兩個字段,分別包含總數和結果:
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
9module.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
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"id": "57fbbdb0a2400007"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets
在呼叫裡需要在body 內指定cat 的id。
建立擴充對象
直接建立擴展對象,可以在創建對象的同時,建立對象之間的聯繫。比如:
1
2
3
4curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "cat"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets
將創建一隻名叫cat 的寵物,並建立與tom 的關聯關係。
讀取擴充對象
讀取擴充物件與讀取基礎物件很相似,也同樣支援keys 選項:
1curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
修改擴充對象
讀取擴充物件與讀取基礎物件很相似:
1
2
3
4curl -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "cat 1"}' \
http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
刪除擴充對象
刪除擴充物件不會刪除物件本身,只會解除物件之間的關係:
1curl -X DETELE http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007
查詢擴充物件列表
查詢擴充物件清單與查詢基礎物件清單很相似,也同樣支援keys 以及條件篩選等選項:
1curl -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
25const orm = require('fib-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,則等同於設定了預設權限:
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
為只允許指定的欄位的存取。delete
和find
不接受array
,如果設定了array
則視同為true
。如果指定的權限不存在,則符合同主體下的*
權限。若都不存在,再一次查詢下一優先權的主體。
例如以上例子,如果需要設定user
只允許讀取title
和detail
,其他人可以讀取 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
24module.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
41module.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);
};
在這個定義中,任何人都可以查閱個人資訊的name
和sex
,並自由查閱和搜尋他的pets
,用戶本人可以操作自己的所有數據,以及擁有自己寵物資訊的全部權限。
在檢查擴充物件的存取權限時,會分別檢查物件權限和擴充物件權限。例如以下請求:
1curl -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
格式類別圖,儲存至檔案會得到類似下面的圖像: