JoyLau's Blog

JoyLau 的技术学习与思考

前言

效果展示

在线地址

开始

需要准备

  • 这次要解析的是 网易云音乐的 MV
  • 需要准备的解析的有
  • 获取 MV 信息列表
  • 获取 MV 详细信息
  • 获取 MV 播放地址
  • 在线播放 MV
  • 获取 MV 排行榜
  • 获取最新 MV

说明

  • 大部分解析提供的接口都和我以前2篇文章类似,之前的文章有分析过,这里就不再多说了
  • 这里重点说明下 MV 的播放问题

关于 MV 的播放

  • 解析 MV 详细信息,可获得 MV 的真实播放的 MP4 的地址
  • 但是这个地址,网易云做了防盗链处理
  • 什么是防盗链?
  • 一般情况下,我的资源文件,比如 图片, css,js,视频,我们自己放到服务器上可以直接引用
  • 同样的道理,别人可以访问你的服务器,也可以直接引用
  • 那么,不想被别人引用怎么办呢?
  • 这就引申出了防盗链的操作
  • 最常见的防盗链的处理就是加上 referer识别,就是来源网址信息
  • referer 其实是个错误的拼写,这个就是有历史原因了,以前的开发人员在定义这个属性的时候,把这个单词写错了,后来没有人注意到,一直使用到他作为标准
  • 后来,也没有人去特意改他了,就这么用着吧
  • 这个是简单防盗链处理
  • 还有更复杂的,比如 js 加密路径信息,每次请求路径都会变化,这个就复杂了
  • 很幸运,网易云的 MV 采用的就是 referer 的识别方式
  • 那么就有相应的破解方法了

关于 referer

  • MP4 的地址在浏览器地址栏直接粘过去是可以播放的,但是由其他网站跳进去的则不能访问,因为带进了 rerferer
  • 那么,要做的就是去除 请求的 rerferer
  • 我找了很多资料也尝试了很多次,想在浏览器端把 rerferer 去除掉,基本是实现不了的,如果你实现在页面里单独请求 mp4 地址时不带referer, 请联系我
  • 那么要做的就是在服务端操作了
  • 在服务端操作很简单,就是伪造头信息进行请求

这个是带 referer 的请求,被网易云直接拒绝了

joymusic-mv-referer

这个是复制地址到地址栏,则可以直接播放

joymusic-mv-no-referer

服务单去除 referer

  • 严格来说不能说去除 refere,我们需要将原本我们自己服务器的 referer 修改为网易云服务器的 referer

Java 版

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
public void playMV(HttpServletResponse res, String mvurl) throws IOException {
if (StringUtils.isEmpty(mvurl)){
return;
}
res.setContentType("video/mpeg4; charset=utf-8");
URLConnection connection = new URL(mvurl).openConnection();
connection.setRequestProperty("referer", "http://music.163.com/");
connection.setRequestProperty("cookie", "appver=1.5.0.75771;");
connection.connect();
InputStream is = connection.getInputStream();
OutputStream os = res.getOutputStream();
byte bf[] = new byte[2048];
int length;
try {
while ((length = is.read(bf)) > 0) {
os.write(bf, 0, length);
}
} catch (IOException e) {
is.close();
os.close();
return;
}
is.close();
os.close();
}

解释:

  1. 首先我们请求的资源不是本地的资源,是存储在其他服务器上的,这里用到的是URL
  2. 这里我们需要设置 referer 和 cookie,结合前面使用的 URL, 这里使用的是URLConnection
  3. 后面的就很好理解了,相当于做了一个管道,将读取的文件流原封不动的通过Response返回给调用者
  4. 不要忘了设置 setContentType 为 MP4 的格式

nodejs 版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require("express");
const router = express();
const request = require("request");

router.get("/", (req, res) => {
const url = req.query.url;
const headers = {
"Referer": "http://music.163.com/",
"Cookie": "appver=1.5.0.75771;",
'Content-Type': 'video/mp4',
'Location': url
};
const options = {
header: headers,
url: url
};
request(options).on('error', err => {
res.send({ err })
}).pipe(res)
});

module.exports = router;

解释:
和上面的 Java 版代码是一个意思,主要是 pipe 流管道将文件流返回给调用者

功能完成

  • 那么这样解决了 MP4 地址防盗链的问题

