日期: 2021 年 5 月 13 日

import 到底在干啥

学了半天,import 到底在干啥?

 

Python凭什么就那么好用呢?

毫无疑问,大量现成又好用的内置/第三方库功不可没。

那我们是怎么使用它们的呢?

噢,对了~是用的import xxx这个语句。

之所以会有此一问,也是之前有一次使用PyCharm进行开发时(又)踩了个坑……

废话少说,先讲问题
像下面这样一个项目结构:

Projetc_example
|– A
|– alpha.py
|– beta.py
|– B
|– theta.py
|– main
|– main.py
假设要在main.py中导入theta.py:

# main/main.py
from B import theta

显然会导致我们所不希望的问题,即Python不知道要到哪里去找这个名为B的模块(包是一种特殊的模块):

Traceback (most recent call last):
File “main/main.py”, line 1, in <module>
from B import theta
ModuleNotFoundError: No module named ‘B’
可是这就奇了怪了,为啥同样的代码,在PyCharm里运行就是好的了呢?

import的查找路径
于是我们不辞艰辛,上下求索,原来在Python中,import语句实际上封装了一系列过程。

1. 查找是否已导入同名模块
首先,Python会按照import xxx中指定的包名,到sys.modules中查找当前环境中是否已经存在相应的包——不要奇怪为什么都没有导入sys这个模块就有sys.modules了。

sys是Python内置模块,也就是亲儿子,导入只是意思一下,让我们这样的外人在导入的环境中也可以使用相关接口而已,实际上相应的数据对Python而言从始至终都是透明的。

我们可以导入sys查看一下这个对象的具体内容(节省篇幅,做省略处理):

>>> import sys
>>> sys.modules
{‘sys’: <module ‘sys’ (built-in)>, ‘builtins’: <module ‘builtins’ (built-in)>, …’re’: <module ‘re’ from ‘E:\\Anaconda\\Anaconda\\lib\\re.py’>, …}
这些就都是Python一开始就已经加载好的模块,也就是安装好Python之后,只要一运行环境中就已经就绪的模块——只是作为外人的我们还不能直接拿过来用,得跟Python报备一声:“欸,我要拿您儿子来用了嗨~”

很容易可以发现,sys.modules中列出来的已加载模块中存在明显的不同,前面的很多模块显得很干净,而后面的很多模块都带有from yyy’的字样,并且这个yyy看起来还像是一个路径。

这就关系到我们接下来要讲的步骤了。

2. 在特定路径下查找对应模块
前面我们讲到了,当我们导入某个模块时,Python先会去查询sys.modules,看其中是否存在同名模块,查到了那当然皆大欢喜,Python直接把这个模块给我们用就好了,毕竟儿子那么多,借出去赚点外快也是好事儿不是?

可问题在于:那要是没找到呢?

