LoopBack是一个高度可扩展的开源Node.js框架,它使您能够以很少的编码或不编写任何代码来创建动态的端到端REST API。
LoopBack 框架是由一组 Node.js 的模块构成的。你可以单独使用这些模块或把它们组合在一起使用。 应用通过 LoopBack model API 可以使用以下三种方式访问数据。

  • 将模型作为一个标准的 Node 对象使用
  • 通过 HTTP REST API 调用
  • 通过封装好的 API SDK,包括 iOS, Android 和 Angular

应用程序通过 LoopBack model API 用以上三种方式查询数据,储存数据,上传文件,发送 email, 推送消息,注册/登陆用户等远程或本地的服务。用户也可以通过 Strong Remoting 将后端的 API 通过 REST, WebSocket(或其他传输协议)供客户端调用。

一、项目概述

这是官方给出的一个关于咖啡店点评的示例。
咖啡店点评是一个网站,我们可以用来发布咖啡店的评论。有三个数据模型:

  • CoffeeShop 咖啡店
  • Review 评论
  • Reviewer 评论者

它们有如下关系:

  • CoffeeShop 拥有多个 review
  • CoffeeShop 拥有多个 reviewer
  • review 属于 CoffeeShop
  • review 属于 reviewer
  • reviewer 拥有多个 review

一般来说,用户可以创建,编辑,删除和阅读咖啡店的评论,并通过 ACLs 指定基本规则和权限:

  • 任何人都可以阅读评论,但必须先登录才能创建,编辑或删除它们。
  • 任何人都可以注册为用户,然后能够登录或者注销。
  • 登录用户可以创建新的评论,编辑或删除自己的评论,但是他们不能修改咖啡店相关内容。

二、创建项目

安装 Loopback CLI 工具

在 nodejs 环境下安装 Loopback CLI 工具。

  1. npm install -g loopback-cli

安装成功后,输入 lb -help 查看 Loopback CLI 工具的命令:

  1. lb -help
  2. Available commands:
  3. lb acl
  4. lb app
  5. lb bluemix
  6. lb boot-script
  7. lb datasource
  8. lb export-api-def
  9. lb middleware
  10. lb model
  11. lb oracle
  12. lb property
  13. lb relation
  14. lb remote-method
  15. lb soap
  16. lb swagger
  17. lb zosconnectee

创建 LoopBack 项目

在命令行输入:
lb
回车后按照提示输入项目名称,其他选择回车。

  1. ? 您的应用程序的名称是什么? CoffeeShop
  2. ? 输入目录名称以包含项目: CoffeeShop
  3. create CoffeeShop/
  4. info 将工作目录更改为 CoffeeShop
  5. ? 您想要使用哪个版本的 LoopBack 3.x (Active Long Term Support)
  6. ? 您想要什么种类的应用程序? api-server (使用本地用户认证的 LoopBack API 服务器)
  7. 正在生成 .yo-rc.json

安装成功后按照提示操作后续步骤:

  1. 后续步骤:
  2. 将目录更改为您的应用程序
  3. $ cd CoffeeShop
  4. 运行应用程序
  5. $ node .

进入项目根目录,运行应用程序。

  1. node .
  2. Web server listening at: http://localhost:3000
  3. Browse your REST API at http://localhost:3000/explorer

打开浏览器,输入上面提示的地址可以访问 LoopBack 的 API 接口测试页面。由于我们还没有创建任何模型,所以只能在页面上看到框架自带的 user 的 API 调用接口。
Snipaste_2020-10-16_21-57-04.png

利用接口页面创建用户

1.在post的/Users下创建用户

  1. {
  2. "username": "ls",
  3. "email": "ls@163.com",
  4. "password":"123"
  5. }

传递创建用户的参数

2.登录用户post下的/Users/login

将用户信息传递,实现登录

  1. {
  2. "username": "ls",
  3. "email": "ls@163.com",
  4. "password":"123"
  5. }

返回结果

  1. {
  2. "id": "QH0siC9jnlBvE20bkep9wBZ4MgNhmtOiU8gtMPE1pkBvthT9Rk5pB7gBcUgrbmJj",
  3. "ttl": 1209600,
  4. "created": "2020-10-16T14:12:31.674Z",
  5. "userId": 2
  6. }

