关于Sails
Sails 是一个很优秀的node.js的web服务器框架,提供完整的web服务的解决方案,比如:
blueprints
blueprints是sails其中一个最出色的功能,blueprints可以瞬间生成完整、复杂的RESTFul接口,包括除了正常的数据增删改查、还包括model之间的associations增删改查,比如有两个model:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| * Company.js */ module.exports = {
attributes: { name : "string", team:{ collection: 'team' }, user:{ collection: 'user' } } };
|
1 2 3 4 5 6 7 8 9
| * Team.js */ module.exports = {
attributes: { name : "string" } };
|
如果创建company下的team,可以直接post team的数据
1 2 3 4 5 6
| $http.post('/company/1/team', {name:"Android"}).success(function(result){ result.should.be.have.property("id", 1); result.should.be.have.property("team").with.lengthOf(1); _.findWhere(result.team, {name:"Android"}).should.be.ok .and.have.property("id"); })
|
如果要添加一个已存在的team,可以post team的id,这样不会创建新的team,只是保存team 2和company 1的关系
1 2 3 4 5 6
| $http.post('/company/1/team', {id:2}).success(function(result){ result.should.be.have.property("id", 1); result.should.be.have.property("team").with.lengthOf(1); _.findWhere(result.team, {name:"Android"}).should.be.ok .and.have.property("id"); })
|
blueprints的查询数据接口,会自动populate所有associations的attributes
例如:get /company/1的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "id" : 1, "name" : "netease", "team" : [ { "name" : "Web" },{ "name" : "Android" },{ "name" : "iOS" } ], "user" : [ { "name" : "Peter" },{ "name" : "Harry" } ] }
|
sails-hook-deepblueprints
前面介绍了sails的blueprints的一些很好的特点,其实blueprints在sails中是hook插件方式存在的,只不过是内置的hook,默认是enable,有兴趣的可以查看一下blueprints的源码
如果有超过两个model存在层级关系的associate,比如modelA,modelB,modelC分别存在下一个的attribute,那么blueprints的查询和添加会分别自动生成两个route:
/modelA/:modelA_id/modelB/:modelB_id?
/modelB/:modelB_id/modelC/:modelC_id?
大多数情况下,这些api或许也够用,但是如果在某些特殊的情况,比如数据的权限访问,如果我要创建一个modelC,在创建modelC之前,我不等不先通过modelB的id查询到对应的modelA的id,然后确认这个id是否有权限,才创建modelC,否则的话返回一个错误。
这样的话,开发者必须做一些额外的工作去做查询上层数据,但如果一开始我可以知道这个数据对应的modelA的id,那么就可以省掉查询modelB是否属于modelA,我只需要知道当前user是否有权限访问modelA的id,其余事情交给代码去验证。比如:/modelA/:modelA_id/modelB/:modelB_id/modelC/:modelC_id?
只需要验证当前用户是否可以访问modelA_id就可以.
sails-hook-deepblueprints就是一个做这个事情的hook,当请求的资源层级超过1层时,就会去验证资源的层级关系是否一一对应,如果不是,将返回一个错误。
特点
sails-hook-deepblueprints 是 sails 的自定义hook, 提供超过两层的资源层级的route
- 创建超过2层资源层级的route:
/deep/company/1/team/2/project/3
- 验证route的相邻资源的层级是否合法
比如: 如果team 2不属于company 1,那么这个请求将会返回400错误
使用方法
例子
这里 有完整的例子
比如这里有4个model,它们分别都是前一个资源是后一个资源的父类
1 2 3 4 5 6 7 8 9 10 11 12
| * Company.js */ module.exports = {
attributes: { name : "string", team:{ collection: 'team' } } };
|
1 2 3 4 5 6 7 8 9 10 11 12
| * Team.js */ module.exports = {
attributes: { name : "string", project : { collection: 'project' } } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| * Project.js */
module.exports = {
attributes: { name : "string", todolist : { collection: 'todolist' } } };
|
1 2 3 4 5 6 7 8 9 10
| * Todolist.js */
module.exports = {
attributes: { name : "string" } };
|
启动sails后,你会看到自动生成了deep-blueprints的routes(打开sails的log level为silly时可以看到log显示)
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
| silly: Binding RESTful deepblueprint/shadow routes for model+controller: company silly: Binding RESTful association deepblueprint `team` for company silly: Binding route :: post /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: post /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/:parentid/team/:id? (ACTION: company/_config) silly: Binding RESTful association deepblueprint `project` for team silly: Binding route :: post /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: post /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/*/team/:parentid/project/:id? (ACTION: company/_config) silly: Binding RESTful association deepblueprint `todolist` for project silly: Binding route :: post /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: post /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: delete /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: put /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config) silly: Binding route :: get /deep/company/*/team/*/project/:parentid/todolist/:id? (ACTION: company/_config)
|
当请求的path命中这些route时,那么deep-blueprints hook将会接管这些请求,并进行对应的数据处理,和blueprints的类似:
1 2 3 4
| add: post %s/:parentid/%s/:id? remove: delete %s/:parentid/%s/:id? update: put %s/:parentid/%s/:id? populate: get %s/:parentid/%s/:id?
|
注意
- model的关联其他model attribute的名称必须和model名称一模一样,比如:
1 2 3 4 5 6 7 8 9 10 11 12
| * Company.js */ module.exports = {
attributes: { name : "string", team:{ collection: 'team' } } };
|
1 2 3 4 5 6 7 8 9
| * Team.js */ module.exports = {
attributes: { name : "string" } };
|
- 因为deep-blueprints 会自动查询所有attributes的associations,并自动创建对应的crud route,所以应该避免循环route,所以不能定义双向associations,比如:
1 2 3 4 5 6 7 8 9 10 11 12
| * Company.js */ module.exports = {
attributes: { name : "string", team:{ collection: 'team' } } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| * Team.js */ module.exports = {
attributes: { name : "string", company : { model : 'company' }, project : { collection: 'project' } } };
|
Test
in deep-blueprints sample, clone repo and run npm test