这显然是一个很现实的问题。毕竟资源是有限的,Python不可能把你可能用到的所有模块全都一股脑给加载起来,否则这样男上加男加男加男……谁也顶不住啊不是(大雾

于是乎就有人给Python出了个主意:那你等到要用的时候,再去找他说他是你儿子呗

Python:妙哇~

有了这个思路,Python就指定了几家特定的酒楼,说:“凡是去消费的各位,都可以给我当儿子。”

就这样,一些本来不是Python亲儿子的人,出于各种原因聚集到了这几家酒楼,以雇佣兵的身份随时准备临时称为Python的儿子。

这可就比周文王开局就收100个义子优雅多了,养家糊口的压力也就没那么大了(Python:什么?我的亲儿子都不止100个?你说什么?听不见啊——

回到正经的画风来——

实际上,在Python中,sys.path维护的就是这样一个py交易的结果~~(诶?好像莫名发现了什么),其中保存的内容就是这几家“指定酒楼”,也就是当Python遇到不认识的儿子~~模块时,就会去实地查找的路径。

我们也可以打印出来看看具体内容:

>>> sys.path
[”, ‘E:\\Anaconda\\Anaconda\\python37.zip’, ‘E:\\Anaconda\\Anaconda\\DLLs’, ‘E:\\Anaconda\\Anaconda\\lib’, ‘E:\\Anaconda\\Anaconda’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin’]
大体上就是安装环境时配置的一些包所在路径,其中*个元素代表当前所执行脚本所在的路径。

也正是因此,我们可以在同一个目录下,大大方方地调用其他模块。

3. 将模块与名字绑定
找到相应的非亲生模块还没完,加载了包还得为它分配一个指定的名字,我们才能在脚本中使用这个模块。

当然多数时候我们感知不到这个过程,因为我们就是一个import走天下:

import sys
import os
import requests
这个时候我们指定的模块名,实际上也是指定的稍后用来调用相应模块的对象名称。

换个更明显的:

import requests as req
如果这个时候只使用了第二种方式来导入requests这个模块,那么很显然在之后的程序流程中,我们都不能使用requests这个名字来调用它而应当使用req。

这就是Python导入过程中的名称绑定,本质上与正常的赋值没有太大区别,加载好了一个对象之后,然后为这个对象赋一个指定的变量名。

当然即使是已经加载好的模块,我们也可以利用这个名称绑定的机制为它们取别名,比如:

>>> import sys
>>> import sys as sy
>>> sys.path
[”, ‘E:\\Anaconda\\Anaconda\\python37.zip’, ‘E:\\Anaconda\\Anaconda\\DLLs’, ‘E:\\Anaconda\\Anaconda\\lib’, ‘E:\\Anaconda\\Anaconda’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin’]
>>> sy.path
[”, ‘E:\\Anaconda\\Anaconda\\python37.zip’, ‘E:\\Anaconda\\Anaconda\\DLLs’, ‘E:\\Anaconda\\Anaconda\\lib’, ‘E:\\Anaconda\\Anaconda’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\win32\\lib’, ‘E:\\Anaconda\\Anaconda\\lib\\site-packages\\Pythonwin’]
>>> sys == sy
True
问题解决
好了,上面就是对Python导入机制的大致介绍,但是说了半天,我们的问题还没有解决:在项目中如何简洁地跨模块导入其他模块?

在使用PyCharm的时候倒是一切顺遂,因为PyCharm会自动将项目的根目录加入到导入的搜索路径,也就是说像下面这样的项目结构,在任意模块中都可以很自然地通过import A导入模块A,用import B导入模块B。

Projetc_example
|– A
|– alpha.py
|– beta.py
|– B
|– theta.py
|– main
|– main.py
但是在非IDE环境中呢?或者说就是原生的Python环境中呢?

很自然地我们就会想到:那就手动把项目根目录加入到sys.path中去嘛。说起来也跟PyCharm做的事没差呀

可以,贫道看你很有悟性,不如跟我去学修仙吧

所以我们就通过sys和os两个模块七搞八搞(这两个模块以前有过介绍,不再赘述)——

噔噔噔噔——好使了

# Peoject_example/A/alpha.py
print(“name: ” + __name__)
print(“file: ” + __file__)

def al():
print(“Importing alpha succeeded.”)
main.py中则加入一个逻辑,在sys.path中增加一个项目根目录:

import os
import sys

sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

import A.alpha

A.alpha.al()

# name: A.alpha
# file: *\Project_example\A\alpha.py
# Importing alpha succeeded.
大功告成,风紧扯呼~

 

自学Python:掌握Socks5代理协议意义何在?

SOCKS5的概念?
一种使用TCP/IP协议进行通前端和服务器之间发挥中介作用,使内部网络的前端机器能够访问互联网的服务器,使通信更加安全。

通过将前端请求转发到真正的目标服务器,SOCKS5服务器模拟了前端行为。

在这种情况下,前端和SOCKS5通过TCP/IP协议进行通信,前端向SOCKS5服务器发送请求,而SOCKS5服务器向实际的服务器发送请求。

SOCKS5服务器在向真实服务器发送通信请求时,对请求包本身没有任何变化。SOCKS5服务器接到真正的服务器反应后,也直接转发到前端。

所以SOCKS5协议是代理协议,可以适用于各种基于TCP/IP的应用层协议,几乎是全能的。虽然不能了解自己转发的数据内部结构,但可以实现对通信包的忠实转发,完成协议需要完成的功能。

与SOCKS5协议不同,HTTP代理商通过HTTP协议实现,HTTP代理服务器软件了解通信包的内部结构,在转发过程中还需要修改和转换通信包的程序。与HTTP代理协议不同,SOCKS5实际上是一个传输层代理协议。

假如每一个特定的应用层协议必须设计出相应的代理协议表示,那么可以想象,一个特定的代理服务器不可能支持这么多新协议。可以说,SOCKS5的出现缓解了不同协议对代理协议的特殊需求。

Python 教你如何给图像分类

Python 教你如何给图像分类

 

在日常生活中总是有给图像分类的场景,比如垃圾分类、不同场景的图像分类等;今天的文章主要是基于图像识别场景进行模型构建。图像识别是通过 Python深度学习来进行模型训练,再使用模型对上传的电子表单进行自动审核与比对后反馈相应的结果。主要是利用 Python Torchvision 来构造模型,Torchvision 服务于Pytorch 深度学习框架,主要是用来生成图片、视频数据集以及训练模型。

模型构建
构建模型为了直观,需要使用 Jupyter notebook 进行模型的构建,Jupyter notebook 的安装及使用详见公众号历史文章 一文吃透 Jupyter Notebook,进入 JupyterNotebook 页面后即可进行编辑。详细页面如下:

导入所需包
图像识别需要用到深度学习相关模块,所以需要导入相应的包,具体导入的包如下:

%reload_ext autoreload
%autoreload 2

import torch
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder
from torchvision import transforms as tfs
from torchvision import models
from torch import nn

import matplotlib.pyplot as plt
%matplotlib inline

import os
os.environ[“KMP_DUPLICATE_LIB_OK”]=”TRUE”

是否使用 GPU
模型的训练主要方式是基于 GPU 或者 CPU 训练,在没有 GPU 的条件下就在 CPU 下进行训练,模型的训练需要花费一定的时间,训练时长根据训练集的数据和硬件性能而定,训练结果精确性根据数据的多少和准确性而且,深度学习需要大量的素材才能判断出精确的结果,所以需要申明使用 CPU 进行训练:

# 是否使用GPU
use_gpu = False
数据增强
将拿到的数据进行训练集的数据预处理并设置训练分层数,再将拿到的图片进行水平翻转后对图片进行剪裁, 剪裁后将图片进行随机翻转,增强随机对比度以及图片颜色变化

# 数据增强
train_transform = tfs.Compose([
# 训练集的数据预处理
tfs.Resize([224, 224]),
tfs.RandomHorizontalFlip(),
tfs.RandomCrop(128),
tfs.ToTensor(),
tfs.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
])

test_transform = tfs.Compose([
tfs.Resize([224,224]),
#     tfs.RandomCrop(128),
tfs.ToTensor(),
tfs.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
])

# 每一个batch的数据集数目
batch_size = 10

数据集和验证集准备
模型训练需要准备数据集和验证集,只有足够的照片才能得到更精准的答案。训练集和验证集部分代码如下:

# 构建训练集和验证集
#
train_set = ImageFolder(‘./dataset1/train’, train_transform)
train_data = DataLoader(train_set, batch_size, shuffle=True, num_workers=0)

valid_set = ImageFolder(‘./dataset1/valid’, test_transform)
valid_data = DataLoader(valid_set, 2*batch_size, shuffle=False, num_workers=0)

train_set.class_to_idx

len(valid_data)

# 数据集准备
try:
if iter(train_data).next()[0].shape[0] == batch_size and \
iter(valid_data).next()[0].shape[0] == 2*batch_size:
print(‘Dataset is ready!’)
else:
print(‘Not success, maybe the batch size is wrong’)
except:
print(‘not success, image transform is wrong!’)
模型构建并准备模型
# 构建模型
def get_model():
model = models.resnet50(pretrained=True)
model.fc = nn.Linear(2048, 3)
return model

try:
model = get_model()
with torch.no_grad():
scorce = model(iter(train_data).next()[0])
print(scorce.shape[0], scorce.shape[1])
if scorce.shape[0] == batch_size and scorce.shape[1] == 3:
print(‘Model is ready!’)
else:
print(‘Model is failed!’)
except:
print(‘model is wrong’)

if use_gpu:
model = model.cuda()
构建模型优化器
# 构建loss函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr = 1e-4)

# 训练的epoches数目
max_epoch = 20

模型训练和训练结果可视化
数据集和训练集准备好后进行模型训练和训练结果可视化,部分代码如下:

def train(model, train_data, valid_data, max_epoch, criterion, optimizer):
freq_print = int(len(train_data) / 3)

metric_log = dict()
metric_log[‘train_loss’] = list()
metric_log[‘train_acc’] = list()
if valid_data is not None:
metric_log[‘valid_loss’] = list()
metric_log[‘valid_acc’] = list()

for e in range(max_epoch):
model.train()
running_loss = 0
running_acc = 0

for i, data in enumerate(train_data, 1):
img, label = data
if use_gpu:
img = img.cuda()
label = label.cuda()

# forward前向传播
out = model(img)

# 计算误差
loss = criterion(out, label.long())

# 反向传播,更新参数
optimizer.zero_grad()
loss.backward()
optimizer.step()

# 计算准确率
_, pred = out.max(1)
num_correct = (pred == label.long()).sum().item()
acc = num_correct/img.shape[0]

running_loss += loss.item()
running_acc +=acc

if i % freq_print == 0:
print(‘[{}]/[{}], train loss: {:.3f}, train acc: {:.3f}’ \
.format(i, len(train_data), running_loss / i, running_acc / i))

metric_log[‘train_loss’].append(running_loss / len(train_data))
metric_log[‘train_acc’].append(running_acc / len(train_data))

if valid_data is not None:
model.eval()
running_loss = 0
running_acc = 0
for data in valid_data:
img, label = data
if use_gpu:
img = img.cuda()
label = label.cuda()

# forward前向传播
out = model(img)

# 计算误差
loss = criterion(out, label.long())

# 计算准确度
_, pred = out.max(1)
num_correct = (pred==label.long()).sum().item()
acc = num_correct/img.shape[0]

running_loss += loss.item()
running_acc += acc

metric_log[‘valid_loss’].append(running_loss/len(valid_data))
metric_log[‘valid_acc’].append(running_acc/len(valid_data))
print_str = ‘epoch: {}, train loss: {:.3f}, train acc: {:.3f}, \
valid loss: {:.3f}, valid accuracy: {:.3f}’.format(
e+1, metric_log[‘train_loss’][-1], metric_log[‘train_acc’][-1],
metric_log[‘valid_loss’][-1], metric_log[‘valid_acc’][-1])
else:
print_str = ‘epoch: {}, train loss: {:.3f}, train acc: {:.3f}’.format(
e+1,
metric_log[‘train_loss’][-1],
metric_log[‘train_acc’][-1])
print(print_str)

# 可视化
nrows = 1
ncols = 2
figsize= (10, 5)
_, figs = plt.subplots(nrows, ncols, figsize=figsize)
if valid_data is not None:
figs[0].plot(metric_log[‘train_loss’], label=’train loss’)
figs[0].plot(metric_log[‘valid_loss’], label=’valid loss’)
figs[0].axes.set_xlabel(‘loss’)
figs[0].legend(loc=’best’)
figs[1].plot(metric_log[‘train_acc’], label=’train acc’)
figs[1].plot(metric_log[‘valid_acc’], label=’valid acc’)
figs[1].axes.set_xlabel(‘acc’)
figs[1].legend(loc=’best’)
else:
figs[0].plot(metric_log[‘train_loss’], label=’train loss’)
figs[0].axes.set_xlabel(‘loss’)
figs[0].legend(loc=’best’)
figs[1].plot(metric_log[‘train_acc’], label=’train acc’)
figs[1].axes.set_xlabel(‘acc’)
figs[1].legend(loc=’best’)
调参进行模型训练
# 用作调参
train(model, train_data, valid_data, max_epoch, criterion, optimizer)
保存模型
# 保存模型
torch.save(model.state_dict(), ‘./model/save_model2.pth’)

根据爬虫采集的具体应用挑选代理IP地址类型

众所周知,网络爬虫程序的工作是通过代理ip来完成的,但要使其正常运行,就必须要减少代理ip。

对于从事互联网大数据工作的人来说,网页爬虫程序并不陌生。网络大数据时代,数据信息尤为重要,互联网业务范围广泛,各种业务所需的数据和规模各不相同。当大量收集数据信息时,应该使用哪个代理ip?

在Web爬虫中大规模收集数据信息必须使用高级代理中的高质量短效代理ip,但如果要大量使用,则需要大量的测试。

优质ip的运营优势是稳定安全绿色,上线速度*快,效率高,ip的资源也非常丰富,专注于完全隐藏真实的ip。

你可以在HTTP代理ip官网上看到各种类型的代理ip,根据自己实际使用情况来选择。

十一回老家买不到票,*后被黄牛套路

十一回老家买不到票,*后被黄牛套路

 

今年十一国庆中秋节连续放假8天,对于我们上班族来说真是太爽了,但对于我这种没车的北漂来说,节前买回老家车票却是一件痛苦的事,尤其今年受疫情影响,再加上十一和中秋是同一天,这回家的车票就更难买了……

买票
其实很早开始就在抢票软件抢,后来加钱光速抢,还是买不到票。后来就想坐大巴回去也行,就去北京长途客运官网查询大巴车票,刚开始查询的时候还挺开心,虽然显示余票少,但还是有票的。只不过买票还需要实名注册,我就注册了一下,然后坑爹的事情来了,选好乘车人点击确认买票时,就提示系统内部错误,请到车站售票处买。然后我以为只是短时的问题,估计一会就好了,结果等下午再试还是那样,然后再等再试依然报错,那一刻我突然发现12306还是很强大的,真是没有比较就不知道没有差的,只有更差的。

看到有票却买不了,那感觉真是五味杂陈,*后没办法只能放弃线上买,准备下班去汽车站售票处买。我以为网上都有余票,说明汽车站的人应该不会太多,结果我还是太天真了。从下地铁开始就感觉不妙,因为下车的人真的很多,我都不用看出站指示牌,跟着人流就能走到汽车站。进售票厅后简直惊呆了,十多个售票厅前面黑压压一片站满了人,之所以说是一片,是因为真的没有排队,不像买火车票时人再多也都是排队。当时只想着怎么回家,忘了拍照片,就不上图了。看到这情景我果断放弃了,如果在这等买上票不知道得到啥时候了。

没办法我就继续抢着火车票,把能选上的车都选上,随便啥车有没有座都无所谓,只要能回家就行。对了在网上买不到大巴票时,也想过用滴滴跨城顺风车,结果直接提示超过200公里不发单,不过我并没有放弃,既然超过200公里不行,那我就选个以内的城市先出北京再说,然后就选了从北京到保定火车站,结果发单两小时了也没人接。而且以前关注很久的几个拼车群,都是人找车的多,车找人的很少,可能跟疫情有关吧,大家宁愿一个人开车回去也不愿带陌生人,避免增加不必要的风险。

套路
好了,再回到长途汽车站,我一边用手机抢票,一边往地铁口走,就听到黄牛在那喊,“到XX的车,马上发车了啊。” 其实出地铁时就看到那人在喊了,只是没答理他,当时很不屑,心想我才不会买黄牛的票。现在准备打脸了,就去问了一声“多少钱?” 他回了句“一百!” 我心想比售票处卖的还便宜,现在的黄牛都这么良心了吗?不会是车有问题吧。

然后我又问了一句”你那什么车啊?“,他说就是55座的旅游大巴,说着就拉我到了旁边,指着不远处站着的一群人说,看那些都是等着坐车的,车一到马上就发车,刚已经走了一批了,怎么样走吧。顺着他说的地方确实看到站着几十人,男女老少,带着各种行李的都有,心想这么多人不至于都是托吧?然后就跟他一起走了过去。

走过去看到这群人边上,有个中年女人坐在小板凳上,手上拿着手机连着个充电宝。另一个手上有一沓”车票“,用引号是因为这车票像是很多年前车站用的那种。然后看有人正在给她用手机转账150,转完到账她就撕下一张票给那人,看来这”票“只是给人一种心理安慰而已。不一会又一个黄牛带了个人过来要买票,看那人也是转了150拿了票。带我来的这个黄牛对那女的说,给我也出张票。遇到这种情况我一般会有很强的防范心理,想等下看看再说,结果那人两句话我就乖乖就范了(真是啪啪打脸啊)。他说:”你看别人都是买的150,你给她一百就行,赶紧吧马上就发车了,再不买就得等下趟了。“ 然后我就想反正也就一百,坑就坑吧,不管怎样能回家就行。掏出手机就扫了支付宝,给那女的转了一百,她看了下到几,也没说什么就撕了张票给我,拿到票我就做好了被坑的准备。

车票
没过多久,听到有人喊,车来了大家出发了,再一细听,车是来了,不过是去保定的车,结果这群人里有一多半人都走了。心想靠,原来不全是去XX的人,果然被坑了。不过好在剩下的一批人中大部分都说自己是去XX的,而且是真有车来能送我们回家,总算是有些心理安慰,那就继续等吧。等着看着,过一会就有黄牛从各方向带人过来买票,而且来了都会说,你看这群人都是等着坐着的,我还能骗你吗?其实大多数人都有从众心理,一看这么多人跟自己一样,很容易就放下戒心选择买票。而且每次都说马上发车了,赶紧买吧,呵呵,都是套路,我都在这站半小时了。

正等着,有人跑过来对卖票那女的说XX(没听清)来了,然后那女的对着人群喊,”大家都散开,别全聚在一起“。说完她就往一块人少的地方走,结果有些人不明所以,也跟着她走了过去,再然后其他人看到有人跟了过去,也一起都跟了过去。结果她走着走着,回头发现全都跟了过来,就喊:”别都跟着我,大家散开别聚一起“,然后她又朝另一块空地走了过去,这次没人跟过去了,不过看出大家都很疑惑,不过还是各自散开。

站口
我心想,难道是警察来了,这女的不会是要卷款跑路吧?我就死死盯着那女的,她又找了个地方坐下,看着手机不一会又有黄牛带人过来买票,这些人不知道之前发生的事,所以买完票还是在那女的周围站着。看这情况应该没啥问题,悬着的心总算放下,然后就听旁边几个买了票的乘客在那聊天,听出他们应该不认识。其中一个人说,”不知道啥时候才能上车,这都等一小时了。” 然后另一个人说:“等着吧,什么时候人数够了,就能发车了!”。然后还听他们说这些黄牛带人买票,为啥价格不一样。总结两个字就是“杀熟”,通过熟人介绍过来的基本都是150,还有通过什么网上渠道预约来的也是150,通过地铁站带的人基本都是100,另外还有120的,多的也有170的,反正就是你来这的目的就是为了坐他这车的,那票价就贵,果然又是套路。

就这样一个多小时过去了,我突然看到一个黄牛在附近不远打电话,可能是周围人多比较吵,所以他打电话说话声音很大。他说:”XX帮我叫个车过来,我这差不多了……对,就在地铁站B口那等着……很快,我们这就叫他们过去……“ 果然车什么时候到,还得看人什么时候能凑够。打完电话他就喊”XX的车到了,我们出发了“,然后他转身就往地铁站里走,对,你没看错就是往地铁站里走,难道还要这群人坐地铁不成?不至于吧……接下来就是一群人跟着他往地铁站走。坐过地铁的应该知道,进地铁站都得安检,只看到突然一大群人来到安检那,安检员都慌了,赶紧喊大家排队,一个一个过安检。然后等着所有人都过了安检,又领着人从另一个出口出去了,我当时就想,为啥不直接从外面过去,难道是为了我们乘车安全,故意带我们走地铁站免费安检下?

回家
从地铁站出来后走到路边,等了一会车就来了,还行,这大巴还可以,京牌的应该没啥问题吧?上了大巴就安心多了,终于可以回家了!不过发车没多久,刚出四环就开始堵,在一个高架桥上堵了将近一小时,然后又过了一个多小时总算出了北京,果然放假了车就是多。不过再堵也没影响心情,因为都是预期内的,这时候不堵才不正常。

再后来就比较顺了,虽然有点小堵,基本都是因为路上有很多小事故导致,不过总体来说比预期的好些。即使这样还是到第二天凌晨才出高速,路上走了6个多小时。本来以为快到家了不会有啥坑了吧,结果出高速司机就让下车了,说不去火车站了,自己打车去吧。这要是正规车肯定不敢这样,车上很多人不满,不过又没办法,本来坐的就是黑车,又没地方投诉。不过总算离家很近,所以我也就无所谓了,赶紧打车回家。

Python 世界的黑客帝国

Python 世界的黑客帝国

 

相比于子弹时间和火爆场景,我更喜欢《黑客帝国》故事背景的假设 —— 人们熟悉的世界是虚构的,是机器给人大脑输入的幻象,而幻象是不完美的,存在一些不符合自然规律的地方,这些地方或多或少的展示了幻象世界的破绽和真实世界的样子,如果你看过《黑客帝国》动画版《超越*限》和《世界纪录》,将会有更深刻的感受

我们熟悉的、赖以生存的 Python 世界,其实也是个虚拟的,这个虚拟世界展示给我们无比绚烂的场景和功能的同时,也存在一些超乎常理和认知的地方,今天就带你一起探寻一些那些超自然点,以及它背后的真实世界

神奇的海象操作符
海象操作符 := 是 Python3.8 引入的一个新特性,意为节省一次临时变量的赋值操作,例如:

a = [1,2,3,4,5]
n = len(a)
if n > 4:
print(n)
意思是,如果列表 a 的长度大于 4,则打印 a 的长度,为了避免对列表长度的两次求解,利用变量 n 存储 a 的长度,合情合理

如果用 海象操作符(:=),会是这样:

a = [1,2,3,4,5]
if n := len(n) > 4:
print(n)
可以看到,省去了临时变量 n 的定义,通过海象操作符,一举两得

不得不说,Python 为能让我们提高效率,真是挖空心思,刚刚发布的 正式版 Python3.9,也是为提升效率做了多处改善

海象的表演
不过,看下面的代码

>>> a = “wtf_walrus”
>>> a
‘wtf_walrus’

>>> a := “wtf_walrus”  # 报错!
File “<stdin>”, line 1
a := ‘wtf_walrus’
^
SyntaxError: invalid syntax

>>> (a := “wtf_walrus”) # 奇迹发生,竟然通过了!
‘wtf_walrus’
>>> a
‘wtf_walrus’
再来一段

>>> a = 6, 9  # 元组赋值
>>> a  # 结果正常
(6, 9)

>>> (a := 6, 9)  # 海象赋值,表达式结果正常
(6, 9)
>>> a  # 临时变量竟然不同
6

>>> a, b = 6, 9 # 解包赋值
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oh no!
File “<stdin>”, line 1
(a, b = 6, 9)
^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # 这里竟然打印出三员元组!
(6, 16, 19)

>>> a # 问题是 a 竟然没变
6

>>> b
16
解密海象
非括号表达式的海象赋值操作海象操作符(:=)适用于一个表达式内部的作用域,没有括号,相当于全局作业域,是会受到编译器限制的

括号里的赋值操作相应的,赋值操作符(=)不能放在括号里,因为它需要在全局作用域中执行,而非在一个表达式内

海象操作符的本质海象操作符的语法形式为 Name := expr,Name 为正常的标识符,expr 为正常的表达式,因此可迭代的 装包 和 解包 表现的结果会和期望不同

(a := 6, 9) 实际上会被解析为 ((a := 6), 9),*终,a 的值为 6,验证一下:

>>> (a := 6, 9) == ((a := 6), 9)
True
>>> x = (a := 696, 9)
>>> x
(696, 9)
>>> x[0] is a # 引用了相同的值
True
同样的,(a, b := 16, 19) 相当于 (a, (b := 16), 19,原来如此

不安分的字符串
先建立一个感知认识

>>> a = “some_string”
>>> id(a)
140420665652016
>>> id(“some” + “_” + “string”) # 不同方式创建的字符串实质是一样的.
140420665652016
奇特的事情即将发生

>>> a = “wtf”
>>> b = “wtf”
>>> a is b
True

>>> a = “wtf!”
>>> b = “wtf!”
>>> a is b  # 什么鬼!
False
如果将这段代码写入脚本文件,用 Python 运行,结果却是对的:

a = “wtf!”
b = “wtf!”
print(a is b)  # 将打印出 True
还有更神奇的,在 Python3.7 之前的版本中,会有下面的现象:

>>> ‘a’ * 20 is ‘aaaaaaaaaaaaaaaaaaaa’
True
>>> ‘a’ * 21 is ‘aaaaaaaaaaaaaaaaaaaaa’
False
20 个字符 a 组合起来等于 20个 a 的字符串,而 21 个就不相等

揭秘字符串
计算机世界里,任何奇特的现象都有其必然原因

这些字符串行为,是由于 Cpython 在编译优化时,某些情况下会对不可变的对象做存储,新建时之间建立引用,而不是创建新的,这种技术被称作 字符串驻留(string interning),这样做可以节省内存和提高效率

上面代码中,字符串被隐式驻留了,什么情况下才会被驻留呢?

所有长度为 0 和 1 的字符串都会被驻留

字符串在编译时被驻留,运算中不会(”wtf” 会驻留,””.join(“w”, “t”, “f”) 则不会)

只有包含了字母、数值和下划线的字符串才会被驻留,这就是为什么 “wtf!” 不会被驻留的原因(其中还有字符 !)

如果 a 和 b 的赋值 “wtf!” 语句在同一行,Python 解释器会创建一个对象,然后让两个变量指向这个对象。如果在不同行,解释器就不知道已经有了 “wtf!” 对象,所以会创建新的(原因是 “wtf!” 不会被驻留)

像 IPython 这样的交互环境中,语句是单行执行的,而脚本文件中,代码是被同时编译的,具有相同的编译环境,所以就能理解,代码文件中不同行的不被驻留字符串引用同一个对象的现象

常量折叠(constant folding)是 Python 的一个优化技术:窥孔优化(peephole optimization),如 a = “a”*20,这样的语句会在编译时展开,以减少运行时的运行消耗,为了不至于让 pyc 文件过大,将展开字符限制在 20 个以内,不然想想这个 “a”*10**100 语句将产生多大的 pyc 文件

注意 Python3.7 以后的版本中 常量折叠 的问题得到了改善,不过还不清楚具体原因(矩阵变的更复杂了)

小心链式操作
来一段骚操作

>>> (False == False) in [False] # 合乎常理
False
>>> False == (False in [False]) # 也没问题
False
>>> False == False in [False] # 现在感觉如何?
True

>>> True is False == False
False
>>> False is False is False
True

>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False
不知到你看到这段代码的感受,反正我看*次到时,怀疑我学的 Python 是假冒的~

到底发生了什么
按照 Python 官方文档,表达式章节,值比较小节的描述(https://docs.python.org/2/reference/expressions.html#not-in):

通常情况下,如果 a、b、c、…、y、z 是表达式,op1、op2、…、opN 是比较运算符,那么 a op1 b op2 c … y opN z 等价于 a op1 b and b op2 c and … y opN z,除了每个表达式只被计算一次的特性

基于以上认知,我们重新审视一下上面看起来让人迷惑的语句:

False is False is False 等价于 (False is False) and (False is False)

True is False == False 等价于 True is False and False == False,现在可以看出,*部分 True is False 的求值为 False, 所以整个表达式的值为 False

1 > 0 < 1 等价于 1 > 0 and 0 < 1,所以表达式求值为 True

表达式 (1 > 0) < 1 等价于 True < 1,另外 int(True) 的值为 1,且 True + 1 的值为 2,那么 1 < 1 就是 False 了

到底 is(是) 也不 is(是)
我直接被下面的代码惊到了

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False
再来一个

>>> a = []
>>> b = []
>>> a is b
False

>>> a = tuple()
>>> b = tuple()
>>> a is b
True
同样是数字,同样是对象,待遇咋就不一样尼……

我们逐一理解下

is 和 == 的区别
is 操作符用于检查两个变量引用的是否同一个对象实例

== 操作符用于检查两个变量引用对象的值是否相等

所以 is 用于引用比较,== 用于值比较,下面的代码能更清楚的说明这一点:

>>> class A: pass
>>> A() is A()  # 由于两个对象实例在不同的内存空间里,所以表达式值为 False
False
256 是既存对象,而 257 不是
这个小标题让人很无语,还确实对象和对象不一样

在 Python 里 从 -5 到 256 范围的数值,是预先初始化好的,如果值为这个范围内的数字,会直接建立引用,否则就会创建

这就解释了为啥 同样的 257,内存对象不同的现象了

为什么要这样做?官方的解释为,这个范围的数值比较常用(https://docs.python.org/3/c-api/long.html)

不可变的空元组
和 -5 到 256 数值预先创建一样,对于不可变的对象,Python 解释器也做了预先创建,例如 对空的 Tuple 对象

这就能解释为什么 空列表对象之间引用不同,而空元组之间的引用确实相同的现象了

被吞噬的 Javascript
先看看过程

some_dict = {}
some_dict[5.5] = “Ruby”
some_dict[5.0] = “JavaScript”
some_dict[5] = “Python”

print(some_dict[5.5])  # Ruby
print(some_dict[5.0])  # Python Javascript 去哪了?
背后的原因
Python 字典对象的索引,是通过键值是否相等和比较键的哈希值来进行查找的

具有相同值的不可变对象的哈希值是相同的

>>> 5 == 5.0
True
>>> hash(5) == hash(5.0)
True
于是我们就能理解,当执行 some_dict[5] = “Python” 时,会覆盖掉前面定义的 some_dict[5.0] = “Javascript”,因为 5 和 5.0 具有相同的哈希值

需要注意的是:有可能不同的值具有相同的哈希值,这种现象被称作 哈希冲突

关于字典对象使用哈希值作为索引运算的更深层次的原因,有兴趣的同学可以参考 StackOverflow 上的回答,解释的很精彩,网址是:https://stackoverflow.com/questions/32209155/why-can-a-floating-point-dictionary-key-overwrite-an-integer-key-with-the-same-v/32211042

总结
限于篇幅(精力)原因,今天就介绍这几个 Python 宇宙中的异常现象,更多的异常现象,收录在 satwikkansal 的 wtfpython 中(https://github.com/satwikkansal/wtfpython)。

任何华丽美好的背后都是各种智慧、技巧、妥协、辛苦的支撑,不是有那么一句话嘛:如果你觉得轻松自如,比如有人在负重前行。而这个人就是我们喜爱的 Python 及其 编译器,要更好的理解一个东西,需要了解它背后的概念原理和理念,期望通过这篇短文对你有所启发

自学Python中,想自己买网络代理,该如何操作?

%title插图%num 如果是新手小白,自学Python阶段,没必要投入那么大。
如果你的目标网站只是自己的blog,网络代理ip是*佳选择了,普通匿名代理足够用了,购买高匿名代理ip也是不错的选择。
那么,自己购买网路代理,该怎么区分ip匿名度呢?

%title插图%num

httproxy:*常用的代理,代理客户端http访问,主要是代理浏览器访问网页,其端口通常是80,8080,3128等等。
HTTPS代理也称为SSL代理:http代理支持*高128位的加密强度,可用于访问加密站点。加密站点是指以https//开头的站点。标准端口sl为443。
HTTPCONNECT代理:允许用户在任何端口上建立TCP连接的代理服务器,不仅可以在HTTP上使用,也可以在FTP,IRC,RM流服务等方面使用。
socks代理:全能代理,就像有很多跳线的转接板一样,只是将一端的系统连接到另一端。对各种协议的支持,包括http请求、ftp请求和其他请求。该协议分为socks4和socks5两种,socks4只支持TCP协议,socks5支持TCP/UDP协议,同时还支持各种认证机制等。它的标准端口是1080。
TUNNELAgent:通过HTTPTunnet程序转换的数据包封装成http请求(Request)以穿透防火墙,允许HTTP服务器执行任何TCP可以执行的操作,其功能与Socks5相同。
教育网络代理:指学术教育机构的局域网,通过特定的代理服务器,可以让不具有出国权限或者不能访问IP段的计算机访问相关资源。
socks5代理:应用于socks5代理,可以认为socks5代理是动态加密的,也可以直接在PSD软件中使用。通常的端口是1813。
代理:在客户端代理ssso程序来访问远程网站,使用SSL加密的超级代理,支持socks。
如果业务类型对个人信息安全的要求比较高,建议使用高安全性的代理IP。

五款Android 应用的自动化测试工具

如今自动化测试已经应用到每天的测试中。这不足为奇,因为自动化测试在测试过程中节约了时间,还能避免包括人为因素造成的测试错误和遗漏。
自动化测试工具选择很多。一些是开源的,一些非常贵。一些自动化工具是几年前出的,一些才在市场上出来。每款工具有一定的特点,都是独特的。

在众多的可选择的自动化工具中,要选到项目合适的工具是比较困难的。问题是,几乎没有任何现有的工具完全适应项目的要求。

%title插图%num

为了自动化测试有效和有益,它必须具有:

  • 研究测试和项目中的软件产品;
  • 明确哪些测试需自动化
  • 制定自动化测试和自动化测试工具的要求
  • 研究至少几个可用的和合适的自动化工具
  • 在研究的基础上选择*合适的一个或多个工具
  • 与其他项目方讨论所选择的自动化工具,解释选择的原因,并得到他们的批准
  • 推进自动化

这些方法和步骤被多数质量保证专家认可。
*近出现了许多不同类型的计算机,他们的出现开始了软件产品的快速发展。*令人惊讶的是移动设备的进化,他们有着不同于普通的个人电脑的特点,方式和交互条件。
因此,智能手机需要的移动应用也是与笔记本不同的。
大多数个人电脑的操作系统是Windows。而流行的移动操作系统是Android,苹果iOS,黑莓OS,Windows手机,Symbian和其他。

Top 5 Android测试工具

让我们探索现在比较流行的移动操作系统支持的自动化测试工具。

#1. Robotium 安卓测试工具

   %title插图%num

Robotium是一款经常使用的自动化测试工具软件,支持Android。

Robotium是一个免费的Android UI测试工具。它适用于为不同的安卓版本和子版本测试自动化。软件开发人员经常把它描述为Android Selenium。Robotium测试是用java写的。事实上,Robotium是一个单元测试库。
但通过Robotium创建测试需要花费很多时间和努力,因为为了自动化测试还需要修改程序源代码。该工具也不适合与系统软件的交互,它不能锁定和解锁智能手机或平板电脑。Robotium也没有录制回放功能,也不提供截图。

#2.MonkeyRunner 安卓应用测试

%title插图%num

Monkeyrunner是一款流行的Android测试工具,用于自动化功能测试。

这个工具比Robotium更低一层次。这个不必处理源代码来做自动化测试。这个测试可以用Python写,并且可以使用录制工具来创建测试。
Monkeyrunner可以连接到电脑或模拟真实设备运行测试。该工具有一个接口,用它来控制智能手机,平板电脑或外部模拟器的Android代码。
这个测试工具的缺点是,它必须为每个设备编写脚本。另一个问题是,每次测试程序的用户界面变化都需要调整测试脚本。

#3.Ronaorex 安卓测试应用工具

   %title插图%num

Ranrex 是一款不仅可以支持*新Android版本,也支持从Android2.2开始的早期版本和分支版本。

Ranorex的优势是它有详细的截屏报告。它能通过Wifi连接智能手机和平板电脑。
一个自动化测试工程师通过这个Android工具可以不用XML数据格式来详细编写数据驱动的测试。Ranorex工作室使自动化测试工程师只要点击鼠标就可容易地创建测试。它允许详细声明额外的程序模块,来用于在后期开发周期中测试更复杂的场景。
它是一个商业的移动应用工具,其许可价格为1990欧元。不过Ranorex搜索功能相当慢;它需要30秒来完成这样的操作。我们必须为Ranorex配备apk文件设备,否则无法通过这个工具实现自动化测试,因为它只能在APK文件设备上工作。

#4.Appium安卓自动化框架

   %title插图%num

这是一个可以为iOS和Android做自动化测试的框架。它是一个开源工具。它支持从2.3及以后的安卓版本。Appium利用WebDriver接口运行测试。它支持多种编程语言,如java,C #,Ruby和其他在WebDriver库中的语言。

它可以控制移动设备上的Safari和Chrome。这样测试移动网站可使用Appium和这些浏览器。
但一些自动化测试工程师抱怨说,它没有详细的报告。其弱点还有减少了在移动设备上XPath支持。

#5.UI Automator 安卓测试自动化

%title插图%num

这款工具是谷歌*近发布的。它支持从4.1开始的安卓版本。这样就得再选择另一个安卓应用测试工具来做早期版本自动化测试。UI Automator能够与各种Android软件产品交互,包括系统中的应用。这使UI Automator可以锁定和解锁智能手机或平板电脑。

通过这个工具创建的脚本可以在许多不同的安卓平台上执行。它可以重现复杂的用户操作动作。
UI Automator也可以利用一个设备的外部按键,如回放键、音量调节键、开关键来控制。
它可以集成测试框架TestNG。在这种情况下,UI Automator可以生成丰富和详细的报告,类似于Ranorex生成报告。另外,这个工具搜索功能非常快。
软件测试专家发现UI Automator是一款适用于许多Android平台的移动应用测试。它是一款*适合安卓应用测试的工具之一,因为它是由谷歌专门为这个操作系统发布的。
通常约有80%的新软件bug能在所有支持的平台上重现。因此,一个可执行在广泛使用的平台上的移动测试工具是可以发现高达80%的缺陷。其余20%将会在其他平台上被发现。这意味着,在大多数情况下,在更少的测试平台上完整地做测试比在众多平台上匆忙测试更好。
目前,安卓操作系统设备上约66%使用的是安卓4.1。这就是为什么许多自动化测试工程师决定UI Automator是*合适的解决方案。
Ranorex经常用于早期的Android版本测试。

结论:

测试自动化是一个复杂的任务。它需要充分地准备和研究。需要紧跟信息技术的各种新奇应用和自动化测试工具。所有这些知识都是创建*有效的测试所必需的。

几款Android 应用自动化测试工具

简述:

本文介绍几款流行的 Android应用自动化测试工具。

Monkey测试:随机测试,压力测试,运行在模拟器或实际设备中。

MonkeyRunner测试:操作简单,可录制测试脚本,可视化操作,主要生成坐标的自动化操作,移植性不强

Robotium 测试

Ronaorex 测试

Appium 测试

UI Automator 测试

TestBird测试

1、Monkey 测试

Monkey 即猴子,Monkey 测试,就像一只猴子,在电脑面前,乱敲键盘在测试。

Monkey 测试主要用于Android 应用程序压力测试的小工具,主要目的就是为了测试app是否会Crash。

Monkey 测试原理:Monkey 是 Android 中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。通常也称随机测试或者稳定性测试。Monkey 测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。

1 > Monkey 特征

A. 测试的对象仅为应用程序包,有一定的局限性。

B. Monky 测试使用的事件流数据流是随机的,不能进行自定义。

C. 可对 MonkeyTest的对象,事件数量,类型,频率等进行设置。

D.Monkey 虽可根据一个指定的命令脚本发送按键消息,但其不支持条件判断,也不支持读取待测界面的信息来执行验证操作。

E.Monkey 运行在设备或模拟器上面,可以脱离PC运行,验证待测应用在这些随机性输入面前是否会闪退或者崩溃。

2 > Monkey 程序介绍
① Monkey 程序由 Android 系统自带,使用Java语言写成,在Android文件系统中的存放路径是: /system/framework/monkey.jar;
② Monkey.jar 程序是由一个名为“ monkey ”的Shell脚本来启动执行,shell脚本在Android文件系统中 的存放路径是:/system/bin/monkey;
③ Monkey 命令启动方式:

a. 可以通过 PC 机 CMD 窗口中执行:  adb shell monkey {+命令参数}来进行Monkey测试

b. 在PC上 adb shell进入Android系统,通过执行monkey {+命令参数}来进行Monkey 测试

c. 在Android机或者模拟器上直接执行monkey命令,可以在Android机上安装Android终端模拟器

④ 对特定APP包进行测试的命令为 adb shell monkey -p <pakage.name>

3 > 实例

① 测试前提条件

a. 将手机恢复出厂设置

b. 恢复出厂设置后,进入设置–>关于手机–>高级设置–>勾选‘保持唤醒状态’

c. 在设置->安全中设置解锁图案以及PIN码

d. 连接 adb tool

e. 手机开启后台log(*#*#3646633#*#*),开启main log,, mobile log和net log

② 测试步骤

a. 使用USB线连接手机和电脑

b. 在电脑中输入Monkey命令:

adb shell monkey -p <package.name> –throttle 380 -v -s 3500 300000 >C:\monkey_log.txt

c. 在Monkey结束以后查看它停留的界面并且做一些简单的测试,如拨打电话,发送信息等

③ 测试结果

Monkey的测试结果可以通过monkey_log.txt 查看,如果测试结果正常,在log*后一行会有monkey finished显示并且手机运行正常。如果应用程序产生了应用程序不响应ANR(application notresponding)的错误,Monkey将会停止并报错,如果应用程序崩溃Crash或接收到任何失控异常,Monkey也会停止并报错。

a. 在运行Monkey命令时,遇到Crash或者ANR,就会自动终止。程序无响应的问题:在日志中搜索 “ANR”

b.崩溃问题:在日志中搜索“Exception”   (如果出现空指针,NullPointerException)  肯定是有bug
例如在log*后一行显示crashed at event ####of 300000 using seed 3500 。

c.*后搜索“error”

一般我们执行Monkey时,在3万次以内发生Crash的话就认为Monkey是有问题的,要提交PR。

④ 提交 Monkey 的PR

在执行Monkey命令时发生Crash或者ANR时需要提交PR,具体提交MonkeyPR的规则如下:

a. 标题:在PR标题中加上[Monkey] 内容:主要要包含自己执行的命令以及在多少次发生crash

b. 内容:主要要包含自己执行的命令以及在多少次发生crash

c .其它:在PR上要附上相关的Monkey log还有手机后台开启的log,如果有相关的强制关闭的图片也可以贴上。

4 > Monkey 参数

Monkey命令:adb shell monkey  -p <package.name> –throttle 380 -v -s 3500300000 > C:\monkey_log.txt,这个monkey命令,当monkey test过程中遇到Crash或者ANR,就会自动终止。

C:\monkey_log.txt指将Monkey 的log存在PC端的C盘根目录下。

%title插图%num

常规类参数

1、 -help

作用:列出简单的用法

例:adb shell monkey -help   也可不写help

2、-v

作用:命令行上的每一个-v都将增加反馈信息的详细级别。
Level0(默认),除了启动、测试完成和*终结果外只提供较少的信息。

adb shell monkey -p com.shjt.map -v 100

Level1,提供了较为详细的测试信息,如逐个发送到 Activity 的事件信息。

adb shell monkey -p com.shjt.map -v -v 100

Level2,提供了更多的设置信息,如测试中选中或未选中的 Activity 信息。

adb shell monkey -p com.shjt.map -v -v -v 100

比较常用的是-v -v -v,即*多详细信息,一般会保存到指定文件中供开发人员查找bug原因时使用。

例:adb shell monkey -v 10

 

事件类参数

1、-s <seed>

作用:伪随机数生成器的seed值。如果用相同的seed值再次运行monkey,将生成相同的事件序列。

例:adb shell monkey -s 1483082208904 -v 10

 

2、–throttle <milliseconds>

作用:在事件之间插入固定的时间(毫秒)延迟,你可以使用这个设置来减缓Monkey的运行速度,如果你不指定这个参数,则事件之间将没有延迟,事件将以*快的速度生成。

注:常用参数,一般设置为300毫秒,原因是实际用户操作的*快300毫秒左右一个动作事件,所以此处一般设置为300毫秒。

例:adb shell monkey –throttle 300 -v 10

 

3、–pct-touch <percent>

作用:调整触摸事件的百分比。(触摸事件是指在屏幕中的一个down-up事件,即在屏幕某处按下并抬起的操作)

注:常用参数,此参数设置要适应当前被测应用程序的操作,比如一个应用80%的操作都是触摸,那就可以将此参数的百分比设置成相应较高的百分比。

例:adb shell monkey –pct-touch 100 -v 10

 

4、–pct-motion <percent>

作用:调整motion事件百分比。(motion事件是由屏幕上某处一个down事件、一系列伪随机的移动事件和一个up事件组成)
注:常用参数,需注意的是移动事件是直线滑动
例:adb shell monkey –pct-motion 100 -v 10

 

5、–pct-trackball<percent>
作用:调整滚动球事件百分比。(滚动球事件由一个或多个随机的移动事件组成,有时会伴随着点击事件)
注:不常使用参数,现在手机几乎没有滚动球,但滚动球事件中包含曲线滑动事件,在被测程序需要曲线滑动时可以选用此参数。
例:adb shell monkey –pct-trackball 100 -v 10

6、–pct-nav<percent>
作用:调整基本的导航事件百分比。(导航事件由方向输入设备的上下左右按键所触发的事件组成)
注:不常用操作。
例:adb shell monkey –pct-nav 100 -v 10

7、–pct-majornav<percent>
作用:调整主要导航事件的百分比。(这些导航事件通常会导致UI界面中的动作事件,如5-way键盘的中间键,回退按键、菜单按键)
注:不常用操作。
例:adb shell monkey –pct-majornav 100 -v 10

8、–pct-syskeys<percent>
作用:调整系统事件百分比。(这些按键通常由系统保留使用,如Home、Back、Start Call、EndCall、音量调节)
注:不常用。
例:adb shell monkey –pct-syskeys 100 -v 10

9、–pct-appswitch<percent>
作用:调整Activity启动的百分比。(在随机的时间间隔中,Monkey将执行一个startActivity()调用,作为*大程度覆盖被测包中全部Activity的一种方法)
注:不常用。
例:adb shell monkey –pct-appswitch 100 -v 5

10、–pct-anyevent
作用:调整其他事件的百分比。(这包含所有其他事件,如按键、其他在设备上不常用的按钮等)
注:不常用。
例:adb shell monkey –pct-anyevent 100 -v 5

约束类参数

1、-p<allowed-package-name>
作用:如果你指定一个或多个包,Monkey将只允许访问这些包中的Activity。如果你的应用程序需要访问这些包(如选择联系人)以外的Activity,你需要指定这些包。如果你不指定任何包,Monkey将允许系统启动所有包的Activity。指定多个包,使用多个-p,一个-p后面接一个包名。
注:常用参数。
例:adb shell monkey -p com.Android.browser -v 10

2、-c<main-category>
作用:如果你指定一个或多个类别,Monkey将只允许系统启动这些指定类别中列出的Activity。如果你不指定任何类别,Monkey将选择谢列类别中列出的Activity,Intent.CATEGORY_LAUNCHER和Intent.CATEGORY_MONKEY。指定多个类别使用多个-c,每个-c指定一个类别。
注:不常用。

3、–dbg-no-events
作用:设置此选项,Monkey将执行初始启动,进入一个测试Activity,并不会在进一步生成事件。为了得到*佳结果,结合参数-v,一个或多个包的约束,以及一个保持Monkey运行30秒或更长时间的非零值,从而提供了一个可以监视应用程序所调用的包之间转换的环境。
注:不常用。

4、–hprof
作用:设置此选项,将在Monkey生成事件序列前后生成profilling报告。在data/misc路径下生成大文件(~5Mb),所以要小心使用。
注:不常用。

5、–ignore-crashes
作用:通常,应用发生崩溃或异常时Monkey会停止运行。如果设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

6、–ignore-timeouts
作用:通常,应用程序发生任何超时错误(如“Application Not responding”对话框)Monkey将停止运行,设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

7、–ignore-security-exception
作用:通常,当程序发生许可错误(例如启动一些需要许可的Activity)导致的异常时,Monkey将停止运行。设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

8、–kill-process-after-error
作用:通常,当Monkey由于一个错误而停止时,出错的应用程序将继续处于运行状态。设置此项,将会通知系统停止发生错误的进程。注意,正常(成功)的结束,并没有停止启动的进程,设备只是在结束事件之后简单的保持在*后的状态。

9、–monitor-native-crashes

作用:监视并报告Andorid系统中本地代码的崩溃事件。如果设置–kill-process-after-error,系统将停止运行。

10、–wait-dbg

作用:停止执行中的Monkey,直到有调试器和它相连接。

样例:

adb shell monkey -p com.android.settings –throttle 380 -v -v -v -s 3500 300000 > E:\Test\monkey_log.txt

测试结果:

测试完成后均正确时会显示Monkey finished:

Events injected: 300
:Sending rotation degree=0, persist=false
:Dropped: keys=0 pointers=2 trackballs=0 flips=0 rotations=0
## Network stats: elapsed time=42700ms (0ms mobile, 0ms wifi, 42700ms not connected)
// Monkey finished

有bug时,会出现 error:

** Monkey aborted due to error.
Events injected: 8530
:Sending rotation degree=0, persist=false
:Dropped: keys=5 pointers=8 trackballs=0 flips=0 rotations=0
## Network stats: elapsed time=1016690ms (0ms mobile, 0ms wifi, 1016690ms not

connected)

2、 MonkeyRunner 测试

MonkeyRunner工具是使用 Jython (使用Java编程语言实现的Python)写出来的,它提供了多个API,通过MonkeyRunner API 可以写一个Python的程序来模拟操作控制Android设备app,测试其稳定性并通过截屏可以方便地记录出现的问题。

MonkeyRunner和Monkey没有直接的关系。Monkey是在设备/模拟器直接运行adb shell命令生成用户或系统伪随机事件流来进行测试的。

%title插图%num

而MonkeyRunner则运行在PC上,需要通过服务器/客户端的的模式向设备或者模拟器上的android应用发送指令来执行测试。它支持自己编写插件,控制事件,随时截图,简而言之,任何你在模拟器/设备中能干的事情,MonkeyRunner都能干,而且还可以记录和回放。

%title插图%num

1 > MonkeyRunner 特征
1)MonkeyRunner工具在工作站上通过API定义的特定命令和事件控制设备或模拟器(可控)

2)精确控制事件之间的事件

3)可以进行:点触屏、拖拽、长按、键盘事件

4)可以智能截图对比和判断

5)回溯出详细具体的BUG路径

2  > MonkeyRunner 优缺点

1) 能完全模拟人工所有操作