缺点

  • 不足之处也暴露了
  • 首先这段代码必须部署到服务端
  • 部署到服务端就需要服务器去拉去 MV 的流信息,这无疑给服务器增加过多的流量压力
  • 其次,由于使用的流传输,这个 MP4 的播放是不支持快进操作的

有个简单的解决方式

  • 在 html5 之后,想去除 referer 信息, a标签有个属性 rel
  • rel="noreferrer" 即可在 a 标签的 href 的链接上去除 referer信息
  • 这一属性已被我使用在播放器的右下角的一个小飞机的按钮上
  • 点击小飞机按钮就可以直接看 MV 视频了,流量走的是网易云的CDN,不再试自己的服务器

joymusic-mv-no-referer-href

不完美

  • 总感觉这个解决不够完美
  • 如果你看到这篇文章能有更好的解决办法,请联系我

欢迎大家来看看试试看!😘 http://music.joylau.cn (当前版本 v1.4)

前言

效果展示

JoyMedia - Search

在线地址

解释

  • 正如文章图片那样,在搜索框中输入想听的音乐/歌手/专辑
  • 在输入过程中及输入完成后,显示搜索结果的列表供用户选择

材料

  • REST 接口
  • jquery-autocomplete插件

优美的开始

准备工作

  • 引入插件 css: jquery.autocomplete.css
  • 引入插件 js : jquery.autocomplete.min.js
  • 写一个数据返回的 REST 接口

开始操作

  • 定义搜索的 input 的 id 值
1
2
3
4
5
6
7
8
9
10
<div class="navbar-form navbar-left input-s-lg m-t m-l-n-xs hidden-xs">
<div class="form-group" style="display: inline">
<div class="input-group">
<span class="input-group-btn">
<button class="btn btn-sm bg-white btn-icon rounded"><i class="fa fa-search"></i></button>
</span>
<input id="keywords" type="text" class="form-control input-sm no-border rounded" placeholder="搜索 单曲/歌手/专辑...">
</div>
</div>
</div>
  • 这里我定义的是 keywords
  • 接下来在我们的 js 文件里调用 : $(“#keywords”).autocomplete
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
$("#keywords").autocomplete("/music/neteaseCloud/search", {
width : 350, // 提示的宽度,溢出隐藏
max : 30,// 显示数量
scrollHeight: 600,
resultsClass: "ac_results animated fadeInUpBig",
autoFill : false,//自动填充
highlight : false,
highlightItem: true,
scroll : true,
matchContains : true,
multiple :false,
matchSubset: false,
dataType: "json",
formatItem: function(row, i, max) {
//自定义样式
},
formatMatch: function(row, i, max) {
return row.name + row.id;
},
formatResult: function(row) {
return row.id;
},
parse:function(data) {
//解释返回的数据,把其存在数组里
if (data.data.length === 0) {
return [];
}else {
return $.map(data.data, function(row) {
return {
data: row
}
});
}

}
}).result(function(event, row, formatted) {
jQuery(this).val(row.name + ' ' + row.author);
addSearchResult(row.id);
});

接下来重点解释这个配置项

  • autocomplete 的第一个参数是url, 值得注意的是,这个 url 我们返回的结果数据是 JSON
  • 后面要专门针对返回的 JSON 数据进行解析
  • 再往后面来,看到的是一些配置项参数,一些简单的我就不在这多解释了,我这边主要说下我觉得比较重要的
  • resultsClass : 这个参数是生成的候选项的父 DIV,如下图所示:

JoyMedia - AutoComplate-Div

  • 默认提供的样式很不好看,默认提供的样式都写在 jquery.autocomplete.css 里面
  • 在这里面,能看到刚才截图的 div : ac_results
  • 那么我们要美化的就是 这个 div 和其子元素 li 的样式了
  • 为了跟契合本站的主题,我采用的黑色主题风格
  • 给ac_results添加了黑色背景色:background-color: #232c32
  • 在js文件里搜索ac_results,添加动画效果,并将这个配置写到配置项里:resultsClass: “ac_results animated fadeInUpBig”
  • ul 里的 li 是交替的样式的,class 分别为ac_odd和 ac_even,鼠标滑上去的效果为 ac_over,这几个地方自定义下样式
  • 还有一个配置: matchSubset,设置为 false ,可以避免输入大小写转换的js错误
  • formatItem : 返回的每一个结果都会再次处理,这里要做的事是以自己想要的样式显示出来
  • formatMatch : 匹配自己在结果集中想要的属性
  • formatResult : 自己最终要取的数据是什么
  • parse : 针对返回的JSON 数据进行转换,这里通过$. map 转化为 数组
  • result : 点击了列表项以后要做什么事情

完美的结束

欢迎大家来听听试试看!😘 http://music.joylau.cn (当前版本 v1.3)

前言

在线地址

Node.js 的学习

  • 入门是从这本书上开始的
  • 结合Node中文网的文档开始探索开发

说明

  • 利用 Node 来解析网易云音乐,其实质就是 跨站请求伪造 (CSRF),通过自己在本地代码中伪造网易云的请求头,来调用网易云的接口

分析

以获取歌曲评论来分析

  • 我们打开其中一首音乐,抓包看一下

JoyMedia - Node

  • 绝大部分的请求都是 POST 的
  • 我们找到其中关于评论的请求,如上图所示
  • 链接中间的部分是歌曲的 id 值
  • 在返回的 JSON 数据中包含了热评和最新评论
  • 评论过多的话是分页来展示的
  • 通过参数 limit 来显示评论数量, offset 来控制分页

JoyMedia - Node

  • 再来看,这是我本地浏览器中的 cookies 值,现在为止知道有个 csrf 值用来加密

JoyMedia - Node

  • 每个请求后面都会跟上csrf_token 值,其他的参数还有params 和 encSecKey
  • 这些值的加密算法无非是2种,一种是前台 js 加密生成的,另一种是将参数传往后台,由后台加密完再传回来
  • 想要测试一下很简单,将里面的值复制一下在 xhr 里找一下就知道了
  • 推测是是 js 加密的,加密的 js 简直不能看,如下图

JoyMedia - Node

  • 看到很多请求后面都返回了 md5 那么 md5 加密是肯定有的
  • 其实仔细看加密的参数,很多都能靠猜出来
  • 本地需要创建一个私钥secKey,十六位,之后aes加密生成,在通过rsa吧secKey加密作为参数一起传回
  • 那么下面贴出加密代码
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
const modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7';
const nonce = '0CoJUm6Qyw8W8jud';
const pubKey = '010001';
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
}

