乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

越来越火的网络请求Fetch和Axios到底有什么区别

发表于 2021-11-30

在这几天由于编写脚本等一系列原因,不知怎么的突然发现现在很多主流的网站已经大量开始使用Fetch进行网络请求,感觉再不学习Fetch就要Out了,所以我花了一些时间专门去研究了一下关于Fetch的相关知识,发现Fetch被讨论的并不多,很多都是一年前甚至两年前的文章,大多数文章最后得出来的结论几乎都是Axios比Fetch好用。

事实确实如此,就我个人的体验来讲,Axios使用体验确实优于Fetch,那么为什么目前在很多大公司的网站上面都开始使用Fetch进行网络请求,带着这个疑问,我又去查找了很多资料,同时又自行将同样的请求使用Axios和Fetch进行尝试,最后得出一个结论:Fetch的优势仅仅在于浏览器原生支持。

对的,其实Fetch比起Axios来讲几乎没有任何优势(除了浏览器原生支持),Axios各个方面都比Fetch好用,Fetch要想实现Axios的一些功能还需要手动进行封装。

我截取了几个比较大的网站的请求图:

掘金:

img

YouTube:

img

知乎:

img

需要注意的是:Axios是对XMLHttpRequest的封装,而Fetch是一种新的获取资源的接口方式,并不是对XMLHttpRequest的封装。

它们最大的不同点在于Fetch是浏览器原生支持,而Axios需要引入Axios库。

1. 火热程度

虽然无法进行直观的比较,但是我们可以从npm包下载量来看:

img

因为Node环境下默认是不支持Fetch的,所以必须要使用node-fetch这个包,而这个包的周下载量一路攀升,可以看到已经来到了每周2千多万的下载量。这还仅仅是Node环境下,浏览器则是默认支持不需要第三方包。

img

上面是Axios的下载量,可以看到也是一路攀升,Axios封装的各种方法确实非常的好用。


本篇文章着重会从下面几项内容进行比较:

  • 兼容性
  • 基本语法
  • 响应超时
  • 对数据的转化
  • HTTP拦截器
  • 同时请求

2. 兼容性问题

Axios可以兼容IE浏览器,而Fetch在IE浏览器和一些老版本浏览器上没有受到支持,但是有一个库可以让老版本浏览器支持Fetch即它就是whatwg-fetch,它可以让你在老版本的浏览器中也可以使用Fetch,并且现在很多网站的开发都为了减少成本而选择不再兼容IE浏览器。

注意:在比较旧的浏览器上面可能还需要使用promise兼容库。

各个浏览器对Fetch的兼容:

img

3. 请求方式

下面我们来看一下如何使用Axios和Fetch进行请求。

Axios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const options = {
url: "http://example.com/",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
data: {
a: 10,
b: 20,
},
};

axios(options).then((response) => {
console.log(response.status);
});

Fetch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const url = "http://example.com/";
const options = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({
a: 10,
b: 20,
}),
};

fetch(url, options).then((response) => {
console.log(response.status);
});

其中最大的不同之处在于传递数据的方式不同,Axios是放到data属性里,以对象的方式进行传递,而Fetch则是需要放在body属性中,以字符串的方式进行传递。

4. 响应超时

Axios的相应超时设置是非常简单的,直接设置timeout属性就可以了,而Fetch设置起来就远比Axios麻烦,这也是很多人更喜欢Axios而不太喜欢Fetch的原因之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
axios({
method: "post",
url: "http://example.com/",
timeout: 4000, // 请求4秒无响应则会超时
data: {
firstName: "David",
lastName: "Pollock",
},
})
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));

Fetch提供了AbortController属性,但是使用起来不像Axios那么简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const controller = new AbortController();

const options = {
method: "POST",
signal: controller.signal,
body: JSON.stringify({
firstName: "David",
lastName: "Pollock",
}),
};
const promise = fetch("http://example.com/", options);

// 如果4秒钟没有响应则超时
const timeoutId = setTimeout(() => controller.abort(), 4000);

promise
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));

5. 对数据的转化

Axios还有非常好的一点就是会自动对数据进行转化,而Fetch则不同,它需要使用者进行手动转化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// axios
axios.get("http://example.com/").then(
(response) => {
console.log(response.data);
},
(error) => {
console.log(error);
}
);

// fetch
fetch("http://example.com/")
.then((response) => response.json()) // 需要对响应数据进行转换
.then((data) => {
console.log(data);
})
.catch((error) => console.error(error));

Fetch提供的转化API有下面几种:

  • arrayBuffer()
  • blob()
  • json()
  • text()
  • formData()

使用Fetch时你需要清楚请求后的数据类型是什么,然后再用对应的方法将它进行转换。