2) 有详细的API文档参考

3) 可以写出智能图像对比脚本

4) 支持 java 和 Python 两种语言脚本

5) 脚本移植性差

3 > MonkeyRunner测试类型

1)多设备控制

MonkeyRunnerAPI可以跨多个设备或模拟器实施测试套件。您可以在同一时间接上所有的设备或一次启动全部模拟器(或统统一起),依据程序依次连接到每一个,然后运行一个或多个测试。您也可以用程序启动一个配置好的模拟器,运行一个或多个测试,然后关闭模拟器。

2)功能测试

MonkeyRunner可以为一个应用自动贯彻一次功能测试。您提供按键或触摸事件的输入数值,然后观察输出结果的截屏。

3)回归测试

MonkeyRunner可以运行某个应用,并将其结果截屏与既定已知正确的结果截屏相比较,以此测试应用的稳定性。

4)可扩展的自动化

由于MonkeyRunner是一个API工具包,您可以基于Python模块和程序开发一整套系统,以此来控制Android设备。除了使用MonkeyRunner API之外,您还可以使用标准的Python os和subprocess模块来调用Android Debug Bridge这样的Android工具。

4 > MonkeyRunner 工具

MonkeyRunner API 主要包括三个模块

1)MonkeyRunner:

此类提供连接真机和模拟器方法waitForConnection(float timeout,stringdeviceid),还提供了创建用户界面显示信息的alert()方法。

