之前我們已經(jīng)開發(fā)過一款小程序適用的qq音樂api庫https://github.com/FisherWY/QQMusicPlugin了督弓,這次開發(fā)網(wǎng)易云音樂api庫的原因是qq音樂api庫在小程序中iOS環(huán)境下無法使用小程序提供的背景音頻播放器播放的問題
網(wǎng)易云的加密算法真的比其他幾家api復(fù)雜太多了弧烤。毛甲。。完爆QQ和酷狗
想要直接用的話可以到Github直接取我封裝好的api庫摇零。
Github地址https://github.com/JabinGP/NetEaseCloudMusicApi
依賴
本api庫參考了Github上面開源的node庫,因為我們只想要查找音樂和播放音樂這兩個功能,雖然Github那個庫很方便挺狰,但是我們不想為了兩個接口特意去跑一個node.js服務(wù)明郭。Github上的庫
- big-integer.js
這里注意,不要使用最新版的丰泊,最新版的庫再模擬器上運行沒有問題薯定,但是在真機調(diào)試的上傳包階段會報錯說無法識別big-integer.js,最后在我的嘗試下瞳购,選用了一個老版本的庫解決了這個問題话侄。 - crypto-js
這個庫是用來aes加密的,在node上面有一個原生的crypto学赛,但是在小程序里我們沒有年堆,所以我照著Github上的源碼一點一點用這個庫翻譯過來的,還有Buffer在小程序里也沒有盏浇,我使用這個庫的方法代替了变丧。
獲取api的原理
網(wǎng)上很多帖子講的很清楚了,這里推薦幾篇文章绢掰,我只做一個簡單的總結(jié)痒蓬,方便大家理解這個庫。
網(wǎng)易云的加密算法大概使用了兩個:
- AES加密+BASE64編碼
- RSA加密
加密大致流程:
- api請求信息先被轉(zhuǎn)成json字符串格式滴劲,然后再使用一個固定的密鑰aes+base64編碼加密攻晒,得到了第一個
加密結(jié)果a
。 - 客戶端從
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/
隨機生成一個新的16位密鑰班挖,然后用這個密鑰去加密加密結(jié)果a
炎辨,得到加密結(jié)果b
。
3.這樣我們的數(shù)據(jù)就被雙重加密了聪姿,但是我們要發(fā)給服務(wù)器去查詢對應(yīng)的數(shù)據(jù)碴萧,服務(wù)器知道第一個固定的密鑰是多少,可以解開第一個加密結(jié)果末购,但是服務(wù)器可不知道我們第二次加密用的是什么破喻,所以服務(wù)器還需要得到我們的第二個生成的隨機加密密鑰。 - 第二個隨機加密密鑰要是直接發(fā)給服務(wù)器好像就不太安全了盟榴,所以客戶端對第二個隨機加密密鑰也進(jìn)行了加密曹质,使用的就是RSA加密,加密后得到的數(shù)據(jù)我們稱為
c
- 將
b
和c
發(fā)送給服務(wù)器擎场,服務(wù)器就會返回給我們對應(yīng)的結(jié)果了羽德。
加密核心代碼
這段代碼傳入對象后可以直接加密成符合網(wǎng)易云api加密的結(jié)果。
// 生成隨機數(shù)迅办,size默認(rèn)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); //十六位十六進(jìn)制數(shù)作為密鑰
let iv = CryptoJS.enc.Utf8.parse(aes_mv); //十六位十六進(jìn)制數(shù)作為密鑰偏移量
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)
}
// 加密總?cè)肟?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/NetEaseCloudMusicApi
下載完成后宅静,這個庫應(yīng)該是可以直接導(dǎo)入微信小程序開發(fā)工具運行的,但是有幾個注意事項
- 這個庫是用TypeScript寫的站欺,但是最后編譯成了JS運行姨夹,但是編譯后JS代碼可讀性很差纤垂,所以我保留了TypeScript源文件,就在NetEaseCloudMusicApi/ts_src里面磷账,應(yīng)用庫的時候不需要使用到
- 關(guān)閉小程序開發(fā)工具的詳情頁的
ES6轉(zhuǎn)ES5
峭沦,可以使用await處理異步請求(因為庫是用Promise
寫的,起碼要能用Promise
逃糟,實例代碼使用的是await
/async
) - await關(guān)鍵字只能在async修飾過的函數(shù)體內(nèi)部使用吼鱼,不懂的可以查一下await和async的用法。
-
NetEaseCloudMusicApi/Libary
文件夾里面包含了項目依賴的js文件绰咽,應(yīng)用的時候最好整個NetEaseCloudMusicApi
文件夾復(fù)制到項目里面使用菇肃。 -
測試的時候可以勾選不校驗合法域名。
1
開始使用之前的準(zhǔn)備
- 找到NetEaseCloudMusicApi這個文件夾剃诅,里面應(yīng)該包括Libary巷送、src驶忌、ts_src三個文件夾矛辕,Libary是我引用的開源庫,ts_src中是TypeScript源文件付魔,src是ts_src編譯后產(chǎn)生的JavaScript文件夾聊品,也就是說不考慮讀ts源文件的話,可以把ts_src刪了几苍,但是17.4 KB 的大小對應(yīng)用包體積應(yīng)該沒有什么影響吧翻屈,留著也行。
- 在要使用到的庫中如下引用
const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");
注意要用花括號吧MusicManager
括起來妻坝,這一句可以需要變化的地方只有
../../NetEaseCloudMusicApi/src/MusicManager
中的../../
伸眶,后面的路徑都代表了NetEaseCloudMusicApi
文件夾和NetEaseCloudMusicApi
里面文件的路徑,因為我的庫就是這樣的結(jié)構(gòu)刽宪,所以不需要改變厘贼,../../
就要根據(jù)你項目中實際結(jié)構(gòu)來改變了。
MusicManager
該類有以下方法:該類提供了所有獲取其他對象的方法圣拄,可以通過該類獲取其他需要的對象而不是new
-
getMusicSearchHelper()
需要參數(shù):{keyword:"搜索歌曲關(guān)鍵詞",limit:數(shù)字}
返回:MusicSearchHelper
搜索器 -
getMusicUrlHelper()
需要參數(shù):musicId
(數(shù)字類型的歌曲id)
返回:MusicUrlHelper
Url獲取器 -
getUserSearchHelper()
需要參數(shù):{userName:"搜索用戶的用戶名關(guān)鍵詞",limit:數(shù)字}
返回:UserSearchHelper
用戶查詢器 -
getUserListHelper()
需要參數(shù):userId
(數(shù)字類型的用戶id值)
返回:UserListHelper
用戶列表查詢器 -
getUserListDetailHelper()
需要參數(shù):listId
(數(shù)字類型的列表id)
返回:UserListDetailHelper
用戶列表詳情信息獲取器
MusicSearchHelper
用于搜索音樂
可用方法:
-
getSearchResult()
---獲取數(shù)據(jù)(默認(rèn)第一頁) -
nextPage()
--- 下一頁 -
previousPage()
---上一頁 -
getCurrentPage()
---查看當(dāng)前頁數(shù)的
執(zhí)行完切換頁數(shù)后需要再次調(diào)用getSearchResult
方法查看新的查詢結(jié)果嘴秸。
MusicUrlHelper
用于將搜索音樂結(jié)果中的id轉(zhuǎn)換為url播放鏈接
可用方法:
-
getUrlResult()
---獲取url播放鏈接
UserSearchHelper
用于根據(jù)用戶名關(guān)鍵字搜索用戶
可用方法:
-
getSearchResult()
---獲取搜索結(jié)果
UserListHelper
用于獲取用戶id后根據(jù)id獲取用戶歌單信息
可用方法:
-
getAllLists()
---獲取用戶所有歌單 -
getILikeList()
---獲取用戶的我喜歡歌單
UserListDetailHelper
用于獲取歌單id后獲取歌單內(nèi)歌曲列表
可用方法:
-
getDeatil()
---獲取歌單內(nèi)列表
搜索歌曲
- 通過
MusicManager
獲取一個MusicSearchHelper
搜索器 -
MusicSearchHelper
的方法:-
getSearchResult()
---獲取數(shù)據(jù)(默認(rèn)第一頁) -
nextPage()
--- 下一頁 -
previousPage()
---上一頁 -
getCurrentPage()
---查看當(dāng)前頁數(shù)的
-
- 執(zhí)行完切換頁數(shù)后需要再次調(diào)用
getSearchResult
方法查看新的查詢結(jié)果。
代碼實例
const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");
async function test(){
// 搜索歌曲
let musicSearchHelper = MusicManager.getMusicSearchHelper({ keyword: "one more time one more chance", limit: 10 });
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.nextPage();
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.previousPage();
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
console.log(musicSearchHelper);
}
test();
通過搜索歌曲的結(jié)果獲取音樂Url
有了搜索結(jié)果庇谆,我們還需要url才能播放資源
- 通過
MusicManager
獲取一個MusicUrlHelper
Url獲取器 - 通過
MusicUrlHelper
的getUrlResult
方法獲取url -
需要注意的是岳掐,由于網(wǎng)易云接口時常返回空回復(fù),所以這里我通過20以內(nèi)的重復(fù)次請求直到有結(jié)果就停止饭耳,如果20次以后還是沒有結(jié)果(據(jù)我測試20次以內(nèi)都請求到結(jié)果了)串述,也就是返回一個空的字符串"",需要使用者自己重新調(diào)用一次(2019.04.27)現(xiàn)在不會返回空值了寞肖,返回空值發(fā)現(xiàn)問題出在使用微信請求時自作聰明將json轉(zhuǎn)成了a=xxxx&b=xxx的格式剖煌,導(dǎo)致微信不能正常轉(zhuǎn)換請求數(shù)據(jù)材鹦,現(xiàn)在每次請求都能獲取結(jié)果。urlHelper
的getUrlResult
方法
代碼實例
const {MusicManager} = require("../../NetEaseCloudMusicApi/src/MusicManager");
async function test(){
// 搜索歌曲
let musicSearchHelper = MusicManager.getMusicSearchHelper({ keyword: "one more time one more chance", limit: 10 });
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.nextPage();
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.previousPage();
console.log(`現(xiàn)在是第${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更新
新增搜索用戶以及用戶歌單獲取接口
搜索用戶
- 通過
MusicManager
獲取一個UserSearchHelper
用戶查詢器 - 通過
UserSearchHelper
的getSearchResult()
方法獲取搜索結(jié)果
async function test(){
// 搜索用戶
let userSearchHelper = MusicManager.getUserSearchHelper({ userName: "JabinGP", limit: 20 });
let users = await userSearchHelper.getSearchResult();
console.log(users);
}
獲取用戶歌單
- 通過
MusicManager
獲取一個UserListHelper
用戶查詢器 - 通過
UserListHelper
的-
getILikeList()
---獲取我喜歡歌單耕姊,返回一個列表對象 -
getAllLists()
---獲取所有歌單桶唐,返回一個列表對象的數(shù)組
-
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中茉兰,運行項目就會自動執(zhí)行輸出結(jié)果尤泽。
async function test(){
// 搜索歌曲
let musicSearchHelper = MusicManager.getMusicSearchHelper({ keyword: "one more time one more chance", limit: 10 });
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.nextPage();
console.log(`現(xiàn)在是第${musicSearchHelper.getCurrentPage()}頁`);
console.log(await musicSearchHelper.getSearchResult());
musicSearchHelper.previousPage();
console.log(`現(xiàn)在是第${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();
結(jié)尾
2019 4.25目前就只有這兩個接口,因為我們項目就只需要這兩個接口规脸,如果有需要更多接口的坯约,可以在下方評論,以上示例代碼都在Github項目上的index.js中莫鸭,也就是你把文件導(dǎo)入微信開發(fā)者工具后闹丐,取消勾選一下詳情的ES6轉(zhuǎn)ES5以及取消勾選合法域名檢驗,就可以在控制臺看到以上示例代碼的輸出了
2019 4.26更新搜索用戶和獲取用戶歌單以及獲取歌單詳細(xì)三個接口被因。
如果對你有幫助卿拴,點個Star吧~