Fetch可以通过一些封装实现Axios的自动转化功能,至于如何实现由于我没有去研究过所以就不再这里多嘴,不过实现起来应该不难,但是要将实现过程写的健壮就需要花费一定的时间。

6. HTTP拦截器

Axios的一大卖点就是它提供了拦截器,可以统一对请求或响应进行一些处理,相信如果看过一个比较完整的项目的请求封装的话,一定对Axios的拦截器有一定的了解,它是一个非常重要的功能。

使用它可以为请求附加token、为请求增加时间戳防止请求缓存,以及拦截响应,一旦状态码不符合预期则直接将响应消息通过弹框的形式展示在界面上,比如密码错误、服务器内部错误、表单验证不通过等问题。

1
2
3
4
5
6
7
8
9
axios.interceptors.request.use((config) => {
// 在请求之前对请求参数进行处理
return config;
});

// 发送GET请求
axios.get("http://example.com/").then((response) => {
console.log(response.data);
});

而Fetch没有拦截器功能,但是要实现该功能并不难,直接重写全局Fetch方法就可以办到。

1
2
3
4
5
6
7
8
9
10
11
12
fetch = ((originalFetch) => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log("请求已发送"));
};
})(fetch);

fetch("http://example.com/")
.then((response) => response.json())
.then((data) => {
console.log(data);
});

7. 同时请求

同时请求在项目中用的不多,但是偶尔可能会用到。

Axios:

1
2
3
4
5
6
7
8
9
10
axios
.all([
axios.get("https://api.github.com/users/iliakan"),
axios.get("https://api.github.com/users/taylorotwell"),
])
.then(
axios.spread((obj1, obj2) => {
...
})
);

Fetch:

1
2
3
4
5
6
7
8
9
10
11
Promise.all([
fetch("https://api.github.com/users/iliakan"),
fetch("https://api.github.com/users/taylorotwell"),
])
.then(async ([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
})
.catch((error) => {
console.log(error);
});

8. 浏览器原生支持

Fetch唯一碾压Axios的一点就是现代浏览器的原生支持。

本着负责的态度(其实是因为这篇文章写得比较困难…因为我对Fetch的研究不深)在这几天,我多次尝试使用Fetch,习惯后觉得还挺好用的,最主要是浏览器原生就支持,不像Axios需要引入一个包,而且需要即时测试某些接口直接在Chrome浏览器中使用Fetch进行请求,尤其是编写爬虫或脚本的时候,你在当前网页打开Chrome的控制台使用Fetch几乎不需要什么配置就可以直接进行请求。

img

上图是在知乎打开Chrome控制台然后调用知乎个人数据API,可以看到能够成功的拿到数据。

9. 最后

Fetch可以实现所有Axios能够实现的功能,但是需要自行进行封装,如果不喜欢折腾直接在项目中使用Axios是一个非常明智的选择,这完全取决于你是否愿意使用浏览器内置API。

有时候新技术逐渐取代老技术是一个必然趋势,所以Fetch有一天终将会取代XMLHttpRequest,也许之后Axios库会改为使用Fetch请求

本文为转载文章 点击进入原文

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

海底掘金自动化挖矿实现

发表于 2021-11-29

前言

最近掘金出了一款小游戏叫做海底掘金, 挖取矿石可以兑换周边礼品

我眼馋那款游戏机已久, 于是乎就玩起了这款游戏, 不过每天手动操作实在太过劳神, 于是, 我打算将其流程自动化

所有的数据都是离不开接口, 我们只需要模拟接口请求即可

那么首先要做的就是分析游戏中每一个动作都发送了什么数据

动作分析

  1. 上移

    1
    {"command":["U"]}

    image-20211129115453501

  2. 下移

    1
    {"command":["D"]}

    image-20211129115518866

  3. 左移

    1
    {"command":["L"]}

    image-20211129115538071

  4. 右移

    1
    {"command":["R"]}

    image-20211129115556431

  5. 跳跃

    1
    2
    3
    4
    5
    6
    7
    8
    //上跳跃
    {command: ["8"]}
    //下跳跃
    {command: ["2"]}
    //左跳跃
    {command: ["4"]}
    //右跳跃
    {command: ["6"]}

    image-20211129120520331

  6. 一层循环2次上移

    1
    {command: [{times: 2, command: ["U"]}]}
    • times: 表示循环次数

    image-20211129115642187

  7. 两层循环

    1
    {command: [{times: 2, command: [{times: 3, command: ["D"]}]}]}

    image-20211129115708099

    多层循环则多层嵌套

接口分析

  1. 动作接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=xxx&time=xxx
  2. 游戏结束接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/over?uid=xxx&time=xxx
  3. 游戏开始接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/start?uid=xxx&time=xxx
  4. Token获取接口

    1
    https://juejin.cn/get/token

    这个接口获取到的值其实就是其他接口中请求头authorization的值, 具体往下看

  5. 获取矿石数量相关信息

    1
    https://juejin-game.bytedance.com/game/sea-gold/home/info?uid=xxx&time=xxx

浏览器控台自动化实现

我们跳过登录验证操作, 先从简单的控台自动化入手, 实现动作的自动化, 有效避免作弊检测

比如我们想要实现以下动作的反复执行

image-20211129121755381

那么发送的数据如下:

1
{"command":[{"times":10,"command":["L","2","R",{"times":10,"command":["L","4","D",{"times":10,"command":["R","D","2"]}]}]}]}

这一步我们可以直接从浏览器控制台里拷贝出来, 不用自己计算, 方便快捷准确

然后结合动作接口和请求头进行模拟请求即可

如下请求头

  • Content-type
  • authorization (关键)
  • x-tt-gameid (关键)
  • accept

以上请求头数据直接从控制台拷贝

代码实现如下:

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
await (async function autoRun(){
//以下三个字段值替换成你自己的就行
let uid = 'xxx';// 你的uid
let authorization = 'xxx'; // request headers中的authorization
let gameid = 'xxx'; // request headers中的x-tt-gameid


let params = {"command":[{"times":10,"command":["L","2","R",{"times":10,"command":["L","4","D",{"times":10,"command":["R","D","2"]}]}]}]};

let datarus = await fetch('https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=' + uid + '&time=' + Date.parse(new Date()), {
method: 'POST',
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': gameid,
},
body: JSON.stringify(params)
}).then(async (res) => {
return res.json();
});

return datarus;

})();