结果中的id为Token,是登录的标识符,可以把该标识符写入token set中,进行查询。

3.查询用户get方法的/Users/{id}

传入参数id为2,可以查询出数据结果。

连接数据源

默认创建的用户无法对数据持久化,想要数据持久化,需要连接数据库。
LoopBack 可以自动连接市面上绝大多数数据源。我们一 MongoDb 为例来为项目添加数据持久化库。

  1. lb datasource
  2. ? 输入数据源名称: md
  3. ? md 选择连接器: MongoDB StrongLoop 支持)
  4. ? Connection String url to override other settings (eg: mongodb://username:password@hostname:port/da
  5. tabase):
  6. ? host: localhost
  7. ? port: 27017
  8. ? user:
  9. ? password: [hidden]
  10. ? database: coffeeshop
  11. ? 安装 loopback-connector-mongodb@^4.0.0 Yes
  12. + loopback-connector-mongodb@4.2.0

创建完成后,会在datasources.json中添加一个md的对象。
将该对象的值,该成默认db的值,将原来的db删除

  1. {
  2. "db": {
  3. "host": "localhost",
  4. "port": 27017,
  5. "url": "",
  6. "database": "coffeeshop",
  7. "password": "",
  8. "name": "md",
  9. "user": "",
  10. "useNewUrlParser": true,
  11. "connector": "mongodb"
  12. }
  13. }

此过程中,系统一定要开启mongod。
创建好数据源,框架帮我们自动连接数据库,我们对数据的持久化操作也会自动保存到数据库中。
然后就可以重新启动项目,并且创建一个人物对象

  1. {
  2. "username": "zs",
  3. "email": "zs@163.com",
  4. "password":"123"
  5. }

创建成功后,重新启动项目,然后再用此信息登录,还是可以登录成功。说明数据持久化成功。

关于查看mongo数据

  1. show dbs //显示所有数据库
  2. use coffeeshop // 切换到创建的数据库
  3. show collections // 显示出所有集合
  4. db.User.find() // 查找出集合下User的所有值

创建数据模型

  1. 在应用程序中创建模型
  2. $ lb model

CoffeeShop 模型

  1. lb model
  2. ? 请输入模型名称: CoffeeShop
  3. ? 选择要向其附加 CoffeeShop 的数据源: db (mongodb)
  4. ? 选择模型的基类 PersistedModel
  5. ? 通过 REST API 公开 CoffeeShop Yes
  6. ? 定制复数形式(用于构建 REST URL):
  7. ? 公共模型或仅服务器? 公共
  8. 现在添加一些 CoffeeShop 属性。
  9. 在完成时输入空的属性名称。
  10. ? 属性名称: name
  11. ? 属性类型: string
  12. ? 是否为必需? Yes
  13. ? 缺省值[对于无,保留为空白]:
  14. 下面添加另一个 CoffeeShop 属性。
  15. 在完成时输入空的属性名称。
  16. ? 属性名称: city
  17. ? 属性类型: string
  18. ? 是否为必需? Yes
  19. ? 缺省值[对于无,保留为空白]:
  20. 下面添加另一个 CoffeeShop 属性。
  21. 在完成时输入空的属性名称。
  22. ? 属性名称

创建出一个CoffeeShop模型对象,对象有两个属性name和city。

Review模型

评论模型

  1. lb model
  2. ? 请输入模型名称: Review
  3. ? 选择要向其附加 Review 的数据源: db (mongodb)
  4. ? 选择模型的基类 PersistedModel
  5. ? 通过 REST API 公开 Review Yes
  6. ? 定制复数形式(用于构建 REST URL):
  7. ? 公共模型或仅服务器? 公共
  8. 现在添加一些 Review 属性。
  9. 在完成时输入空的属性名称。
  10. ? 属性名称: date
  11. ? 属性类型: date
  12. ? 是否为必需? Yes
  13. ? 缺省值[对于无,保留为空白]:
  14. 下面添加另一个 Review 属性。
  15. 在完成时输入空的属性名称。
  16. ? 属性名称: rating
  17. ? 属性类型: number
  18. ? 是否为必需? No
  19. ? 缺省值[对于无,保留为空白]:
  20. 下面添加另一个 Review 属性。
  21. 在完成时输入空的属性名称。
  22. ? 属性名称: comments
  23. ? 属性类型: string
  24. ? 是否为必需? Yes
  25. ? 缺省值[对于无,保留为空白]:
  26. 下面添加另一个 Review 属性。
  27. 在完成时输入空的属性名称。
  28. ? 属性名称:

创建出一个Review模型对象,对象有三个属性date和rating和comments。

Reviewer模型

reviewer 模型继承自框架自带的 user 模型。在选择模型的基类时不能选择 PersistedModel,而要选择 user,不用添加任何属性,直接回车即可。

  1. lb model
  2. ? 请输入模型名称: Reviewer
  3. ? 选择要向其附加 Reviewer 的数据源: db (mongodb)
  4. ? 选择模型的基类 User
  5. ? 通过 REST API 公开 Reviewer Yes
  6. ? 定制复数形式(用于构建 REST URL):
  7. ? 公共模型或仅服务器? 公共
  8. 现在添加一些 Reviewer 属性。
  9. 在完成时输入空的属性名称。
  10. ? 属性名称:

在项目根目录命令行通过 node . 来启动服务。在浏览器中再次打开 http://localhost:3000/explorer。
可以看到 API 调试界面除了先前的 user 接口外,增加了我们刚刚创建的 3 个模型的接口。
Snipaste_20手动阀-01.png

三.定义模型之间的关系

LoopBack 支持许多不同类型的模型关系:BelongsTo, HasMany, HasManyThrough, and HasAndBelongsToMany 等等。根据项目需求可以定义如下关系:

  • CoffeeShop HasMany review
  • CoffeeShop HasMany reviewer
  • review BelongsTo CoffeeShop
  • review BelongsTo reviewer
  • reviewer HasMany review

CoffeeShop 拥有多个 review,没有中间模型和外键。

  1. lb relation
  2. ? 选择从中创建关系的模型: CoffeeShop
  3. ? 关系类型: has many
  4. ? 选择与之创建关系的模型: Review
  5. ? 输入关系的属性名称: reviews
  6. ? (可选)输入定制外键:
  7. ? 需要直通模型? No
  8. ? 允许在 REST API 中嵌套关系: No
  9. ? 禁止包含关系: No

CoffeeShop 拥有多个 reviewer,没有中间模型和外键。

  1. lb relation
  2. ? 选择从中创建关系的模型: CoffeeShop
  3. ? 关系类型: has many
  4. ? 选择与之创建关系的模型: Reviewer
  5. ? 输入关系的属性名称: reviewers
  6. ? (可选)输入定制外键:
  7. ? 需要直通模型? No
  8. ? 允许在 REST API 中嵌套关系: No
  9. ? 禁止包含关系: No

review 属于一个 CoffeeShop,没有中间模型和外键。

  1. lb relation
  2. ? 选择从中创建关系的模型: Review
  3. ? 关系类型: belongs to
  4. ? 选择与之创建关系的模型: CoffeeShop
  5. ? 输入关系的属性名称: coffeeShop
  6. ? (可选)输入定制外键:
  7. ? 允许在 REST API 中嵌套关系: No
  8. ? 禁止包含关系: No

review 属于唯一一个 reviewer,外键是 publisherId,没有中间模型。

  1. lb relation
  2. ? 选择从中创建关系的模型: Review
  3. ? 关系类型: belongs to
  4. ? 选择与之创建关系的模型: Reviewer
  5. ? 输入关系的属性名称: reviewer
  6. ? (可选)输入定制外键: publisherId
  7. ? 允许在 REST API 中嵌套关系: No
  8. ? 禁止包含关系: No

reviewer 拥有多个 review,外键是 publisherId,没有中间模型。

  1. lb relation
  2. ? 选择从中创建关系的模型: Reviewer
  3. ? 关系类型: has many
  4. ? 选择与之创建关系的模型: Review
  5. ? 输入关系的属性名称: reviews
  6. ? (可选)输入定制外键: publisherId
  7. ? 需要直通模型? No
  8. ? 允许在 REST API 中嵌套关系: No
  9. ? 禁止包含关系: No

四、定义操作权限

