项目作者:盛文增
本项目开源于Github ,遵循GPL-3.0 License 协议。
网易云音乐小程序项目报告 一、API
的搭建 1. API
的选择 一个项目应当从数据开始。为了获取网易云的数据,首先要获取网易云的API
。所幸,万能的GitHub
有一个API
项目:Binaryify/NeteaseCloudMusicApi: 网易云音乐 Node.js API service (github.com) 。首先将此项目部署在自己的服务器上:
以下命令为大致步骤
安装nodejs
安装pm2
克隆项目
使用node
启动此项目并使用pm2
管理
1 2 3 4 5 yum install nodejs git clone https://github.com/Binaryify/NeteaseCloudMusicApi.git yum install pm2 pm2 start app.js pm2 save
并将域名www.raspberry.cool
解析到此服务器(在阿里云控制台解析)。
2. API
的HTTP支持与鉴权 经过以上步骤,此时可以访问到此API
,但是仍然存在两个重要问题:
未解决以上问题,决定使用Nginx
作反向代理并配置域名证书,实现HTTP
访问和鉴权。
具体解决办法:使用Nginx
进行判断,如果header
或者get
参数中存在appkey
并且正确,那么就可以通过并访问到API
接口,否则直接拦截返回404
页面。
使用Lua
脚本实现以上过程会简单很多,但是由于自己把Lua
等常用模块编译到Nginx
中较为麻烦耗时,因此决定采用OpenResty
——内置了Lua
模块。
OpenResty
中的nginx.conf
关键配置:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 worker_processes 1; error_log logs/error.log; error_log logs/error.log notice; error_log logs/error.log info; pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; server { listen 80 default_server; listen [::]:80 default_server; server_name _; rewrite ^(.*)$ https://$host$1 permanent; include /etc/nginx/default.d/*.conf; } rewrite_by_lua_no_postpone on; server { listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; server_name www.raspberry.cool; root /usr/share/nginx/html; ssl_certificate "/usr/local/openresty/nginx/cert/www.raspberry.cool.pem"; ssl_certificate_key "/usr/local/openresty/nginx/cert/www.raspberry.cool.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #使用该协议进行配置。 include /etc/nginx/default.d/*.conf; location / { rewrite_by_lua_block{ -- 请求头 a local headers = ngx.req.get_headers() -- 准备从请求头中获取 appkey appkey = nil -- 遍历请求头 for k,v in pairs(headers) do if k == "appkey" then appkey = v end end -- 如果请求头没有 appkey if appkey ~= "4e51fb5b4a2d4e3cb7cccf87e6c4908a" then -- 并且 get 参数也没有 appkey if ngx.var.arg_appkey ~= "4e51fb5b4a2d4e3cb7cccf87e6c4908a" then ngx.req.set_uri("/404"); end end } proxy_pass http://localhost:8080; } } }
配置中的appkey
是写死在代码中的,更好的方案是通过lua
从文件中读取。
3. 小程序中的API
使用与调试 在header
中写appkey
的原因是为了方便小程序请求,可以在request
中统一封装header
。在get
中写appkey
的原因是为了方便浏览器直接打开地址调试。
封装的小程序request
实例(其中promisic
函数是将小程序的原生APIpromise
化):
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 27 28 29 30 31 32 33 34 35 36 37 import { promisic } from "../miniprogram_npm/lin-ui/utils/util" class Http { static async request ({ url, data = {}, method = 'GET' } ) { return await promisic(wx.request)({ url : `https://www.raspberry.cool${url} ` , data, method, header : { appkey : "4e51fb5b4a2d4e3cb7cccf87e6c4908a" } }) } } export { Http }
二、小程序页面设计 由于是仿照网易云播放器的项目,开始之前我仔细观察了网易云音乐APP、网易云音乐微信小程序、QQ音乐APP、QQ音乐微信小程序以及Apple Music APP,同时结合微信小程序的特征——小而美,使用PS工具、蓝湖等进行页面的全新设计。
三、小程序实现 1. 视图层 由于此项目较为复杂,使用第三方组件能够加快开发效率。考虑市场上诸多第三方小程序组件,如微信官方的WeUI WXSS
、iView
、ZanUI WeApp
、MinUI
、ColorUI
、Lin UI
,考虑良久后决定选择Lin UI
,其基于微信小程序原生语法实现的组件库,没有太多的破坏小程序的原生感。遵循简洁,易用的设计规范。且其开发者团队非常活跃,问题能够得到有效的解决。
上图为Lin-UI
交流群。
页面实现:
2. 逻辑层 本项目力求做到代码优雅、逻辑明确、结构清晰,因此在架构设计上花费很多时间,同时仔细研读了《高级JavaScript程序设计 第四版》一书。代码注释量足够,可读性较高。
项目目录结构:
目录及对应代码介绍:
image
:存放一些必须存放在本地的图片,即tabbar
的小图标。其他图片全部从服务端获取。
model
:存放实体类,如歌单类、音乐类。并提供对应的方法。例如,对于音乐类,代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 import { Http } from "../utils/http" class Music { static async checkMusic (songId ) { return await Http.request({ url : "/check/music" , data : { id : songId } }) } static async getSongBySongId (songId ) { return await Http.request({ url : '/song/url' , data : { id : songId } }) } static async getSongDetailBySongId (songId ) { return await Http.request({ url : "/song/detail" , data : { ids : songId } }) } static async getSongLyrics (songId ) { return await Http.request({ url : "/lyric" , data : { id : songId } }) } static async searchMusic (value ) { return await Http.request({ url : '/search' , data : { keywords : value } }) } } export { Music }
其他代码展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 async _indexContentService (coldLoad ) { if (coldLoad || this .data.PLAYLIST_RCMD.length === 0 ) { const homePageBlocks = await Homepage.getHomePageBlockPage() this .setData({ PLAYLIST_RCMD : homePageBlocks.data.data.blocks.find(items => items.blockCode === 'HOMEPAGE_BLOCK_PLAYLIST_RCMD' ).creatives, STYLE_RCMD : homePageBlocks.data.data.blocks.find(items => items.blockCode === 'HOMEPAGE_BLOCK_STYLE_RCMD' ).creatives, OFFICIAL_PLAYLIST : homePageBlocks.data.data.blocks.find(items => items.blockCode === 'HOMEPAGE_BLOCK_OFFICIAL_PLAYLIST' ).creatives }) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 phoneNumberRules: [{ validator (rule, value, callback, source ) { if (value) { if (/^1[3-9]\d{9}$/ .test(value)) { callback() } else { callback(false ) } } else { callback(false ) } }, message : '你真的输入正确的手机号了吗' , trigger : 'blur' , required : true }]
四、迭代历史 项目在GitHub
上有完整的迭代历史。
五、心得体会 由于时间仓促,仍然有不少不完善的地方,一些界面仍然不够美观、一些功能还来不及实现。
这几个月来,我通读了一遍《JavaScript高级程序设计 第四版》,对前端的语法、设计思想等有了很多的了解,并活学活用,极大地提高了我的编码能力。对整个项目的设计流程有了大致的了解——从后端开发、UI设计到前端实现,
在项目目录设计方面,看了很多知名项目的目录设计;在代码可读性、优雅性方面做了很多努力;写了很多注释,可读性较高;保持良好的编码习惯,不乱用变量,能使用局部不可变变量就不使用全局可变变量等。