注意, 每一局的gameId是不一样的, 使用相同的gameId会报游戏异常, 这种操作大家要尽量避免, 谁知道官方有没有利用这个来判定玩家是否作弊呢, 谨慎为上

请求成功后响应数据如下:

1
{"data":{"appendMapData":[0,0,133,0,0,0,27,122,6,0,0,22,6,0,0,6,135,6,0,21,21,0,0,122,0,0,0,0,23,0,6,6,0,0,6,0,25,0,0,121,0,6,6,0,29,0,6,6,0,151,6,0,0,6,27,0,0,6,0,0,0,0,125,6,0,0,0,5,0,135,113,143,0,6,131,0,0,102,101,151,6,6,0,0,0,0,3,0,0,6,0,6,6,6,122,0,6,6,0,6,6,0,0,0,29,0,6,0,6,0,0,101,6,0,24,6,6,0,0,0,6,0,6,6,6,132,0,6,0,0,0,0,6,0,5,6,6,131,114,0,0,0,0,0,0,6,6,0,6,0,6,6,6,0,104,0,6,0,4,0,0,0,6,125,6,113,0,0,0,6,0,6,6,0,0,0,6,6,0,0,6,0,5,6,5,121,28,0,0,24,0,0,6,151,0,0,141,6,0,24,0,6,0,0,6,6,0,0,131,6,105,6,0,0,27,113,6,0,6,0,0,143,0,0,101,0,29,0,0,6,0,0,0,133,6,6,25,24,151,29,6,104,6,0,6,6,5,0,0,131,6,131,6,6,0,0,0,6,0,28,133,0,124,6,6,0,0,0,131,0,22,0,0,0,0,0,0,0,22,131,6,6,0,6,6,24,124,6,0,0,101,6,6,0,0,0,103,0,6,26,0,0,0,0,103,0,6,0,6,131,0,6,0,6,6,6,0,141,6,29,0,0,0,6,0,3,6,0,6,6,6,0,0,114,0,0,6,0,0,6,0,6,0,6,6,6,0,0,28,23,6,102,0,115,4,6,6,0,6,27,6,6,0,6,6,0,0,24,0,3,6,0,0,143,0,29,0,26,0,4,6,105,0,0,0,6,0,21,132,21,0,0,0,6,4,103,6,0,27,6,0,24,24,0,6,104,0,104,0,0,6,6,152,6,0,6,0,0,0,0,0,0,0,6,24,6,6,23,143,0,0,24,0,6,0,6,0,0,0,151,0,26,0,6,0,0,0,0,6,0,0,0,0,6,135,0,6,6,0,0,0,0,29,6,0,0,6,0,24,25,6,0,0,0,0,0,6,0,0,6,0,6,152,28,6,25,6,0,6,0,6,6,0,0,0,25,0,0,6,122,6,0,0,6,112,6,0,0,124,0,0,0,0,25,0,6,6,6,6,27,112,24,121,6,6,0,6,0,6,0,6,115,0,22,6,6,6,115,6,6,101,0,27,5,105,0,0,0,0,0,0,0,6,0,0,0,3,6,6,0,0,0,0,6,0,0,0,0,26,6,6,0,0,6,6,6,6,6,0,6,0,6,6,0,0,0,0,6,0,6,6,0,0,3,5,104,0,0,122,27,0,0,0,0,23,0,0,0,0,0,4,23,25,6,0,153,0,0,0,0,0,6,0,0,0,6,6,6,0,6,0,4,0,6,6,0,3,0,112,0,0,0,0,0,0,6,0,113,0,0,0,6,6,21,0,6,124,0,6,115,0,0,23,132,0,0,6,0,6,6,0,6,6,6,0,6,0,6,0,0,6,6,0,6,6,26,0,134,0,6,6,6,0,0,0,0,0,0,0,0,0,6,6,6,113,111,0,6,0,6,0,0,0,0,0,124,0,0,6,0,22,6,6,24,22,6,104,0,125,0,0,6,6,0,6,6,0,0,121,6,0,6,0,0,6,0,6,6,0,0,0,0,0,25,0,0,6,0,6,6,6,111,0,0,6,0,5,0,0,0,6,6,6,0,6,0,123,6,0,6,25,0,0,6,113,0,0,6,0,0,0,0,0,6,0,0,6,122,6,134,0,0,6,0,134,0,6,0,0,0,6,103,0,24,0,6,0,6,0,0,0,0,0,6,6,0,132,6,0,6,103,6,0,6,28,26,6,0,111,6,131,0,27,27,0,6,6,0,102,0,0,6,0,6,0,0,6,21,6,0,6,0,6,121,26,0,6,0,6,0,0,0,0,0,0,6,0,24,0,0,0,6,0,4,0,22,23,28,0,6,6,111,153,131,0,0,0,0,0,6,27,0,0,23,0,0,0,0,6,0,0,104,0,0,0,104,6,0,0,0,6,6,0,0,6,29,6,0,0,0,0,6,6,0,0,6,6,0,0,0,23,6,0,0,0,6,0,113,6,0,6,6,6,0,0,0,29,152,0,0,0,153,0,6,6,0,6,6,0,26,6,6,6,151,21,0,21,6,0,21,6,6,6,0,6,0,0,0,22,6,6,6,0,6,0,21,6,6,0,6,6,22,0,5,0,6,0,0,0,0,0,28,6,0,6,114,0,6,0,0,6,0,23,0,0,6,0,0,114,6,6,0,0,0,6,0,0,0,0,6,0,6,6,0,0,0,0,0,0,0,24,0,0,0,6,134,0,0,0,131,0,0,0,0,6,6,25,0,0,6,5,0,104,0,6,6,0,0,6,0,0,0,6,0,6,101,0,0,6,0,6,0,0,0,125,0,0,6,134,0,6,6,27,0,6,0,131,6,0,0,6,0,0,6,0,123,0,0,0,0,6,0,0,6,6,6,6,0,6,0,6,0,0,0,25,0,0,0,0,0,6,0,0,6,0,6,0,0,0,6,0,6,6,6,114,6,6,0,0,0,0,0,6,0,0,6,124,6,6,0,0,154,6,0,152,0,0,22,6,6,6,0,6,26,0,0,111,0,133,29,0,0,6,6,104,133,0,0,6,26],"curPos":{"x":5,"y":210},"blockData":{"moveUp":29,"moveDown":30,"moveLeft":14,"moveRight":27,"jump":7,"loop":15},"gameDiamond":112},"code":0,"message":"success","logId":"xxxx","serviceTime":1638159487113}

