乐趣区

为微信小程序开发的网易云音乐api库

之前我们已经开发过一款小程序适用的 qq 音乐 api 库 https://github.com/FisherWY/Q…,这次开发网易云音乐 api 库的原因是 qq 音乐 api 库在小程序中 iOS 环境下无法使用小程序提供的背景音频播放器播放的问题

网易云的加密算法真的比其他几家 api 复杂太多了。。。完爆 QQ 和酷狗
想要直接用的话可以到 Github 直接取我封装好的 api 库。
Github 地址 https://github.com/JabinGP/Ne…

依赖

本 api 库参考了 Github 上面开源的 node 库,因为我们只想要查找音乐和播放音乐这两个功能,虽然 Github 那个库很方便,但是我们不想为了两个接口特意去跑一个 node.js 服务。Github 上的库

  1. big-integer.js
    这里注意,不要使用最新版的,最新版的库再模拟器上运行没有问题,但是在真机调试的上传包阶段会报错说无法识别 big-integer.js,最后在我的尝试下,选用了一个老版本的库解决了这个问题。
  2. crypto-js

这个库是用来 aes 加密的,在 node 上面有一个原生的 crypto,但是在小程序里我们没有,所以我照着 Github 上的源码一点一点用这个库翻译过来的,还有 Buffer 在小程序里也没有,我使用这个库的方法代替了。

获取 api 的原理

网上很多帖子讲的很清楚了,这里推荐几篇文章,我只做一个简单的总结,方便大家理解这个库。
网易云的加密算法大概使用了两个:

  1. AES 加密 +BASE64 编码
  2. RSA 加密

加密大致流程:

  1. api 请求信息先被转成 json 字符串格式,然后再使用一个固定的密钥 aes+base64 编码加密,得到了第一个 加密结果 a
  2. 客户端从 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/ 随机生成一个新的 16 位密钥,然后用这个密钥去加密 加密结果 a ,得到 加密结果 b

3. 这样我们的数据就被双重加密了,但是我们要发给服务器去查询对应的数据,服务器知道第一个固定的密钥是多少,可以解开第一个加密结果,但是服务器可不知道我们第二次加密用的是什么,所以服务器还需要得到我们的第二个生成的随机加密密钥。

  1. 第二个随机加密密钥要是直接发给服务器好像就不太安全了,所以客户端对第二个随机加密密钥也进行了加密,使用的就是 RSA 加密,加密后得到的数据我们称为c
  2. bc发送给服务器,服务器就会返回给我们对应的结果了。

加密核心代码

这段代码传入对象后可以直接加密成符合网易云 api 加密的结果。

// 生成随机数,size 默认 16
function createSecretKey(size) {
    const keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    let key = ""
    for (let i = 0; i < size; i++) {let pos = Math.random() * keys.length
        pos = Math.floor(pos)
        key = key + keys.charAt(pos)
    }
    return key
}


// aes 加密方法
function aesEncrypt(word, secKey) {let key = CryptoJS.enc.Utf8.parse(secKey);  // 十六位十六进制数作为密钥
    let iv = CryptoJS.enc.Utf8.parse(aes_mv);   // 十六位十六进制数作为密钥偏移量
    let srcs = CryptoJS.enc.Utf8.parse(word);
    let encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
    let res = encrypted.toString();
    console.log(res);
    return res;
}

// 填充方法
function zfill(str, size) {while (str.length < size) str = "0" + str
    return str
}

// rsa 加密方法
function rsaEncrypt(text, pubKey, modulus) {const _text = text.split('').reverse().join('')
    const biText = bigInt(CryptoJS.enc.Utf8.parse(_text).toString(), 16),
        biEx = bigInt(pubKey, 16),
        biMod = bigInt(modulus, 16),
        biRet = biText.modPow(biEx, biMod)
    return zfill(biRet.toString(16), 256)
}


// 加密总入口
function Encrypt(obj) {const text = JSON.stringify(obj)
    const secKey = createSecretKey(16)
    const encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
    const encSecKey = rsaEncrypt(secKey, pubKey, modulus)
    return {
        params: encText,
        encSecKey: encSecKey
    }
}

封装好的 Api 库

