一直以來都在關注nodejs,直到最近空閒下來,才嘗試寫寫nodejs小demo。文章若有錯誤歡迎批評指正!來自一位不知名的前端。
一、本demo剛開始採用koa2開發,後面因爲koa2的異步響應很差處理(還在研究中)改用express;後續會嘗試使用koa2改造。
二、http請求(csrf僞造)主要參考netmusic-node;其餘部分思路參考NeteaseCloudMusicApi。
三、本項目預覽地址https://music.jeeas.cn/html
以get方式請求 一、搜索歌曲名 params[s=歌曲名,type,offset,limit] getApi/search?s=歌曲名 二、單曲播放地址 params[id=歌曲id,br] getApi/music/url?id=25643093 三、歌詞 params[id] getApi/lyric?id=25643093 四、單曲詳情 params[id] getApi/music/detail?id=25643093 五、專輯詳情 params[id] getApi/album/detail?id=2263164 六、歌單類型列表 params[] getApi/playlist/catlist 七、歌單類型列表-熱門類型 params[] getApi/playlist/hot 八、推薦新音樂 params[] getApi/personalized/newsong 九、搜索hot params[] getApi/search/hot 十、推薦歌單 params[] getApi/personalized
"dependencies": { "apicache": "^1.5.2", "big-integer": "^1.6.47", "crypto": "^1.0.1", "ejs": "^2.7.1", "express": "^4.17.1", "express-rate-limit": "^3.5.3", "fs-extra": "^8.1.0", "redis": "^2.8.0", "request": "^2.88.0", "socket.io": "~2.1.1" }
apicache:請求cache緩存,防止api調用過於頻繁ip被禁掉
ejs:數據模板
express-rate-limit:express內部封裝api訪問限制
redis:數據臨時存儲到redis中未涉及到數據庫操做
socket.io:socket基礎包
前端
server.js
項目啓動後redis的鏈接狀態以及soketIo會掛載在整個server下,方便後面調用。node
... var redisClient = false; var ioSocket = null; //啓動查看redis狀態 redis.initRedis(function(client) { redisClient = client && client != '' && client != null ? true : false; io.redisClient = redisClient; }); ... socketServer.initServer(io); //static //api訪問限制20s內60次 //cache 緩存2分鐘 2 minutes 0.05 minutes=3s //設置跨域訪問 //使用ejs //使用路由 app.use(router); ... server.listen(port, () => { console.log('Socket Server listening at port %d', port); console.log('Visit http://127.0.0.1:%d', port); console.log('Hello, I\'m %s, how can I help?', serverName); });
http.js
項目http請求簡單封裝git
var http = require('http'); ... function WebAPI(options, callback) { //.... var httpClients = http.request({ hostname: 'music.163.com', method: method, path: options.path, headers: {...} },function(req, res) { req.on('error', function(err) { //請求異常處理... }); req.on('data', function(chunk) { //獲取數據... }); req.on('end', function() { //網易雲api調用有時候會響應爲空,爲空時從新發起請求 //數據接收完畢處理rsp //回調以前用websoket向全部clients廣播消息,以及更新redis callback(rsp); }) }); //網易雲csrf僞造,詳情crypto.js httpClients.write('params=' + cryptoreq.params + '&encSecKey=' + cryptoreq.encSecKey); httpClients.end(); } ... module.exports = { WebAPI: WebAPI, }
socket.js
github
服務端接收clients的消息發送,以及向全部clients廣播消息(api調用數)web
function initServer(io,callback) { if (io) { io.on('connection', function(socket) { socket.on('message', function(data) { console.log(data.message) }); //console.log('emit全部人推送,broadcast除某個套接字之外的全部人發送消息,eg:connection不推送'); //向全部鏈接推送news消息 socket.broadcast.emit('news', { message: 'a new connection', newsType: 'server-prop' }); if(callback){ callback(socket) } socket.on('disconnect', function() { //console.log('disconnect') }); }); } } module.exports = { initServer: initServer };
redis.js
redis簡單的查詢更新封裝redis
var redis = require('redis'); var client = null; function initRedis(callback) { if (!client) { client = redis.createClient(6379, '127.0.0.1'); } client.on('connect', function() { //redis鏈接成功 console.log('redis connect success!'); if (callback) { callback(client); } }); let first = true; client.on('error', function() { //redis鏈接失敗 console.log('redis connect error!'); if (callback && first == true) { first = false; callback(null); } }); }; function hmSet(options, callback) { if (client) { client.hmset(options.apiType, options.apiName, options.total); //redis簡單的使用(設置keys的值) } } function hgetAll(keys, callback, isTotal) { if (client) { client.hgetall(keys, function(err, obj) { //redis簡單的使用(查詢全部keys) }); } } function getApiNumber(res) { //簡單計算api調用總數 } module.exports = { hmSet: hmSet, hgetAll: hgetAll, initRedis: initRedis };
基礎功能瞭解差很少以後,重點介紹一下routes.js路由內部的API封裝,也是整個demo部署的比較核心部分。外部公開的API以http(GET方式)爲主,本次以Socket調用(內部)示例,大致分享一下本身的心得體會。
routes.js
數據庫
ejs使用說明須要在server.js入口配置
app.set('views', path.join(__dirname, './views'));
app.set("view engine", "ejs");
express
const express = require('express'); const router = express.Router(); const version = "/v1"; ... router.get('/music', function(request, response) { //監聽路由路由/music訪問 //console.log(request.query) //模板使用ejs,response須要設置type,不然會以ejs源碼展現 response.type('html'); //經常使用模式 response.render(路由模板,回傳頁面參數) //此時會查找ejs的配置,view/music.ejs response.render('music', { title: '網易雲音樂Cloud', keywords:Tools.randomSongs()//隨機取一首歌曲名稱搜索 }) //在music.ejs模板中能夠<%= title %> <%= keywords %> 使用變量 }); /* ** ** 一、搜索歌曲名 ** params[s=歌曲名,type,offset,limit] ** /search?s=大城小愛 ** */ router.get(version + '/search', function(request, response) { //type:1: 單曲, 10: 專輯, 100: 歌手, 1000: 歌單, 1002: 用戶, 1004: MV, 1006: 歌詞, 1009: 電臺, 1014: 視頻 //offset : 偏移數量,用於分頁 , 如 : 如 :( 頁數 -1)*20, 其中20爲limit的值,默認爲0 var data = { s: decodeURI(request.query.s) || '', offset: request.query.offset || 0, limit: request.query.limit || 20, type: request.query.type || 1 }; var params = { path: '/weapi/cloudsearch/get/web', request: request, response: response, data: data, apiType: '1' }; //在WebAPI內部response.send響應web數據 Tools.WebAPI(params) }); ... module.exports = router;
而後將router.js掛載在server.js下,整個已配置的路由即可以訪問
demo剛開始使用koa2做爲服務端的框架,後續由於router.get(xx,function(request, response) { 內部response異步響應很差處理(初學者才疏學淺,很是抱歉,能夠一塊兒探討),改用express處理異步響應。})
json
網易雲API的請求訪問方式,能夠抓包或者網易雲音樂官網搜索慢慢摸索查看,固然也能夠借鑑別人的勞動成果!
心得體會編寫中