注意看返回的gameDiamond这个字段, 我们执行一次接口立马就获取了112个矿石, 也就是说我们掉完接口立马结束游戏, 那么这矿石就到手了, 省去了游戏执行的时间

在同一局中, 我们可以反复调接口, 直到道具不足为止, 如果道具不足就重开一局再过一遍之前的代码

到这里 问题就来了, 每一局的x-tt-gameid都不一样, 每新开一局都要拷贝一次太繁琐了,接下来我们解决x-tt-gameid的问题

签名破解

接下来全局翻找源码, 从js源码中, 我大致猜测这个x-tt-gameid是由ES256算法生成accessToken, 找到如下代码:

1
h.default.sign({gameId:this.gameId,time:t},"-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n",{algorithm:"ES256",expiresIn:2592e3,header:{alg:"ES256",typ:"JWT"}});return console.log("token",e)

当然也只是猜测, 需要进行验证

为了方便测试, 我将代码测试从控台转移到nodejs环境中

对于nodejs不太熟悉的朋友, 我这里简单介绍一下:

nodejs可以通俗理解为是模拟了一个浏览器的环境, 相当于java中的jre, 以前js代码只能在浏览器中执行, 有了nodejs环境, 我们就可以脱离浏览器随处运行js代码啦

Nodejs官网

