扮演服务层角色 -- NodeJs

续上一篇搭建音乐播放器桌面应用–前端篇后,这次讲述给界面提供数据包的后端扮演者–Node接入层。

Node在项目中扮演的是后端,但准确地说Node这并不是后端的角色,真正后端角色是豆瓣FM暴露的API。如下图

三端

中间这一服务层即是本文的重点:对接口的挖取与封装。豆瓣FM官方并没对外提供API手册,尽管豆瓣提供了开放平台, 但与我们登录官方网站进行授权与操作走的并不是同一通道。

开放平台开放的类似于第三方登录 给开发者一个新的API Key,有了这个key而允许了我们对指定的功能进行指定的操作。这对于开发者是很受限的,因此我们需要采取各种可能的手段抓包,分析整合,整理出我们真正需要的接口。

上图中豆瓣FMAPI来源:https://douban.fm。
豆瓣FM

源码

Musicer

三端如何分工

  • 前端:界面布局,数据渲染
  • 服务层(接入层):接口封装,功能拆分
  • 后端(豆瓣FM接口):提供原始数据包

端与端之间如何交互

交互可分为前端与服务层的交互,以及服务层与后端的交互。

前端与服务层

在这两者之间我们需要思考几个问题

  • 如何设计接口
  • 接口应遵循哪些规范
  • 提供哪些类型的接口

如何设计接口

我们采用RESTful API,它是目前比较成熟的一套API设计理论,事实上在工作中我接触到的后端接口也均是RESTful设计的API。RESTful API是面向资源的API设计架构,资源即为可供我们操作的数据群。结合我们这个项目,资源即为用户登录而获得用户的token,个人信息,爱心歌曲,歌曲歌词等。到此我们可以确定我们的API一半身形:/aaa 或者 /bbb ,这aaa 和 bbb 指的是资源位置。

接口遵循的规范:

  • 路径:根据功能划分,播放器这个项目中我们分为两大部分功能,用户管理和歌曲管理,因此路径可能长这样:

    1
    2
    3
    4
    5
    6
    7
    8
    用户管理:
    /user/login: 登录
    /user/basic: 获取个人基本信息
    歌曲管理
    /song/next 下一首
    /song/like 点赞歌曲
    /song/lyric 获取歌词
  • 方法:

    这个项目中我们只用到三种方法,GET,POST

    • GET:用于资源获取,不做增删改查,如切换到粤语歌曲。
    • POST:用于对资源修改或获取一些重要信息。如点赞歌曲,记录会存在个人账号里面;登录时获取token信息。
  • 返回格式:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    <!-- 失败:0 成功:1 -->
    "code": 1,
    "msg": "success", // failed
    "data" : {
    }
      }
  • 一个登陆API示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    登录
    - url: http://xxxxx/user/login
    - param: {
    username:13798994068,
    password:123456c
    }
    - method: get
    - return: {
    <!-- 失败:0 成功:1 -->
    "code": 1,
    "msg": "success", // failed
    "data" : {
    "access_token": "25a64943770f2dbca55d46995",
    "douban_user_name": "abigaleyu",
    "douban_user_id": "1688842",
    "expires_in": 7775999,
    "refresh_token": "5f2388c502a9a4f0799f2f9"
    }
      }

服务层与豆瓣FM的交互

这一交互我们同样需要思考几个问题:

  • 如何从豆瓣FM获取登录信息
  • 个人账号下操作歌曲
  • 如何根据接口规范封装接口

路径

从入口文件index.js中我们可以看到:

1
2
3
4
5
const PORT = process.env.PORT || 8082;
const server = express();
server.use('/user', user);
server.use('/song', song);

如何从豆瓣FM获取登录信息

从豆瓣FM获取登录信息占据了这层面工作量的70%,原因在与豆瓣FM的登陆是会重定向到豆瓣官网的登陆,需要多次重定向并且在不同的URL中读取header里的cookie并存下来,召集我们所需要的几个k-v之后才可以为所欲为,比如听自己喜欢的歌呀,点赞喜欢的歌曲呀~