function aesEncrypt(text, secKey) {
const _text = text;
const lv = new Buffer('0102030405060708', "binary");
const _secKey = new Buffer(secKey, "binary");
const cipher = crypto.createCipheriv('AES-128-CBC', _secKey, lv);
let encrypted = cipher.update(_text, 'utf8', 'base64');
encrypted += cipher.final('base64');
return encrypted
}

function zfill(str, size) {
while (str.length < size) str = "0" + str;
return str
}

function rsaEncrypt(text, pubKey, modulus) {
const _text = text.split('').reverse().join('');
const biText = bigInt(new Buffer(_text).toString('hex'), 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
}
}
  • 挺复杂的,很多我也是参考网络上其他人的加密方式

伪造网易云头部请求

  • 这一步就很简单了,主要需要注意的就是 referer 的地址一定要是网易云的地址
  • 其他的想 cookie 和 User-Agent 直接复制浏览器的即可
  • 那我们构造一个 POST 的请求
  • 需要都回到函数和错误返回回调函数
  • 贴下代码
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
const Encrypt = require('./crypto.js');
const http = require('http');
function createWebAPIRequest(host, path, method, data, cookie, callback, errorcallback) {
let music_req = '';
const cryptoreq = Encrypt(data);
const http_client = http.request({
hostname: host,
method: method,
path: path,
headers: {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'http://music.163.com',
'Host': 'music.163.com',
'Cookie': cookie,
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36',

},
}, function (res) {
res.on('error', function (err) {
errorcallback(err)
});
res.setEncoding('utf8');
if (res.statusCode !== 200) {
createWebAPIRequest(host, path, method, data, cookie, callback);

} else {
res.on('data', function (chunk) {
music_req += chunk
});
res.on('end', function () {
if (music_req === '') {
createWebAPIRequest(host, path, method, data, cookie, callback);
return
}
if (res.headers['set-cookie']) {
callback(music_req, res.headers['set-cookie'])
} else {
callback(music_req)
}
})
}
});
http_client.write('params=' + cryptoreq.params + '&encSecKey=' + cryptoreq.encSecKey);
http_client.end()
}
  • 那么再结合我们刚才分析的评论API, 发出该请求
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
const express = require("express");
const router = express();
const { createWebAPIRequest } = require("../common");