好了 下载安装完nodejs之后, 安装jsonwebtoken模块用于jwt的生成

1
npm install jsonwebtoken

然后运行以下代码测试:

1
2
3
4
5
const jwt = require('jsonwebtoken');
const privatekey = "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n"

console.log(jwt.decode("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJnYW1lSWQiOiIyMDIxLTExLTI2IDA5OjUwOjIwIiwidGltZSI6IjE2Mzc5MTMyNjIwMTkiLCJpYXQiOjE2Mzc5MTMyNjIsImV4cCI6MTY0MDUwNTI2Mn0.2SWFcrvSMGzRIhsZe6ny0ugj5eoJnb4DlGGudFzUgvwPTqbeZaSa2FG5jYF5VPOXfv7Ye0TlYPt3VmviclOGKQ")
)

解码得到:

1
2
3
4
5
6
{
gameId: '2021-11-26 09:50:20',
time: '1637913262019',
iat: 1637913262,
exp: 1640505262
}

至此, 我们既验证了密钥的正确性, 同时得到了加密参数, 而这个time则是当前时间毫秒值

那接下来我们需要在游戏中实际验证一下才行, 验证步骤如下:

  1. 调用游戏开始接口获取返回的gameId
  2. 将gameId进行ES256加密得到签名
  3. 调用动作接口, 并将签名放入x-tt-gameid请求头中
  4. 请求成功, 验证ok

好了 我们的第一阶段的破解工作已完毕, 接下来就是对代码进行整理, 实现自动化运行

由于代码转移到nodejs上之后使用fetch进行网络请求代码一直报错, 因此请求方式改为axios

这里多一嘴, 虽然nodejs模拟了浏览器的环境, 但是浏览器内置的一些API, nodejs上不一定也内置了, 要用的话的npm手动安装, 比如fetch是浏览器内置的API, 控台直接可以使用, 如果要在node上用的话可以执行以下指令安装:

1
npm install node-fetch

然后代码中引用:

1
const fetch = require('node-fetch');

同样axios也是:

1
npm install axios

代码引用:

1
const axios = require('axios');

关于fetch和axios两者的实现原理和区别, 有兴趣的可自行百度, 这里就不展开介绍了

回过头来, 整理后的代码如下:

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
const jwt = require('jsonwebtoken');
const axios = require('axios');


const uid = 'xxx';// 你的uid
let authorization = 'xxx'; // request headers中的authorization


let currentTime = Date.parse(new Date())
let gameId = "2021-11-30 10:36:12"
function getAccesssToken() {

const privatekey = "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n"
currentTime = Date.parse(new Date())
const payload = { "gameId": gameId, "time": currentTime }

const accessToken = jwt.sign(payload, privatekey, {
expiresIn: "240h",
algorithm: "ES256",
});
//console.log(accessToken)
return accessToken
}


//游戏开始
async function gameStart() {



//选择 click这个角色 钻石有加成
let params = { "roleId": 2 };

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/start?uid=' + uid + '&time=' + Date.parse(new Date()),
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': '',
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Mobile Safari/537.36'
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);
if (response.data.code == 4007) {
//游戏正在执行 则结束游戏
// gameOver()

} else if (response.data.code == 0) {
//成功开始
gameId = response.data.data.gameId


}
});


}
//执行游戏动作
async function gameRun() {


let gameid = getAccesssToken(); // request headers中的x-tt-gameid



let paramsList = [{ "command": [{ "times": 10, "command": ["L", "2", "R", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] },
{ "command": [{ "times": 10, "command": ["L", "D", "R", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] },
{ "command": [{ "times": 10, "command": ["U", "4", "L", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] }
];

let params = paramsList[Math.floor(Math.random() * paramsList.length)];

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=' + uid + '&time=' + currentTime,
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': gameid,
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);

if (response.data.code == 0) {
//成功

return response.data.data.gameDiamond

} else {
//4009 代码块不足
return 0
}
});

}
//结束游戏
async function gameOver() {



let params = { "isButton": 1 };

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/over?uid=' + uid + '&time=' + Date.parse(new Date()),
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);
if (response.data.code == 0) {
//成功

return response.data.data.todayDiamond>=response.data.data.todayLimitDiamond

} else {

return true
}
});


}
async function delay(time) {

var start = Number(new Date());
while (start + time * 200 > Number(new Date())) { }
}
async function start() {
//开始游戏-->游戏执行-->游戏结束
for (let index = 0; index < 100; index++) {
console.log("倒计时:" + index)
await gameStart()
let time = await gameRun()


//同步延迟结束游戏 根据获取的金币而定 金币越多 延迟时间越长
await delay(time)
let info=await gameOver()
//金币达到上限 停止游戏
if(info){
return
}
}


}