loopback 权限控制由权限控制列表或 ACL 决定。

根据项目需求,权限控制应执行以下规则:
任何人都可以阅读评论。
但是创建、编辑和删除的操作必须在登录之后才有权限。
任何人都可以注册为用户,可以登录和登出。
登录用户可以创建新的评论,编辑或删除自己的评论。然而,他们不能修改咖啡店的评论。

首先,拒绝所有人操作所有接口,这通常是定义 ACL 的起点,因为您可以选择性地允许特定操作的访问。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: (所有现有模型)
  3. ? 选择 ACL 作用域: 所有方法和属性
  4. ? 选择访问类型: 全部(匹配所有类型)
  5. ? 选择角色 所有用户
  6. ? 选择要应用的许可权 明确拒绝访问

现在允许所有人对 reviews 进行读操作。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: Review
  3. ? 选择 ACL 作用域: 所有方法和属性
  4. ? 选择访问类型: 读取
  5. ? 选择角色 所有用户
  6. ? 选择要应用的许可权 明确授权访问

允许通过身份验证的用户对 coffeeshops 进行读操作,已登录的用户可以浏览所有咖啡店。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: CoffeeShop
  3. ? 选择 ACL 作用域: 所有方法和属性
  4. ? 选择访问类型: 读取
  5. ? 选择角色 任何已认证的用户
  6. ? 选择要应用的许可权 明确授权访问

允许经过身份验证的用户对 reviews 进行创建操作,已登录的用户可以添加一条评论。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: Review
  3. ? 选择 ACL 作用域: 单个方法
  4. ? 输入方法名称 create
  5. ? 选择角色 任何已认证的用户
  6. ? 选择要应用的许可权 明确授权访问

使 review 的作者有权限(其“所有者”)对其进行任何更改。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: Review
  3. ? 选择 ACL 作用域: 所有方法和属性
  4. ? 选择访问类型: 写入
  5. ? 选择角色 拥有该对象的用户
  6. ? 选择要应用的许可权 明确授权访问

五、自动填充字段内容

用户在添加评论时,自动填充日期字段的内容为当前的日期。
由于评论和评论者之间是通过publisherId这个外键进行关联的,用户在添加评论时,需要将评论者的用户 Id 作为其内容进行填充。
定义一个远程钩子,每当在 Review 模型上调用 create()方法时(在创建新的评论时),它将被调用。

可以定义两种远程钩子:

  • beforeRemote()在远程方法之前运行。
  • afterRemote()在远程方法之后运行。

在这两种情况下,有两个参数可以供我们使用:一个与要钩子函数的远程方法匹配的字符串,和一个回调函数。

创建一个远程钩子

将在 review 模型中定义一个远程钩子,具体来说是 Review.beforeRemote

  • 设置 publisherId 为请求中的 userId
  • 设置日期为当前日期。

修改 common/models/review.js:

  1. module.exports = function(Review) {
  2. Review.beforeRemote('create', function(context, user, next) {
  3. context.args.data.date = Date.now();
  4. context.args.data.publisherId = context.req.accessToken.userId;
  5. next();
  6. });
  7. };

创建 Review 模型的新实例之前调用此函数。

使用当前上下文

Loopback应用程序需要访问上下文信息来处理业务逻辑,如:

  • 访问当前登录的用户,根据token进行判断相关业务
  • 访问HTTP的请求中字段

beforeRemote钩子

调用方法前被执行的一个钩子方法,此处可以获取到ctx.args.options;例如调用user下的greet方法时,增加的业务处理。

  1. User.beforeRemote('greet', function(ctx, unused, next) {
  2. if (!ctx.args.options.accessToken) return next();
  3. User.findById(ctx.args.options.accessToken.userId, function(err, user) {
  4. if (err) return next(err);
  5. ctx.args.options.currentUser = user;
  6. next();
  7. });
  8. })

invoke钩子处理options