router.get("/", (req, res) => {
const rid=req.query.id;
const cookie = req.get('Cookie') ? req.get('Cookie') : '';
const data = {
"offset": req.query.offset || 0,
"rid": rid,
"limit": req.query.limit || 20,
"csrf_token": ""
};
createWebAPIRequest(
'music.163.com',
`/weapi/v1/resource/comments/R_SO_4_${rid}/?csrf_token=`,
'POST',
data,
cookie,
music_req => {
res.send(music_req)
},
err => res.status(502).send('fetch error')
)
});

module.exports = router;
  • 值得注意的是,这里我的 node 模板选择的 EJS 所使用的 js 语法格式也比较新,你需要将你 WebStorm 的 js 编译器的版本提升到ECMAScript 6,否则的话会报错,如下图所示:
    JoyMedia - Node

封装

  • 我们写一个入口文件,可以直接运行期容器,以及提供 APIs
  • 那么,这个就跟简单了
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
const express = require('express');
const http = require('http');
const app = express();


const port = 3000;

const v = '/apis/v1';

app.listen(port, () => {
console.log(`server starting ${port}`)
});

/*APIs 列表*/
app.use(express.static('public'));


//推荐歌单
app.use(v + "/personalized", require("./apis/personalized"));

//歌单评论
app.use(v + '/comment/playlist', require('./apis/comment_playlist'));

//获取歌单内列表
app.use(v + '/playlist/detail', require('./apis/playlist_detail'));

//获取音乐详情
app.use(v + '/song/detail', require('./apis/song_detail'));

//单曲评论
app.use(v + '/comment/music', require('./apis/comment_music'));

//获取音乐 url
app.use(v + '/music/url', require('./apis/musicUrl'));

// 获取歌词
app.use(v + '/lyric', require('./apis/lyric'))


process.on('uncaughtException', function (err) {
//打印出错误的调用栈方便调试
console.log(err.stack);
});


module.exports = app;
  • 引用 http 模块,开启 node 的默认3000 端口
  • 目前提供了上述注释里所写的 APIs
  • 每一个 API 都会单独写一个模块,以在此调用
  • 有一个地方值得注意的事
  • node 是单线程的异步 IO,这使得他在高并发方面得到很快相应速度,但是也有缺点
  • 当其中一个操作出错异常了,就会导致整个服务挂掉
  • 我在此的处理方式是:监听全局异常,捕到异常后将错误的堆栈信息打印出来,这样使得后续的操作不得进行以至于使整个服务挂掉
  • 当然,还有其他的方式来处理,可以通过引用相应的模块,来守护 node 的进程,简单的来说就是挂掉我就给你重启
  • 我觉得第二种方式不是我想要的,我是采取的第一种方式
  • 况且我还真想看看是什么错误引起的
  • 最后发现都是网络原因引起的错误 🤣🤣🤣🤣😂😂😂😂😂

运行

  • npm install
  • node app.js

查看效果

JoyMedia - Node

JoyMedia - Node

欢迎大家来听听试试看!😘 http://music.joylau.cn (当前版本 v1.3)

JoyMedia - Beta - 预览图
JoyMedia - Beta - 系统结构

在线地址

JoyMedia - Beta 预览版

项目介绍

实现目的

  • 本人经常在写代码或者没事的时候会听一些音乐
  • 以前大部分会选择本地安装客户端
  • 其中最喜欢的认为做的比较好的音乐客户端实属网易云音乐了
  • 无论是从 Mac 版的客户端,还是 IOS 版的客户端,界面都非常优美,简直是极客和码农的必备
  • 最主要是的网易云的歌曲推荐功能,很强大,我一度认为2个人的歌单相似度超过90%,就可以在一起了,这样再也不怕找不到对象了,😆
  • 但也有些问题,网易云有一些版权音乐,是无法听到的,有时候昨天还在听得音乐,今天就听不了了
  • 这就很烦了

自己的想法

  • 最初想把这个版权音乐的 mp3 地址解析出来,这样就可以直接听了
  • 恩,想法很 nice
  • 那么,照着这个想法做吧

项目实现