start()

将以上代码拷贝到你的本地文件中, 填入你的游戏authorization和uid, 然后每天用node运行一遍即可

到这里, 第一阶段的自动化已然实现

程序定时执行

每天手动跑代码太费事, 稍不留神就有可能某天给漏了, 尤其是周六日不开电脑的时候岂不是没法执行脚本了

这时 我们可以考虑放到服务器中每天定时执行, 365天风雨无阻, 推荐大家使用github或者travis ci免费服务部署

我这里手上刚好有一台阿里云的服务器, 因此直接用服务器跑, 这里我使用的是crontab定时程序, 关于crontab的用法, 可参见《Linux Crontab 命令安装和使用教程:在 VPS 上设置定时任务》

以ubuntu为例具体操作步骤如下:

  1. 将本地脚本拷贝至服务器

    1
    scp 脚本文件 root@服务器IP:/root

    注意:冒号前后不能有空格, 否则会提示目录不存在

  2. 登录服务器配置crontab任务

    执行crontab -e进入cron编辑界面, 将以下定时代码写入

    1
    0 8 * * *  /usr/bin/node /root/jwt_test >>/root//log/jwt_test_$(date +\%Y-\%m-\%d-\%H).log 2>&

    表示每天8点执行该脚本

  3. 安装nodejs及相关依赖

    如果服务器已经安装nodejs可忽略该步操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //更新源
    sudo apt-get update
    //安装nodejs
    sudo apt-get install -y nodejs
    //安装npm包管理器
    sudo apt-get install npm
    //安装脚本依赖
    npm install jsonwebtoken
    npm install axios
  4. 配置完毕

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

android中获取控件宽高的几种方式总结

发表于 2021-11-26

前言

我们知道, 在Activity的onCreate中通过width和height方法是通常是无法获取到控件的宽高的

因为控件还未测量完毕

如果想要获取到值, 那么有以下几种方式可选

第一种 获取前调用一次measure方法

示例如下:

1
2
3
4
5
6
7
int width = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
int height = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
view.measure(width, height);
view.getMeasuredWidth(); // 获取宽度
view.getMeasuredHeight(); // 获取高度

第二种 使用ViewTreeObserver. OnPreDrawListener进行绘制前事件监听

示例如下:

1
2
3
4
5
6
7
8
9
10
11
view.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {

@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
return true;
}
});

该方法会被调用多次,因此获取到视图的宽度和高度后要移除该监听事件

第三种 使用ViewTreeObserver. OnGlobalLayoutListener

在布局发生改变或者某个视图的可视状态发生改变时调用该事件

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
view.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {

@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= 16) {
view.getViewTreeObserver()
.removeOnGlobalLayoutListener(this);
}
else {
view.getViewTreeObserver()
.removeGlobalOnLayoutListener(this);
}
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}
});

该方法会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

第四种 使用View.OnLayoutChangeListener

在视图的 layout 改变时调用该事件, API>=11才能使用

示例如下:

1
2
3
4
5
6
7
8
9
10
11
view.addOnLayoutChangeListener(
new View.OnLayoutChangeListener() {

@Override
public void onLayoutChange(View v, int l, int t, int r, int b,
int oldL, int oldT, int oldR, int oldB) {
view.removeOnLayoutChangeListener(this);
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}
});

该方法同样会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。

第五种 View.post() 方法

方法会在 View 的 measure、layout 等事件完成后触发

示例如下:

1
2
3
4
5
6
7
8
view.post(new Runnable() {

@Override
public void run() {
view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}
});

该方法只会执行一次,且逻辑简单,建议使用

第六种 重写 View 的 onSizeChanged 方法

在视图的大小发生改变时调用该方法,会被多次调用,因此获取到宽度和高度后需要考虑禁用掉代码

示例如下:

1
2
3
4
5
6
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度

该实现方法需要继承 View,且多次被调用,不建议使用

第七种 重写 View 的 onLayout 方法

示例如下:

1
2
3
4
5
6
7
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);

view.getWidth(); // 获取宽度
view.getHeight(); // 获取高度
}

该实现方法需要继承 View,且多次被调用,不建议使用

实际场景

目前有个业务需求, 我需要在addView之后获取到被add子控件的宽高, 此时最好的方法就是使用View.post, 如下:

image-20211126123723329

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