用来给参数options即参数type设置为optionsFromRequest对象设置自定义值
loopback内置两个阶段:auth和invoke,所有远程方法都在第二阶段运行invoke。应用可以自定义一些字段或者方法在调用任何远程方法之前运行代码,可以将该代码放置在引导脚本中。

  1. module.exports = function(app) {
  2. app.remotes().phases
  3. .addBefore('invoke', 'options-from-request')
  4. .use(function(ctx, next) {
  5. if (!ctx.args.options.accessToken) return next();
  6. const User = app.models.User;
  7. User.findById(ctx.args.options.accessToken.userId, function(err, user) {
  8. if (err) return next(err);
  9. ctx.args.options.currentUser = user;
  10. next();
  11. });
  12. });
  13. };

六、自定义角色与授权

LoopBack 允许我们定义静态和动态角色.静态角色存储在数据源中,并映射到用户.相反,动态角色不分配给用户,而是在访问期间确定。

动态角色

LoopBack 提供以下内置动态角色:

Role object property String value Description
Role.OWNER $owner Owner of the object
Role.AUTHENTICATED $authenticated authenticated user
Role.UNAUTHENTICATED $unauthenticated Unauthenticated user
Role.EVERYONE $everyone Everyone

静态角色

LoopBack 允许我们自己编写 js 脚本文件,脚本文件可以任意取名,放在/server/boot/目录下。
当执行node .命令时, 会自动执行脚本文件中的代码。我们可以这样来编写/server/boot/createAdmin.js代码:

  1. module.exports = function(app) {
  2. var User = app.models.Reviewer;
  3. var Role = app.models.Role;
  4. var RoleMapping = app.models.RoleMapping;
  5. User.create(
  6. [{ username: "admin", email: "aa@bb.cc", password: "admin" }],
  7. function(err, users) {
  8. if (err) throw err;
  9. console.log("Created users:", users);
  10. //create the admin role
  11. Role.create(
  12. {
  13. name: "admin"
  14. },
  15. function(err, role) {
  16. if (err) throw err;
  17. console.log("Created role:", role);
  18. //make RoleMapping
  19. role.principals.create(
  20. {
  21. principalType: RoleMapping.USER,
  22. principalId: users[0].id
  23. },
  24. function(err, principal) {
  25. if (err) throw err;
  26. console.log("Created principal:", principal);
  27. }
  28. );
  29. }
  30. );
  31. }
  32. );
  33. };

代码解析

  • 第 1 行是固定写法。定义一个函数并暴露出去,并传入app参数,用来调用 app 的模型。
  • 第 2-4 行分别定义用户、角色和映射三个变量并赋值。
  • 第 6 行到 11 行创建用户。如果创建用户成功,继续创建角色。
  • 第 14 行到 21 行创建角色。如果创建用户成功,继续创建映射。
  • 第 24 行到 32 行创建用户与角色之间的映射关系。

角色创建成功后,我们在 MongoDB 数据库中,可以看到刚刚创建的 3 个文档:

  1. > use coffeeshop
  2. switched to db coffeeshop
  3. > show collections
  4. AccessToken
  5. Reviewer
  6. Role
  7. RoleMapping
  8. User
  9. > db.Role.find()
  10. { "_id" : ObjectId("5f8a6c690e27563e80c2358c"), "name" : "admin", "created" : ISODate("2020-10-17T04:00:41.675Z"), "modified" : ISODate("2020-10-17T04:00:41.675Z") }

七、角色授权

角色授权还是用lb acl命令。给admin角色赋予应用内的最大权限。
上一步只是创建了admin用户角色。要拥有权限,还需要使用lb acl进行赋权。

  1. lb acl
  2. ? 选择要应用 ACL 条目的模型: (所有现有模型)
  3. ? 选择 ACL 作用域: 所有方法和属性
  4. ? 选择访问类型: 全部(匹配所有类型)
  5. ? 选择角色 其他
  6. ? 请输入角色名称: admin
  7. ? 选择要应用的许可权 明确授权访问

授权之后,我们打开/common/models/coffee-shop.json文件,可以看到对admin角色的授权配置代码:

  1. {
  2. "accessType": "*",
  3. "principalType": "ROLE",
  4. "principalId": "admin",
  5. "permission": "ALLOW"
  6. }

/common/models/review.json/common/models/review.json文件中同样可以看到对admin角色的授权配置代码。
在业务开发中,所有接口的权限初始化应该是关闭的,给修改review.json中acls

  1. "acls": [
  2. {
  3. "principalType": "ROLE",
  4. "principalId": "$everyone",
  5. "permission": "DENY"
  6. }
  7. ]