2)MonkeyDevice

代表一个设备或模拟器。此类提供了安装和卸载程序包、开启Activity、发送按键和点击事件、运行测试包等方法

拖拉控件drag(tuple start,tuple end,floatduration,integer steps)  //duration手势持续时间

按键press(string keycode,dictionary type) //keycode:KEYCODE_HOME,..   type:DOWN ,UP,DOWN_AND_UP…

安装应用 installPackage(pc端存放apk路径)

启动应用starActivity(package+’/’+activity)//一个参数

点击touch(integer x,integer y, integer type)//type:DOWN,UP,DOWN_AND_UP…

输入type(string message)

截屏takeSnapshot()

3)MonkeyImage

这个类提供了捕捉屏幕的方法。

在测试过程中用来保存测试截图,将位图保存为各种格式,并可以比较两个MonkeyImage对象,将image保存到文件等。

图像对比sameAs(MonkeyImage other,float percent)//对比的相似度,结果boolean类型

图像保存writetoFile(string path,string format)

5 > MonkeyRunner 环境搭建

Monkeyrunner的环境搭建,需要安装以下工具:jdk、android sdk、python编译器。

MonkeyRunner 环境搭建
Eclipse中MonkeyRunner环境搭建

6 > MonkeyRunner 运行

运行有两种方式