android中获取控件坐标的几种api区分

发表于 2021-11-25

前言

我们经常会看到以下几个方法:

  • getGlobalVisibleRect()

  • getLocalVisibleRect()

  • getLocationOnScreen()

  • getLocationInWindow()

傻傻分不清? 今天我们简单介绍一下它们各自的作用

getGlobalVisibleRec

方法作用:

其一: 用于判断控件是否可见, 以屏幕为参考 如果可见返回true, 不可见返回false, 我们可以借助该方法检测控件是否被滑出屏幕外

其二: 用于获取控件可见区域的坐标, 这个坐标是相对于屏幕而言

在这里插入图片描述

坐标存放在了Rect对象中, 以参数的形式传入:

1
getGlobalVisibleRect(new Rect)

getLocalVisibleRect

这个方法和上面的getGlobalVisibleRec方法类似, 唯一区别是参考系不同, 这个方法参考系是控件本身

以下是这两个方法的坐标打印:

getRect

getLocationOnScreen

获取控件相对于屏幕而言的坐标位置, 与可不可见无关

getLocationInWindow

获取控件相对于父控件而言的坐标位置, 与可不可见无关

在这里插入图片描述

坐标补充

我们在进行自定义控件绘制的时候, 总是容易被一些参数给绕的头晕脑涨, 什么top right, left, bottom, 四条边与屏幕的距离等等, 想想就觉得费劲, 再加上Android中坐标系和我们数学中的坐标系不一致 使得我们在进行计算的时候难免有点吃不消

实际上对于一些规则的图形绘制, 比如矩形, 我们需要明白的一点, 那就是只需要两个点的坐标即可绘制出来, 也就是两个对角点

那我们在进行图形绘制的时候, 将计算范围缩小, 焦点集中在两个对角坐标位置计算上即可

这两个对角可以使左上角右下角, 也可以是左下角和右上角

这样一想, 那么所谓的left和top实际就是左上角的xy坐标, 而right和 bottom就是右下角的坐标

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

关于Android中adjustResize属性不生效的问题

发表于 2021-11-22

前言

写聊天布局的时候需要给Activity用上adjustResize属性来保证输入框不会被软键盘遮盖住

如果该属性不生效, 那么一般情况是没有给布局添加fitsSystemWindows属性, 那么给布局根控件添加该属性即可:

1
android:fitsSystemWindows="true"

此时adjustResize属性生效, 但是新问题又出现了, 布局上方出现了一块间隔区域

image-20211122135418218

解决方案

重写根控件的onApplyWindowInsets方法, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ChatLayout @JvmOverloads constructor(

context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

override fun onApplyWindowInsets(insets: WindowInsets): WindowInsets {
return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
insets.systemWindowInsetBottom
));
}
}

问题解决

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

关于继承和接口的形象理解

发表于 2021-11-19

前言

很多Java初学者对于继承和接口的作用区分总是很模糊, 如果不是因为回调需要接口, 估计能用继承实现的都用了继承

继承和接口有很多相似的地方, 比如抽象实现

但是它们的不同点呢 这个就得考虑到实际应用场景了, 脱离了业务研究 啥也不是

今天我们只说一个形象比喻

继承是先天 接口是后天

何为先天, 一生下来就有, 不要也得要, 说的高端一点这叫天赋

何为后天, 和父母无关 全靠自己

比如 父母有个好嗓子, 你继承了这个优点, 不用努力就比别人强

随着年龄的增长, 你发现干IT能促进植发行业的发展, 于是乎通过后天努力掌握了编程技能

那么

好嗓子–继承而来–继承

编程技能–后天获取–接口

似乎还是有点抽象 哈哈, 具体一点, 比如我们想让所有的子类都有相同的一个功能, 那我们可以使用继承

如果我们想让其中一部分子类有某个功能, 而剩下一些没有, 则可以使用接口, 实现差异化处理

也就是说接口的功能 可要可不要, 想要什么样的就接什么样的 比较自由 而继承的话全部子类必须都得有 没得选

后天可以弥补先天的不足, 接口可以弥补继承的不全面

不知道能不能理解

无妨 , 编程这东西 就是编程来的 慢慢就都明白了, 以上也只是个人的小小想法罢了

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

YouTube视频和字幕下载以及合并的工具推荐

发表于 2021-10-26

前言

最近有一个视频下载的需求, 需要将视频以及中英文字幕下载下来, 于是乎用到了一些工具, 有些使用体验还不错, 在此推荐给大家

视频下载

以下以YouTube视频网站为例:

YouTube视频或音频在线下载 :这个需要大爬梯

YouTube视频在线下载 :这个不需要大爬梯 而且支持手机端