总体架构

  • 正如上述系统结构所示
  • 我自己有2台云服务器,一台阿里云的,另一台是腾讯云的
  • 这2台服务器,我是这样分配的: 阿里云只提供 WEB 服务,腾讯云为 WEB 访问提供各种服务
  • 当然服务器上我还跑了其他服务

阿里云服务器

  • Nginx 主要负责了 JoyMedia 的 负载均衡,在该台服务器上,我用 部署了2个 spring-boot 项目,以权重的方式配置了负载均衡,这样我在更新项目的时候可以保证另一个服务的可用性
  • 当然 Nginx 还有个反向代理的作用, upstream 配置了其他项目的访问
  • 还有台 Redis 服务了,爬到的数据会存到 Redis 了,以供 WEB 服务迅速读取,当然在有些地方不会读取 Redis ,比如单曲歌曲播放的 mp3地址的获取
  • 在最开始的时候我会先在后台解析出来再存到 Redis 里,但是发现网易云的歌曲 mp3 地址失效太快了,有时会导致播放异常,不如实时解析来的实在
  • 在比如单首歌曲的评论的获取,这个得是实时解析的

腾讯云服务器

  • 提供网易云音乐解析的是一个 Node 服务,这个 Node 服务是如何解析地址的,这个需要单独再写一篇文章,先知道这个 Node 服务是干嘛的就好
  • 然后部署了3个spring-boot服务,分别提供了各自的服务,有定时爬去网易云音乐的推荐歌单,爬取歌单的歌曲列表,爬取歌单评论
  • 由于爬到的音乐信息很快就会失效,这个服务都要定时的爬取
  • 爬取到的数据的落地存储,我是存到的MongoDB中,在这篇文章中:重剑无锋,大巧不工 SpringBoot — 整合使用MongoDB , 我说明了为什么要选择 MongoDB
  • 这3个服务爬到的数据会实时存到 Redis 中,另一方面,会异步存到 MongoDB 中,我想着这些数据或许还能做什么数据分析之类的,😄

初版完成后

等我搭建完这个服务后,发现了问题

  • 有版权控制的音乐根本解析不到 mp3 的实际地址
  • 那么我想听的音乐,听不到还是听不到,突然变得很尴尬

又有了想法

  • 一般情况下,我们在一家音乐网站上找不到自己想要的音乐,就回去其他音乐网站上找
  • 恩,就这么干
  • 网易云找不到的音乐,我就去虾米音乐,去 QQ 音乐找
  • 这2个网站的音乐我都小试了下,都是可以的
  • 于是我现在把这些功能集中在页面的搜索框中,搜索这3个音乐网站的结果,然后实施解析来播放
  • 这是我下步要做的事情

有些地方还有 BUG

  • 有些地方还是有 BUG 的,需要修复

有些地方功能还没写好

  • 比如右上角的用户登录,现在的想法是使用第三方登录,比如 QQ, 微信…,但是是登录网易云音乐呢,还是登录网站呢?
  • 要是登录网易云音乐的话,估计账号安全是个问题,而且登录接口不能频繁调用
  • 要是登录网站的,好像没什么卵用
  • 再比如左下角的歌词界面,虽然能获取到歌词,但是怎么做到歌词随着歌曲的播放实时滚动,这个现在还没有头绪…

还在继续开发中…

前言

MongoDB 安装

  • yum install mongodb-server mongodb
  • systemctl start mongod
  • whereis mongo

MongoDB 配置文件

  • 修改 bind_ip为 0.0.0.0 即可外网可访问
  • 修改 fork 为 true 即可后台运行
  • 修改 auth为 true 即访问连接时需要认证
  • 修改 port 修改端口号

开始使用

引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

配置文件

mongoDB配置
还有种配置url方式: spring.data.mongodb.uri=mongodb://name:pass@host:port/db_name

相比这种方式,我觉得第一种截图的方式要更直观一些

在 SpringBoot 项目中使用

  • 主要的一个接口MongoRepository<T,ID>,第一个是要存储的实体类,第二个参数是 ID 类型
  • 自定义一个接口实现上述接口
  • 定义实体类
  • 自定义实现类可直接注入使用
  • 默认的已经存在了增删改查的方法了,可以直接使用
  • 想要更多的功能可以在接口中实现更多的自定义
  • 下面截图所示:

自定义一个 DAO :
mongoDB-DAO

查看如何使用 :
mongoDB-method
有个 username 忘了配置了,得加上的