① 在CMD命令窗口直接运行monkeyrunner

② 使用Python编写测试代码文件,在CMD中执行monkeyrunner xxx.py运行

不论使用哪种方式,您都需要调用SDK目录的tools子目录下的monkeyrunner命令。

1)模拟器启动

在运行monkeyrunner之前必须先运行相应的模拟器或连接真机,否则monkeyrunner无法连接到设备

运行模拟器有两种方法:1、通过eclipse中执行模拟器 2、在CMD中通过命令调用模拟器

这里介绍通过命令,在CMD中执行模拟器的方法

emulator -avd AVD_test
上面命令中 AVD_test 是指模拟器的名称。

2)交互对话环境

cmd 运行 monkeyrunner 交互命令“monkeyrunner” 或:

monkeyrunner -plugin

3)cmd 终端导入monkeyrunner所要使用的模块

from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage

然后便可以开始利用monkeyrunner进行测试了。

3) 模拟器连接

device=MonkeyRunner.waitForConnection(6,’emulator-5554′)

参数1:超时时间,单位秒,浮点数,默认是无限期地等待。
参数2:指定的设备名称device_id,默认为当前设备(手机优先,其次为模拟器)

4) app 安装

模拟器启动成功后,安装apk

 

device.installPackage(‘F:\\QQyinle_439.apk’)