YouTue视频下载软件 免费用户每天可以下载5个不带字幕视频 格式转换和视频与字幕合并功需要付费能 支持Windows和Mac平台

国外视频在线下载: 支持下载YouTube、TikTok、Facebook、Twitter、Instagram等视频

image-20240404184925016

如果你是Mac用户, 那么有一个网站推荐给大家:

image-20220805174904728

网站官网: 点击进入

字幕下载

YouTube字幕在线下载 支持多种语言和双语字幕

视频和字幕合并

这个就得使用软件了, 免费的有:

  • 格式工厂
  • 诸如pr、剪映、比剪等剪辑工具
  • 小丸工具箱 目前只支持Windows平台
  • Subler 目前只支持Mac平台
  • tipard_video_converter_ultimate 提供Mac下载 (访问密码:312306)
  • HandBrake 支持Windows和Mac 推荐使用

如果非要在线合并的话, 推荐使用B站的云剪辑

其界面长这样:

image-20211026171444006

如果你刚好是UP主, 需要合并完视频后直接投稿, 可以使用这种方式, 挺方便的

然而最后我选择了tipard_video_converter_ultimate这个软件处理字幕和视频的合并, 界面如下:

image-20211026170010642

image-20211026171217226

这个软件不仅可以添加字幕 同时也可以移除字幕或者音频

就是转换的速度不是很快, 导出的视频文件体积过大 两百多M的视频, 花了快十分钟 最后视频体积高达2个G , 如果对视频体积有要求话, 建议大家直接使用HandBrake, 输出的体积和原文件差不多, 甚至更小

值得欣慰的是字幕合成的质量不错:

image-20211026175323771

我还尝试了使用B站云端剪辑合成, 没有对比就没有伤害:

image-20211026175441786

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

解决Mac平台USB移动硬盘无法写入和删除数据的问题

发表于 2021-10-22

前言

使用Mac的小伙伴应该会碰到相关问题

我们在接入移动硬盘时只能读取数据却不能写入或者删除数据

这是移动硬盘格式不兼容导致的, 可以通过格式化硬盘的方式来实现Mac和Windows平台双端兼容, 但格式化意味着移动硬盘数据全部被清除

这肯定不是我们想要的

为了解决这个问题, 网上也出了相关的NTFS读写, 但是都是收费的

后来找到一款免费的NTFS移动硬盘读写工具 名为Omi NTFS, 体验还不错 这里推荐给大家使用

工具下载地址

点击下载

(访问密码:312306)

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

如何撤销上一次 Git add 的内容

发表于 2021-10-12

前言

有时候, 我们使用git add .不小心误添加了某些个文件到本地暂存库中, 如果我们需要撤销该add记录, 可以通过以下方式

撤销指令

  1. 撤销上次add的指定文件

    1
    git reset HEAD <路径/文件名>
  2. 撤销上次add的所有文件

    1
    git reset HEAD

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Kindle传书的几种便捷方式

发表于 2021-09-30

前言

给使用kindle的小伙伴介绍几种好用的传书方法

第一种 使用网页传书

以轻松传这个网站为例:

官方网址: https://easychuan.cn/

image-20210930094805282

我们只需要将文件上传, 得到一个取件码, 然后在kindle中打开体验版浏览器, 同样输入https://easychuan.cn/

image-20210930095755229

填入相应的取件码就可以直接下载了

类似这样的网站除了轻松传之外, 还有快牙:

image-20211223145143645

点击进入快牙传输网站

还有就是Feem的WebShare功能也可以传输文件, 直接通过一个ip和端口号进行访问下载, 不过仅限于局域网内:

image-20211223145420896

点击进入Feem官网

第二种 使用邮箱传书

kindle亚马逊账号登录后会自动分配一个邮箱账号, 这个账号可以在kindle设置中进行查看获取

我们只需要将文件发送到这个邮箱账号上即可

注意使用的是邮件附件 而不是超大附件

第三种 使用Kindle App

Kindle App 是亚马逊开发出的阅读 App,可用于同步我们在亚马逊 Kindle 书城购买中购买的书到各个客户端(Windows、OSX、Android 以及 iOS)

可以看成是App版的Kindle, 除了可以用来看书外, 还可以用来传书

只需登录kindle账号然后直接上传发送即可

image-20210930101514103

第四种 USB传书

简单粗暴, 快捷高效, 这个不用多说, 一根数据线的事

总结

  • EasyChuan网页: 支持电脑和手机, 无需登录, 如果文件不多的话推荐使用该方法
  • USB传书: 文件大小无限制, 如果批量传书推荐该方法

image-20210930101639592

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…212223…51

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

505 日志
145 标签
RSS
© 2026 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%