重启服务后,在review模型下定义的所有接口都拒绝访问。调用接口时返回

  1. {
  2. "error": {
  3. "name": "Error",
  4. "status": 401,
  5. "message": "Authorization Required",
  6. "statusCode": 401,
  7. "code": "AUTHORIZATION_REQUIRED",
  8. "stack": "Error: Authorization Required"
  9. }
  10. }

principalId 对某一身份设置权限控制

在 LoopBack 中常用取值

  1. $everyone :所有人
  2. $owner :用户只能访问自己的信息,其他用户下的该接口并没有访问权限。
  3. $authenticated :只要用户的token信息合法,就可以调用此接口。
  4. 自定义角色,例如 admin

通过修改permission的属性,使property下的接口可以访问

  1. "acls": [{
  2. "principalType": "ROLE",
  3. "principalId": "$everyone",
  4. "permission": "DENY"
  5. },{
  6. "accessType": "EXECUTE",
  7. "principalType": "ROLE",
  8. "principalId": "$everyone",
  9. "permission": "ALLOW",
  10. "property": "shopLists"
  11. }]

八、接口调试

未登录用户操作

右上角未设置任何token的情况下

  • 点击 Reviewer,展开 Reviewer 相关的 API 接口。
  • 点击get/Reviewers,出现操作提示界面

011.png

  • 点击Try it out!按钮,显示 401 错误,提示需要授权。

012.png

用 静态 admin 用户登录

  • 点击Post/Reviewers/login,出现登录操作提示界面。

012.png

  • 登录成功后返回 admin 的相关信息。

012.png
复制 admin 返回信息中的id字符串值,本例中GX8G8hYMTWmcaSWuD0QEH0PQq3HiY4wZO9hKw1w3NVVYUNntEGj5GrzoHjkaHygi粘贴到窗口页面右上角的accessToken中,然后点击Set Access Token按钮。

  • 再次点击get/Reviewers,出现操作提示界面后,点击Try it out!按钮,不再提示需要授权的信息,成功返回应用中已有的用户的信息。

012.png

注销登录

点击Post/Reviewers/logout,注销登录
012.png

用 动态 用户(zs)登录

创建咖啡店

由于只有admin用户有创建咖啡店的权限,所以要先用admin登录
在点击Post/CoffeeShops
012.png
如果用zs或者ls登录,会有权限不够
012.png

创建评论

在用户登录的情况下才可以,首先登录zs
然后post/Reviews菜单下创建评论
012.png
用ls账户登录,添加评论
012.png

查看评论

点击get/Reviews,可以查看所有评论
012.png

修改评论

点击put/Reviews/{id},可以修改当前用户下的评论,修改其他用户的评论,会出现权限不足。admin可以修改所有评论。
传入要修改的Id
012.png
返回修改完成的结果。

删除评论

只有admin用户才可以删除评论。
首先登录admin账户
012.png

九:配置header信息

image.png

第一部分的配置:

/coffectshop/package.json

  1. {
  2. "name": "loopbacko_o",
  3. "version": "1.0.0",
  4. "main": "server/server.js",
  5. "description": "loopback 扩展的swagger接口"
  6. }

第二部分

由于/server/server.js在启动时加载的是loopback-component-explorer组件创建的页面。
查看node_modules下node_modules/loopback-component-explorer/index.js文件

  1. const SWAGGER_UI_ROOT = require('swagger-ui/index').dist;
  2. // 把原来的public路径修改为../../client/public,并且把public文件夹拷贝到client下。
  3. const STATIC_ROOT = path.join(__dirname, '../../client/public');
  4. function routes(loopbackApplication, options) {
  5. router.get('/config.json', function(req, res) {
  6. //...
  7. router.use(loopback.static(STATIC_ROOT));
  8. // 通过路由设置了跳转到的静态文件
  9. router.use(loopback.static(SWAGGER_UI_ROOT));
  10. }
  11. }

通过修改client下的public内的文件内容就可以修改。

链接:https://juejin.im/post/6844903949493862407
CNode:https://cnodejs.org/topic/57e5b2859c495dce044f397c