使用起来就是如此简单,感觉使用起来很像 mybatis 的 mapper 配置

有一些注解的配置

有时候使用起来会有一些问题

  • 在默认策略下, Java 实体类叫什么名字,生成后的表名就叫什么,但我们可能并不想这样
  • 同样的道理,有时属性名和字段也并不想一样的
  • 有时一些属性我们也并不想存到 MongoDB

注解解决这些问题

  • @Id : 标明表的 ID , 自带索引,无需维护
  • @Document : 解决第一个问题
  • @Field : 解决第二个问题
  • @Transient : 解决第三个问题

此外,还有其他的注解

可能并不常用,在此也说明下

  • @Indexed(unique = true) : 加在属性上,标明添加唯一索引
  • @CompoundIndex : 复合索引

预览

查看下刚爬的网易云官网的歌曲信息吧

![歌曲信息](//s3.joylau.cn:9000/blog/springboot-mongoDB-preview.gif)

前言

  • ZeroC Ice 的背景我就不介绍了
  • ZeroC Ice 环境安装搭建,概念原理,技术基础,这些网络上都有,再介绍的话就是copy过来了,没有多大意义,不再赘述了
  • 下面我们开始实战

开始动手

  • 首先我们需要几个ice接口文件,比如说这几个:
    Ice 文件展示
  • 我们来看一下其中一个ice文件定义的接口说明
    Ice接口文件说明
    文件里定义了5个接口,可以很明显的的看到是区间的增删改查接口
    刚好很适合我们对外提供增删改查的RESTFul API 接口
    这里在对外提供 RESTFul API 是可以很清楚的 使用 POST GET PUT DELETE
    可以说这里很好的提供了这样一个例子
  • 命令 slice2java xxx.ice 生成 java 的 client,server类
    生成的Java类
    生成的Java文件很多,这个不用管,更不必更改里面的代码内容
    你要是有兴趣的话,也可以将这些文件分为 client 和 server 分门别类的归纳好
    打开看一下,里面的代码很混乱,无论是代码风格,样式,变量命名,对于我来说,简直不忍直视
    生成的Java代码
  • 编写client类
    client类
    代码如下:
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
@Data
@Component
@ConfigurationProperties(prefix = "ice")
public class Client {
private String adapterName;
private String host;
private int port;

private Logger _logger = LoggerFactory.getLogger(Client.class);

/**
* 执行操作
*
* @param command 命令体
* @return Result
*/
public Result execute(CommandBody command) {
Ice.Communicator ic = null;
try {
//初使化通信器
ic = Ice.Util.initialize();
//传入远程服务单元的名称、网络协议、IP及端口,获取接口的远程代理,这里使用的stringToProxy方式
Ice.ObjectPrx base = ic.stringToProxy(getStringProxy());
//通过checkedCast向下转换,获取接口的远程,并同时检测根据传入的名称获取的服务单元是否代理接口,如果不是则返回null对象
ZKRoadRangeAdminPrx interfacePrx = ZKRoadRangeAdminPrxHelper.checkedCast(base);
if (interfacePrx == null) {
return new Result(false, "Invalid proxy");
}
//把接口的方法传给服务端,让服务端执行
Result result = executeCommand(command, interfacePrx);
if (result == null) {
return new Result(false, "暂无此操作命令");
}
return result;
} catch (Exception e) {
_logger.info(e.getMessage(), e);
return new Result(false, "连接错误!" + e);
} finally {
if (ic != null) {
ic.destroy();
}
}
}

/**
* 执行操作命令
*
* @param command 命令体
* @param interfacePrx 接口
* @return ProgramResponse
*/
private Result executeCommand(CommandBody command, ZKRoadRangeAdminPrx interfacePrx) {
CommandType type = command.getCommandType();
if (type.equals(CommandType.addRange)) {
return returnMessage(interfacePrx.AddRange(command.getZkRoadRange()));
} else if (type.equals(CommandType.updateRange)) {
return returnMessage(interfacePrx.UpdateRange(command.getZkRoadRange()));
} else if (type.equals(CommandType.removeRange)) {
return returnMessage(interfacePrx.RemoveRange(command.getZkRoadRange().code));
} else if (type.equals(CommandType.getRange)) {
return new Result(true, JSONObject.toJSONString(interfacePrx.GetRange(command.getZkRoadRange().code)));
} else if (type.equals(CommandType.listRanges)) {
return new Result(true, JSONObject.toJSONString(interfacePrx.ListRanges()));
}
return null;
}


/**
* 获取配置的地址信息
*
* @return String
*/
private String getStringProxy() {
return adapterName + ":tcp -h " + host + " -p " + port;
}


private Result returnMessage(boolean result) {
return result ? new Result(true, "success") : new Result(false, "failure");
}

}
  • 需要三个配置: 适配器名,IP地址,端口号,配置在SpringBoot项目里,如下:
    ICE配置信息

再封装一下

  • 封装返回消息体
    ICE配置信息
  • 封装执行命令体
    ICE配置信息

重要

  • 调用 ice 里的接口方法:获取远程代理的 checkedCast
  • 获取远程接口的 interfacePrx 可直接调用 ice 文件里的方法
  • 服务端的 Ice 版本最好和 客户端的版本相同
  • 服务端提供服务时需要创建一个 servant ,一般的我们会在接口名后面加一个I,以此命名作为Java文件类名
  • 该servant继承 接口文件的Disp类,并重写接口中定义的方法,实现具体的业务逻辑
  • Server端创建一个适配器 adapter,将servant 放进去
  • 服务退出前,一直对请求持续监听

听首歌回忆下

实验步骤

  • 新建一个项目
  • 可先分别在码云和 GitHub 上建好仓库<可选>
  • 将项目提交的码云上
  • 项目提交到另一个仓库的时候重新 define remote <可选>
  • 之后每次先提交到本地仓库,可以根据每次提交到本地仓库的不同,来选择定义的 remote 来分别提交
  • 每次 pull 也可以选择仓库

遇到个问题

问题

  • 在我新建好码云的仓库后,提交项目,遇到 Git Pull Failed: fatal: refusing to merge unrelated histories

原因

  • 原因:git拒绝合并两个不相干的东西

解决

  • 此时在命令行输入 : git pull origin master –allow-unrelated-histories
  • 要求我输入提交信息
  • 输入完成后,按一下Esc,再输入:wq,然后回车就OK了
  • 再回来提交就可以了

系统工具

  • BetterZip : mac上面的最好的解压工具
  • CHM View : 查看chm类型的开发文档
  • Easy New File Free : 右击桌面,可以像win一样新建文件
  • Bartender 2 : 任务栏menu图标整理
  • iStat Menus : 系统网速、cpu、内存监控工具
  • SwitchResX : 外接显示器,调节DPI
  • Go2Shell : 在finder的任意文件夹下打开终端
  • Aria2GUI : 突破百度限速
  • Alfred 3 : 效率神器,谁用谁知道
  • PDF Expert : 查看pdf
  • 远程桌面连接 : mac电脑上远程连接windows,网址: https://rink.hockeyapp.net/apps/5e0c144289a51fca2d3bfa39ce7f2b06 (2017年10月26日加)

播放器

  • 网易云音乐 :这个必备啊
  • 优酷 :这个可以免费看1080P视频,没广告,有时候出抽风的时候还可以看会员视频
  • OBS : 视频直播、录制软件
  • Movist : 视频播放器,支持的格式很多

小工具

  • CleanMyMac 3 : 清理mac电脑垃圾
  • ShadowsocksX : 翻墙必备
  • TeamView : 桌面远程软件
  • MacDown : 开源的markdown编辑器
  • Path Finder : Finder增强版
  • Parallels Desktop : 虚拟机
  • FileZilla : ftp工具
  • Foxmail : 邮箱客户端
  • Folx : 下载工具

开发工具

  • FireFox : 火狐
  • Google Chrome : 必备
  • IntelliJ IDEA : 必备IDE
  • WebStorm : web开发必备
  • DataGrip : 数据库管理软件
  • Navicat Premium : 已经使用习惯的MySQL连接工具,也支持其他数据库
  • XShell : SSH远程连接工具,我还是比较喜欢终端下的ssh命令连接,虽然有一个家族的系列产品
  • Sublime Text3 : 文本编辑器
  • Beyond Compare : 文本比较工具
  • GitHub Desktop : github GUI客户端
  • rdm : redis可视化GUI界面
  • HBuilder : h5开发工具
  • iTerm : 终端

自己暂时使用的工具都已归纳出来,以后有新的好用的工具,会加上的,Mac下大部分工具都是收费的,你可以偷偷点一下 xclient.info

0%