其中,参数为apk的相对路径。成功返回true,且模拟器的IDLE界面可看到安装apk 图标

5)app 启动

device.startActivity(component=”package名/.activity”)

如何获取一个app的package名和activity?
使用

#apk路径中一定不能有空格
aapt dump badging F:\QQyinle_439.apk

aapt dump badging F:\QQyinle_439.apk > F:\log.txt

所以:

device.startActivity(component=” com.tencent.qqmusic/.activity.AppStarterActivity “)

命令执行后,模拟器上的app被启动。启动成功后,便可以向模拟器发送如按键、滚动、截图、存储等操作了

6)

问题:CMD运行提示monkeyrunner不是内部或外部命令,也不是可运行的程序或批处理文件。

解决:电脑环境变量未配置,将monkeyrunner所在目录配在环境变量里。

变量名:Path

变量值:D:\android\android-sdk-windows\tools;D:\android\android-sdk-windows\platform-tools

7 > 样例(Monkeyrunner运行python脚本)

test.py

#-*-UTF-8-*-
#如果导入的模块起了别名,后面就必须使用别名
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner import MonkeyDevice as md
from com.android.monkeyrunner import MonkeyImage as mi

#连接设备或虚拟器。
#参数1,超时时间,单位秒,默认无限期等待;参数2,设备名称,默认当前设备
device=mr.waitForConnection(2,’192.168.56.101:5555′)