首先到 Github 下载我的 Api 库 https://github.com/JabinGP/Ne…
下载完成后,这个库应该是可以直接导入微信小程序开发工具运行的,但是有几个注意事项

  1. 这个库是用 TypeScript 写的,但是最后编译成了 JS 运行,但是编译后 JS 代码可读性很差,所以我保留了 TypeScript 源文件,就在 NetEaseCloudMusicApi/ts_src 里面,应用库的时候不需要使用到
  2. 关闭小程序开发工具的详情页的 ES6 转 ES5,可以使用 await 处理异步请求(因为库是用Promise 写的,起码要能用Promise,实例代码使用的是await/async
  3. await 关键字只能在 async 修饰过的函数体内部使用,不懂的可以查一下 await 和 async 的用法。
  4. NetEaseCloudMusicApi/Libary文件夹里面包含了项目依赖的 js 文件,应用的时候最好整个 NetEaseCloudMusicApi 文件夹复制到项目里面使用。
  5. 测试的时候可以勾选不校验合法域名。

开始使用之前的准备

  1. 找到 NetEaseCloudMusicApi 这个文件夹,里面应该包括 Libary、src、ts_src 三个文件夹,Libary 是我引用的开源库,ts_src 中是 TypeScript 源文件,src 是 ts_src 编译后产生的 JavaScript 文件夹,也就是说不考虑读 ts 源文件的话,可以把 ts_src 删了,但是 17.4 KB 的大小对应用包体积应该没有什么影响吧,留着也行。
  2. 在要使用到的库中如下引用
const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");

注意要用花括号吧 MusicManager 括起来,这一句可以需要变化的地方只有
../../NetEaseCloudMusicApi/src/MusicManager 中的 ../../,后面的路径都代表了NetEaseCloudMusicApi 文件夹和 NetEaseCloudMusicApi 里面文件的路径,因为我的库就是这样的结构,所以不需要改变,../../就要根据你项目中实际结构来改变了。

MusicManager

该类有以下方法:该类提供了所有获取其他对象的方法,可以通过该类获取其他需要的对象而不是 new

  • getMusicSearchHelper()

需要参数:{keyword:"搜索歌曲关键词",limit: 数字}
返回:MusicSearchHelper搜索器

  • getMusicUrlHelper()
    需要参数:musicId(数字类型的歌曲 id)

返回:MusicUrlHelperUrl 获取器

  • getUserSearchHelper()

需要参数:{userName:"搜索用户的用户名关键词",limit: 数字}
返回:UserSearchHelper用户查询器

  • getUserListHelper()

需要参数:userId(数字类型的用户 id 值)
返回:UserListHelper用户列表查询器

  • getUserListDetailHelper()

需要参数:listId(数字类型的列表 id)
返回:UserListDetailHelper用户列表详情信息获取器

MusicSearchHelper

用于搜索音乐
可用方法:

  • getSearchResult()— 获取数据(默认第一页)
  • nextPage()— 下一页
  • previousPage()— 上一页
  • getCurrentPage()— 查看当前页数的

执行完切换页数后需要再次调用 getSearchResult 方法查看新的查询结果。

MusicUrlHelper

用于将搜索音乐结果中的 id 转换为 url 播放链接
可用方法:

  • getUrlResult() — 获取 url 播放链接

UserSearchHelper

用于根据用户名关键字搜索用户
可用方法:

  • getSearchResult()— 获取搜索结果

UserListHelper

用于获取用户 id 后根据 id 获取用户歌单信息
可用方法:

  • getAllLists()— 获取用户所有歌单
  • getILikeList()— 获取用户的我喜欢歌单

UserListDetailHelper

用于获取歌单 id 后获取歌单内歌曲列表
可用方法:

  • getDeatil()— 获取歌单内列表

搜索歌曲

  1. 通过 MusicManager 获取一个 MusicSearchHelper 搜索器
  2. MusicSearchHelper的方法:

    • getSearchResult()— 获取数据(默认第一页)
    • nextPage()— 下一页
    • previousPage()— 上一页
    • getCurrentPage()— 查看当前页数的
  3. 执行完切换页数后需要再次调用 getSearchResult 方法查看新的查询结果。

代码实例

const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");
async function test(){
     // 搜索歌曲
    let musicSearchHelper = MusicManager.getMusicSearchHelper({keyword: "one more time one more chance", limit: 10});
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.nextPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.previousPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    console.log(musicSearchHelper);
} 
test();

通过搜索歌曲的结果获取音乐 Url

有了搜索结果,我们还需要 url 才能播放资源

  1. 通过 MusicManager 获取一个MusicUrlHelperUrl 获取器
  2. 通过 MusicUrlHelpergetUrlResult方法获取 url
  3. 需要注意的是,由于网易云接口时常返回空回复,所以这里我通过 20 以内的重复次请求直到有结果就停止,如果 20 次以后还是没有结果(据我测试 20 次以内都请求到结果了),也就是返回一个空的字符串 ””,需要使用者自己重新调用一次 urlHelpergetUrlResult方法(2019.04.27)现在不会返回空值了,返回空值发现问题出在使用微信请求时自作聪明将 json 转成了 a =xxxx&b=xxx 的格式,导致微信不能正常转换请求数据,现在每次请求都能获取结果。

代码实例

const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");

async function test(){
    // 搜索歌曲
    let musicSearchHelper = MusicManager.getMusicSearchHelper({keyword: "one more time one more chance", limit: 10});
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.nextPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.previousPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    console.log(musicSearchHelper);

    // 获取歌曲 url
    let songs = await musicSearchHelper.getSearchResult();
    let musicId  = songs[0].id;
    let musicUrlHelper = MusicManager.getMusicUrlHelper(musicId);
    console.log(` 歌曲的 ID 是:${musicId}`);
    let url = await musicUrlHelper.getUrlResult();
    console.log(` 歌曲的 url 链接是:${url}`);
} 
test();

4.26 更新

新增搜索用户以及用户歌单获取接口

搜索用户

  1. 通过 MusicManager 获取一个 UserSearchHelper 用户查询器
  2. 通过 UserSearchHelpergetSearchResult()方法获取搜索结果
async function test(){
  // 搜索用户
  let userSearchHelper = MusicManager.getUserSearchHelper({userName: "JabinGP", limit: 20});
  let users = await userSearchHelper.getSearchResult();
  console.log(users);
}

获取用户歌单

  1. 通过 MusicManager 获取一个 UserListHelper 用户查询器
  2. 通过 UserListHelper

    • getILikeList() — 获取我喜欢歌单,返回一个列表对象
    • getAllLists() — 获取所有歌单,返回一个列表对象的数组
async function test(){
  // 搜索用户
  let userSearchHelper = MusicManager.getUserSearchHelper({userName: "JabinGP", limit: 20});
  let users = await userSearchHelper.getSearchResult();
  console.log(users);
  
  // 获取我喜欢歌单
  let userListHelper = MusicManager.getUserListHelper(users[0].userId);
  let iLikeList = await userListHelper.getILikeList()
  console.log(iLikeList);
 }

通过歌单里的 Id 获取歌曲 url

与前面一致,不再赘述

完整实例

完整实例代码在项目 page 下的 index.js 中,运行项目就会自动执行输出结果。

async function test(){
    // 搜索歌曲
    let musicSearchHelper = MusicManager.getMusicSearchHelper({keyword: "one more time one more chance", limit: 10});
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.nextPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    musicSearchHelper.previousPage();
    console.log(` 现在是第 ${musicSearchHelper.getCurrentPage()}页 `);
    console.log(await musicSearchHelper.getSearchResult());
    console.log(musicSearchHelper);

    // 获取歌曲 url
    let songs = await musicSearchHelper.getSearchResult();
    let musicId  = songs[0].id;
    let musicUrlHelper = MusicManager.getMusicUrlHelper(musicId);
    console.log(` 歌曲的 ID 是:${musicId}`);
    let url = await musicUrlHelper.getUrlResult();
    console.log(` 歌曲的 url 链接是:${url}`);

    // 搜索用户
    let userSearchHelper = MusicManager.getUserSearchHelper({userName: "JabinGP", limit: 20});
    let users = await userSearchHelper.getSearchResult();
    console.log(users);

    // 获取用户歌单
    let userListHelper = MusicManager.getUserListHelper(users[0].userId);
    let iLikeList = await userListHelper.getILikeList()
    console.log(iLikeList);
    
    // 获取我喜欢歌单
    let userListDeatilHelper = MusicManager.getUserListDetailHelper(iLikeList.id);
    let listDetail = await userListDeatilHelper.getDeatil();
    console.log(listDetail);
    let timer=0;
    for(let song of listDetail.tracks){
      musicUrlHelper.musicId=song.id;
      console.log(` 歌曲的 ID 是:${musicUrlHelper.musicId}`);
      let url2 = await musicUrlHelper.getUrlResult();
      console.log(` 歌曲的 url 链接是:${url2}`);
      if(timer++>20)break;
    }
  }   
test();

结尾

2019 4.25 目前就只有这两个接口,因为我们项目就只需要这两个接口,如果有需要更多接口的,可以在下方评论,以上示例代码都在 Github 项目上的 index.js 中,也就是你把文件导入微信开发者工具后,取消勾选一下详情的 ES6 转 ES5 以及取消勾选合法域名检验,就可以在控制台看到以上示例代码的输出了

2019 4.26 更新搜索用户和获取用户歌单以及获取歌单详细三个接口。

如果对你有帮助,点个 Star 吧~

退出移动版