文件 src/routes/user.js 中我们可以看到存在这一逻辑:

/login —>/basic?username=xxx&password=xxx&token=xxx –> getUserAc() –> serveceAcount() –> getDouBanFm() –> getUserBid() –> getUserAc()

这就是为了从多个重定向的header里面挖取到我们需要的cookie

/login获取到到access_token,官方请求如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 登录
- url: https://www.douban.com/service/auth2/token
- param: {
username: username,
password: password
}
- method: post
- return: {
access_token: '208e18axxxxxxxxx78ebf00a1',
douban_user_name: 'abigaleyu',
douban_user_id: '168889042',
expires_in: 7775999,
refresh_token: 'd9243263xxxxxxxx22a0b7eea'
}

获取个人基本信息,headers中需要加上以上获取的有效token

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
- url: https://accounts.douban.com/j/popup/login/basic
- headers: {
Authorization: 'Bearer '+ token
}
- param: {
source: fm
referer: https://douban.fm/
ck: L-UM
name: username
password: password
captcha_solution: null
captcha_id: null
}
- method: post
- return: {
<!-- 获取成功 -->
{
"status": "success",
"message": "success",
"description": "处理成功"
}
<!-- 获取失败 根据图片输入验证码再次请求 -->
"status": "failed",
"message": "captcha_required",
"description": "需要图形验证码"
}

我们可以从返回结果中拿到我们要的基本信息

获取dbcl2

我们要找的key-value就藏在截图部分,从cookie里劫持下来,存到本地,再看一个截图,状态码是200而不是3开头,是因为这个basic的请求中,Request Headers有一行:Referer:https://accounts.douban.com/popup/login?source=fm&use_post_message ,有什么特殊的吗~可以看下下面的截图

location

关注Response Headers 里的Location,我们正需要从它调整的链接中去找下一个值。也正式因为它在Response中而非Request中,所以Status Code 为3开头。

具体获取过程可以查看src/routes/user.js,总之我们最终需要集齐token,bid,dbcl12,ck这几个值,以下请求是我在登录状态下点击下一首歌曲浏览器显示的请求头,我们获取到的值就派上用场了~

下一首

接口封装

我们根据约定好的规范:方法,返回结果等进行文档梳理

  • 登录态:我们分为初次登录和采用token登录

    初次登录:即无token或token已过期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ## 登录
    - url: http://localhost:8082/user/login
    - param: {
    username: username,
    password: password
    }
    - method: post
    - return: {
    code: 1,
    msg: 'success',
    data: {
    access_token: '208e18axxxxxxxxx78ebf00a1',
    douban_user_name: 'abigaleyu',
    douban_user_id: '168889042',
    expires_in: 7775999,
    refresh_token: 'd9243263xxxxxxxx22a0b7eea'
    }
    }

    采用token登录:token未过期的条件下,我们可以重复利用它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    - url: http://localhost:8082/user/loginByToken
    - param: {
    username:13798994068,
    token:xxxxxxx
    }
    - method: get
    - return: {
    <!-- 失败:0 成功:1 -->
    "code": 1,
    "msg": "success", // failed
    "data" : {
    <!-- 成功:1 -->
    "access_token": "25a64943770f2dbca55d46995",
    "douban_user_name": "abigaleyu",
    "douban_user_id": "1688842",
    "expires_in": 7775999,
    "refresh_token": "5f2388c502a9a4f0799f2f9"
    }
    <!-- 失败 0 -->
    "code": -1,
    "msg": "failed"
    }

    我在Node接入层与豆瓣FM的交互中重点解释了登录逻辑,关于歌曲的获取,切换等方式可以参考登录API获取的步骤,它们会比登录简单的哟~

总结一下,Node实现的这一层为接入层,负责再次封装接口以及输出,也为我们做一些安全相关信息的保护,假如你需要做一些服务器同构渲染,很建议加入这样一层接入层