#向设备或模拟器安装apk,以下两种方式都是对的
device.installPackage(‘D:\\baiduliulanqi_186.apk’)
#device.installPackage(‘D:/baiduliulanqi_186.apk’)

#启动APP
device.startActivity(‘cmp=com.baidu.browser.apps/com.baidu.browser.framework.BdBrowserActivity’)
mr.sleep(3)

#点击搜索框 #指定位置发送触摸事件
device.touch(100,100,’DOWN_AND_UP’)
mr.sleep(1)

#输入查询词
device.type(‘test’)
mr.sleep(1)

#点击回车键 #发送指定类型键码的事件
device.press(‘KEYCODE_ENTER’,’DOWN_AND_UP’)
mr.sleep(2)

#截图
result=device.takeSnapshot()

#保存到文件
result.writeToFile(‘./test.png’,’png’)

#清除搜索框
device.touch(100,100,’DOWN_AND_UP’)
mr.sleep(1)
device.press(‘KEYCODE_DEL’,’DOWN_AND_UP’)
mr.sleep(2)

#字符串发送到键盘
#device.type(‘字符串’)
device.type(‘Findyou’)

#唤醒设备屏幕
#锁屏后,屏幕关闭,可以用下命令唤醒
device.wake()

#重起手机
device.reboot()

#模拟滑动
#device.drag(X,Y,D,S)
#X 开始坐标
#Y 结束坐标
#D 拖动持续时间(以秒为单位),默认1.0秒
#S 插值点时要采取的步骤。默认值是10
device.drag((100,1053),(520,1053),0.1,10)

运行测试脚本test.py:monkeyrunner test.py
monkeyrunner 录制和回放
录制:monkey_recorder.py

from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
device=mr.waitForConnection()
recorder.start(device)

在 cmd 命令行运行 monkeyrunner  monkey_record.py,会弹出一个MonkeyRecord窗口界面该窗口的功能:

%title插图%num

A. 可以自动显示手机当前的界面

B. 自动刷新手机的*新状态

C. 点击手机界面即可对手机进行操作,同时会反应到真机,而且会在右侧插入操作脚本

D.

wait: 用来插入下一次操作的时间间隔,点击后即可设置时间,单位是秒

Press a Button:用来确定需要点击的按钮,包括menu、home、search,以及对按钮的press、down、up属性Type Something:用来输入内容到输入框

Fling:用来进行拖动操作,可以向上、下、左、右,以及操作的范围

Export Actions:用来导出脚本,不需要后缀名,也可以添加后缀名.mr

Refresh Display:用来刷新手机界面,估计只有在断开手机后,重新连接时才会用到

用录制函数导出操作的脚本,通过monkey_playback.py函数回放之前的操作

回放:monkey_playback.py,

import sys
from com.android.monkeyrunner import MonkeyRunner as mr

CMD_MAP = {
‘TOUCH’:lambda dev,arg:dev.touch(**arg),
‘DRAG’: lambda dev,arg:dev.drag(**arg),
‘TYPE’: lambda dev,arg:dev.type(**arg),
‘PRESS’: lambda dev,arg:dev.press(**arg),
‘WAIT’: lambda dev,arg:mr.sleep(**arg)
}

def process_file(f,device):
for line in f:
(cmd,rest)=line.split(‘|’)
try:
rest = eval(rest)
except:
print ‘unable to parse options’
continue
if cmd not in CMD_MAP:
print ‘unknown command: ‘ + cmd
continue
CMD_MAP[cmd](device, rest)
def main():
file = sys.argv[1]
f = open(file,’r’)
device = mr.waitForConnection()
process_file(f,device)
f.close()
if __name__ = ‘__main__’
main()

8 > 其他

#卸载设备或模拟器中的APK ,参数为APK包名
device.removePackage(‘cn.richinfo.thinkdrive’)
print (‘Uninstall Success!’)

#发送指定类型指定键码的事件
#device.press(参数1:键码,参数2:触摸事件类型)
#参数1:见android.view.KeyEvent
#参数2,如有TouchPressType()返回的类型-触摸事件类型,有三种。
#1、DOWN 发送一个DOWN事件。指定DOWN事件类型发送到设备,对应的按一个键或触摸屏幕上。
#2、UP 发送一个UP事件。指定UP事件类型发送到设备,对应释放一个键或从屏幕上抬起。
#3、DOWN_AND_UP 发送一个DOWN事件,然后一个UP事件。对应于输入键或点击屏幕。
以上三种事件做为press()参数或touch()参数

#按下HOME键
device.press(‘KEYCODE_HOME’,MonkeyDevice.DOWN_AND_UP)
#按下BACK键
device.press(‘KEYCODE_BACK’,MonkeyDevice.DOWN_AND_UP)
#按下下导航键
device.press(‘KEYCODE_DPAD_DOWN’,MonkeyDevice.DOWN_AND_UP)
#按下上导航键
device.press(‘KEYCODE_DPAD_UP’,MonkeyDevice.DOWN_AND_UP)
#按下OK键
device.press(‘KEYCODE_DPAD_CENTER’,MonkeyDevice.DOWN_AND_UP)

KeyCode:
home键 KEYCODE_HOME
back键 KEYCODE_BACK
send键 KEYCODE_CALL
end键 KEYCODE_ENDCALL
上导航键 KEYCODE_DPAD_UP
下导航键 KEYCODE_DPAD_DOWN
左导航 KEYCODE_DPAD_LEFT
右导航键 KEYCODE_DPAD_RIGHT
ok键 KEYCODE_DPAD_CENTER
上音量键 KEYCODE_VOLUME_UP
下音量键 KEYCODE_VOLUME_DOWN
power键 KEYCODE_POWER
camera键 KEYCODE_CAMERA
menu键 KEYCODE_MENU

3、Robotium测试

Robotium 是一款常用的免费的 Android 自动化测试工具软件,适用于为不同的安卓版本和子版本黑盒测试自动化。Robotium 测试是用java写的。提供了模拟各种手势操作(点击、长按、滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium 对 Activity,Dialog,Toast,Menu 都是支持的。软件开发人员经常把它描述为Android Selenium。事实上,Robotium是一个单元测试库。

为了自动化测试需要修改程序源代码。该工具也不适合与系统软件的交互,它不能锁定和解锁智能手机或平板电脑。Robotium 也没有录制回放功能,也不提供截图。

详情请参考:

Robotium自动化测试框架使用教程

4、Ronaorex测试

Ranrex 是一款不仅可以支持*新Android版本,也支持从Android2.2开始的早期版本和分支版本。

Ranorex的优势是它有详细的截屏报告。它能通过Wifi连接智能手机和平板电脑。
一个自动化测试工程师通过这个Android工具可以不用XML数据格式来详细编写数据驱动的测试。Ranorex工作室使自动化测试工程师只要点击鼠标就可容易地创建测试。它允许详细声明额外的程序模块,来用于在后期开发周期中测试更复杂的场景。

它是一个商业的移动应用工具,其许可价格为1990欧元。不过Ranorex搜索功能相当慢;它需要30秒来完成这样的操作。我们必须为Ranorex配备apk文件设备,否则无法通过这个工具实现自动化测试,因为它只能在APK文件设备上工作。

5、Appium测试

这是一个可以为iOS和Android做自动化测试的框架。它是一个开源工具。它支持从2.3及以后的安卓版本。Appium利用WebDriver接口运行测试。它支持多种编程语言,如java,C #,Ruby和其他在WebDriver库中的语言。

它可以控制移动设备上的Safari和Chrome。这样测试移动网站可使用Appium和这些浏览器。
但一些自动化测试工程师抱怨说,它没有详细的报告。其弱点还有减少了在移动设备上XPath支持。

Appium环境搭建(Windows版)

6、UI Automator 测试

这款工具是谷歌发布的。它支持从4.1开始的安卓版本。UI Automator能够与各种Android软件产品交互,包括系统中的应用。这使UI Automator可以锁定和解锁智能手机或平板电脑。

通过这个工具创建的脚本可以在许多不同的安卓平台上执行。它可以重现复杂的用户操作动作。
UI Automator也可以利用一个设备的外部按键,如回放键、音量调节键、开关键来控制。
它可以集成测试框架TestNG。在这种情况下,UI Automator可以生成丰富和详细的报告,类似于Ranorex生成报告。另外,这个工具搜索功能非常快。

软件测试专家发现UI Automator是一款适用于许多Android平台的移动应用测试。它是一款*适合安卓应用测试的工具之一,因为它是由谷歌专门为这个操作系统发布的。

通常约有80%的新软件bug能在所有支持的平台上重现。因此,一个可执行在广泛使用的平台上的移动测试工具是可以发现高达80%的缺陷。其余20%将会在其他平台上被发现。这意味着,在大多数情况下,在更少的测试平台上完整地做测试比在众多平台上匆忙测试更好。

uiautomatorviewer :一个图形界面工具来扫描和分析应用的UI控件。

uiautomator :一个测试的Java库,包含了创建UI测试的各种API和执行自动化测试的引擎

参考:

http://blog.csdn.net/u010961631/article/details/9616581

https://www.jianshu.com/p/c900efe8c982

https://segmentfault.com/a/1190000004619487

7、TestBird 测试

TestBird自动回归测试平台为手游/APP开发者提供APP自动化回归测试,简单点击自动生成图片用例;多台手机同时执行用例回归;基线对比,找出问题;调整基线,维护测试用例;一键生成报告,全面提升测试效率和质量。

TestBird*初是从手游测试开始起步,在手游圈积累起很高的知名度,目前也在逐步向APP测试领域进军,同时TestBird也加入了智能硬件的测试领域。基于全球首创的对象识别技术,TestBird可以深入到移动App&游戏内部所有功能的深度解析能力。TestBird建立了云手机、云测试和云分析三大测试平台,通过自助App功能测试、远程真机调试、真机兼容性测试、真人体验测试、 真人压力测试和崩溃分析等,为移动应用提供从研发到上线再到运营的一站式质量管理服务。

 

Sign in with Apple(苹果授权登陆) java jwt方式验证

苹果授权登陆方式
PC/M端授权登陆,采用协议类似于oauth2协议
App端授权登陆,提供两种后端验证方式
开发者后台配置
详细配置参考该文档,手把手教学
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple

1、 PC/M接入方式

https://appleid.apple.com/auth/authorize?response_type=code&client_id=&redirect_uri=&state=1234
参考上面的后台配置,其中client_id对应的是Services ID,redirect_uri就是后台配置的接收code码的地址
2、APP端客户端授权登陆功能开发,可以参考如下文档

https://www.jianshu.com/p/23b46dea2076

重点讲解苹果授权登陆后端如何验证
1、基于JWT的算法验证

使用到的Apple公钥接口:https://appleid.apple.com/auth/keys
详细接口文档说明参见:https://developer.apple.com/documentation/signinwithapplerestapi/fetch_apple_s_public_key_for_verifying_token_signature
接口返回值:

{
“keys”:[
{
“kty”:”RSA”,
“kid”:”AIDOPK1″,
“use”:”sig”,
“alg”:”RS256″,
“n”:”lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w”,
“e”:”AQAB”
}
]
}

kid,为密钥id标识,签名算法采用的是RS256(RSA 256 + SHA 256),kty常量标识使用RSA签名算法,其公钥参数为n和e,其值采用了BASE64编码,使用时需要先解码

使用方式:APP内苹果授权登陆会提供如下几个参数:userID、email、fullName、authorizationCode、

identityToken
userID:授权的用户唯一标识
email、fullName:授权的用户资料
authorizationCode:授权code
identityToken:授权用户的JWT凭证
下面针对identityToken后端验证做简要说明:

dentityToken参考样例(dentityToken为ios传给后端,后端也只用到了这一个jwt串):
// jwt格式 (这个jwt串为ios传给后端)
eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuZGV2aWNlbW9uaXRvciIsImV4cCI6MTU2NTY2ODA4NiwiaWF0IjoxNTY1NjY3NDg2LCJzdWIiOiIwMDEyNDcuOTNiM2E3OTlhN2M4NGMwY2I0NmNkMDhmMTAwNzk3ZjIuMDcwNCIsImNfaGFzaCI6Ik9oMmFtOWVNTldWWTNkcTVKbUNsYmciLCJhdXRoX3RpbWUiOjE1NjU2Njc0ODZ9.e-pdwK4iKWErr_Gcpkzo8JNi_MWh7OMnA15FvyOXQxTx0GsXzFT3qE3DmXqAar96nx3EqsHI1Qgquqt2ogyj-lLijK_46ifckdqPjncTEGzVWkNTX8uhY7M867B6aUnmR7u-cf2HsmhXrvgsJLGp2TzCI3oTp-kskBOeCPMyTxzNURuYe8zabBlUy6FDNIPeZwZXZqU0Fr3riv2k1NkGx5MqFdUq3z5mNfmWbIAuU64Z3yKhaqwGd2tey1Xxs4hHa786OeYFF3n7G5h-4kQ4lf163G6I5BU0etCRSYVKqjq-OL-8z8dHNqvTJtAYanB3OHNWCHevJFHJ2nWOTT3sbw
1
2
如何验证?
验证苹果登录主要是通过ios端传给后端的jwt串验证是否在有效期内

上面说到请求苹果的公钥接口会返回一些参数,我们主要用2个参数 n 和 e 来算出publicKey 作为jwt签名解开jwt
那么通过n和e算出publicKey 我们用到了一个工具类 hutool(因为封装的好比较方便,并且不需要三方依赖)
引入hutool和jwt的maven依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>

然后使用hutool

//n为请求苹果接口返回的n值
String n=”lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w”;
//e为请求苹果接口返回的e值
String e=”AQAB”;
//苹果请求回来的n和e为base64加密过,导入import cn.hutool.core.codec.Base64Decoder包,解密base64为数组
byte[] nDecode = Base64Decoder.decode(n.getBytes());
byte[] eDecode = Base64Decoder.decode(e.getBytes());
//通过RSAPublicKeySpec构造器生成对象
RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(new BigInteger(1, nDecode), new BigInteger(1, eDecode));
//传入hutool工具类中生成publicKey import cn.hutool.crypto.SecureUtil;
PublicKey publicKey = SecureUtil.generatePublicKey(“RSA”, rsaPublicKeySpec);
//ios传给后端的jwt串
String jwt=”eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuZGV2aWNlbW9uaXRvciIsImV4cCI6MTU2NTY2ODA4NiwiaWF0IjoxNTY1NjY3NDg2LCJzdWIiOiIwMDEyNDcuOTNiM2E3OTlhN2M4NGMwY2I0NmNkMDhmMTAwNzk3ZjIuMDcwNCIsImNfaGFzaCI6Ik9oMmFtOWVNTldWWTNkcTVKbUNsYmciLCJhdXRoX3RpbWUiOjE1NjU2Njc0ODZ9.e-pdwK4iKWErr_Gcpkzo8JNi_MWh7OMnA15FvyOXQxTx0GsXzFT3qE3DmXqAar96nx3EqsHI1Qgquqt2ogyj-lLijK_46ifckdqPjncTEGzVWkNTX8uhY7M867B6aUnmR7u-cf2HsmhXrvgsJLGp2TzCI3oTp-kskBOeCPMyTxzNURuYe8zabBlUy6FDNIPeZwZXZqU0Fr3riv2k1NkGx5MqFdUq3z5mNfmWbIAuU64Z3yKhaqwGd2tey1Xxs4hHa786OeYFF3n7G5h-4kQ4lf163G6I5BU0etCRSYVKqjq-OL-8z8dHNqvTJtAYanB3OHNWCHevJFHJ2nWOTT3sbw”;
//import io.jsonwebtoken.Jwts;解jwt,publicKey作为签名
Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();

此时按照上面的jwt值运行会抛出:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2019-08-13T11:48:06+0800. Current time: 2019-12-17T12:14:49+0800
说明该jwt已经过期,可以自己catch住这个异常给前端返回code和message

如果使用真实jwt串解开,可以使用 body.getSubject() 获取用户的唯一标识

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速