前言

基于ATX-Server2的UI自动化测试框架,支持多设备的并行测试,并生成统一的测试报告

支持atx-server2

android:修改config下的Server地址为atx-server2的Url,token填写正确后,就可以正常运行了

atxserver2设备管理平台搭建

前置条件

部署好atxserver2
只支持Python 3上运行 本人python版本 3.7
Android设备需要通过uiautomator2 init 初始化完成(python -m uiautomator2 init),确认可以正常连接

工程介绍

工程目录如下

Directory

common:

  • devices.py 获取atx-server2上特定设备(get_atxserver2_online_devices())、或config.ini下devices IP列表的在线设备(get_local_devices())、有线连接电脑的设备自动连接u2(get_usb_devices())
  • atxserver.py 获取
  • base_page.py 用于设备的初始化 u2.connect 已经一些公共模块的封装
  • chromedriver.py 和Ports.py 结合使用,启动chromedriver以便实现u2的webview操作(目前还没做到根据设备的chromeversion 启动指定版本的chromedriver)
  • cases_trategy.py 组织测试用例
  • decorator.py 有*@*testcase*@*teststep这样的装饰器用例执行日志打印、错误后的处理、生成截图(支持gif测试步骤)
  • report.py 对生成的报告的一些操作,备份Testreport的报告到TestReport_History下、多设备统一报告的生成、报告的文件夹压缩
  • test_data.py 在执行测试前的测试数据的生成,会在common下生成data.json,测试执行的时候各个设设备更具自己的serial获取对应的测试数据
  • drivers.py 设备的获取,初始化准备,测试执行都是在这里完成的
  • run_case.py 存放测试报告/日志/截图的路径的生成,以及最终通过HTMLTestRunner来执行用例
  • config.ini 一些需要用到的数据,atx-server地址、测试设备的ip、测试数据等

流程:

通过runAll .py开始执行测试

1
2
3
4
5
if __name__ == '__main__':
cs = CaseStrategy()
cases = cs.set_case_suite()
Drivers().run(cases)
zip_report()
  1. ​ 通过CaseStrategy获取到需要执的测试用例
  2. Drivers().run(cases)开始执行测试
  3. ​ 执行完成之后打包压缩(压缩后发邮件方便)

Drivers().run(cases)执行测试

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
def run(self, cases):
start_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
devices = check_devices()
if not devices:
print('没有发现设备,测试结束')
return

# generate test data data.json 准备测试数据
generate_test_data(devices)

print('Starting Run test >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
# devices_list = []
# for i in range(len(devices)):
# devices_list.append(RunCases(devices[i]))
run_devices_list = [RunCases(device) for device in devices]

# run on every device 开始执行测试
pool = Pool(processes=len(run_devices_list))
for run_device in run_devices_list:
pool.apply_async(self._run_cases,
args=(run_device, cases,))
print('Waiting for all runs done........ ')
pool.close()
pool.join()
print('All runs done........ ')
# ChromeDriver.kill()

# Generate statistics report 生成统计测试报告 将所有设备的报告在一个HTML中展示
create_statistics_report(run_devices_list)
backup_report('./TestReport', './TestReport_History', start_time)
```
```python
def check_devices():
# 根据method 获取android设备
method = ReadConfig().get_method().strip()
if method == 'SERVER2':
print('atxserver2 中获取可用的在线设备…')
devices = get_atxserver2_online_devices(Atxserver2(ReadConfig().get_server_url()).present_android_devices())
print('atxserver2中有%s台在线设备' % len(devices))

elif method == 'UDID':
print('Get available UDID devices %s from atxserver2...' % ReadConfig().get_server_udid())
devices = get_atxserver2_online_devices(Atxserver2(ReadConfig().get_server_url()).present_udid_devices())
print('\nThere has %s available udid devices in atxserver2' % len(devices))

elif method == 'IP':
# get devices from config devices list
print('Get available IP devices %s from config... ' % ReadConfig().get_devices_ip())
devices = get_local_devices()
print('\nThere has %s devices alive in config IP list' % len(devices))

elif method == 'USB':
# get devices connected PC with USB
print('Get available USB devices connected on PC... ')
devices = get_usb_devices()
print('\nThere has %s USB devices alive ' % len(devices))

else:
raise Exception('Config.ini method illegal:method =%s' % method)

return devices
```
```python
def _run_cases(run_device, cases):
log = Log()
log.set_logger(run_device.get_device()['model'], os.path.join(run_device.get_path(), 'client.log'))
log.i('udid: %s' % run_device.get_device()['udid'])

# 设置cls.path, 必须在操作任何页面之前调用
path = ReportPath()
path.set_path(run_device.get_path())

# 设置cls.driver, 必须在操作任何页面之前调用
base_page = BasePage()
if 'ip' in run_device.get_device():
base_page.set_driver(run_device.get_device()['ip'])
else:
base_page.set_driver(run_device.get_device()['serial'])

try:
# run cases
base_page.set_fastinput_ime() # 切换成FastInputIME输入法
base_page.d.shell('logcat -c') # 清空logcat

run_device.run_cases(cases) # RunCases().run(cases)
# 将logcat文件上传到报告
base_page.d.shell('logcat -d > /sdcard/logcat.log')
time.sleep(1)
base_page.d.pull('/sdcard/logcat.log', os.path.join(path.get_path(), 'logcat.log'))
base_page.set_original_ime() # 切换成正常输入法

if ReadConfig().get_method().strip() in ["UDID", "SERVER2"]:
log.i('释放设备 %s ' % run_device.get_device()['serial'])
Atxserver2(ReadConfig().get_server_url()).release_device(run_device.get_device()['serial'])
else:
pass
except AssertionError as e:
log.e('AssertionError, %s', e)
  1. ​首先根据config.ini中method的值来判断从SERVER2获取online的设备 还是从config.ini中的ip来获取在线的设备
  2. ​在获取到设备之后,根据设备生产data.json测试数据
  3. ​生成设备列表
  4. 根据设备列表数生成多进程,执行测试用例
  5. ​测试完之后,杀掉执行过程
  6. ​最后在TestReport下生成统计测试报告,并且移到TestReport_History下(自动化测试报告.html

结果展示

生成的测试报告路径结构如下
TestReport

每个设备的测试结果及报告或存放在单独的文件夹下
在Testreport目录下会有一个统计测试报告(自动化测试报告.html)会将所有设备的报告统一在一个页面展示
Report

性能测试工具

谈到性能测试工具,我们首先想到的是LoadRunner或JMeter。
LoadRunner是非常有名的商业性能测试工具,功能非常强大。但现在一般不推荐使用该工具来进行性能测试,主要是使用也较为复杂,而且该工具体积比较大,需要付费且价格不便宜。
JMeter同样是非常有名的开源性能测试工具,功能也很完善,我们之前介绍了它作为接口测试工具的使用。
Locust同样是性能测试工具,虽然官方这样来描述它:“An open source load testing tool.”,但它和前面两个工具有一些不同。  

Locust简介

Locust完全基于Python编程语言,采用纯 Python描述测试脚本,并且HTTP请求完全基于Requests库。除了HTTP/HTTPS协议外,Locust还可以测试其他协议的系统,只需采用Python调用对应的库进行请求描述即可。
LoadRunner和JMeter这类采用进程和线程的测试工具,都很难在单机上模拟出较高的并发压力。Locust的并发机制摒弃了进程和线程,采用协程(gevent)的机制。协程避免了系统级资源调度,因此可以大幅提高单机的并发能力。

扩展资料:协程—廖雪峰博客

下载安装
官网地址:https://www.locust.io

使用pip命令安装Locust:

1
pip install locustio

安装完成之后检测是否安装成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
locust -help
Usage: locust [options] [LocustClass [LocustClass2 ... ]]

Options:
-h, --help show this help message and exit
-H HOST, --host=HOST Host to load test in the following format:
http://10.21.32.33
--web-host=WEB_HOST Host to bind the web interface to. Defaults to '' (all
interfaces)
-P PORT, --port=PORT, --web-port=PORT
Port on which to run web host
-f LOCUSTFILE, --locustfile=LOCUSTFILE
Python module file to import, e.g. '../other.py'.
Default: locustfile
....

测试案例

测试场景

针对如下两个接口进行性能测试:

1
2
http://127.0.0.1:8000/users/
http://127.0.0.1:8000/groups/

以上两个接口也就是我们之前项目django_restful的接口

负载场景

* 每秒生成2个用户,总共生成60个用户。
* 负载测试5分钟然后查看接口的平均响应时间。

脚本实现

restful_api_locust.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from locust import  HttpLocust,TaskSet,task

class UserBehavior(TaskSet):

@task(2)
def test_users(self):
self.client.get("/users/",auth=('51zxw','zxw20182018'))


@task(1)
def test_groups(self):
self.client.get("/groups/",auth=('51zxw','zxw20182018'))


class WebsiteUser(HttpLocust):
task_set = UserBehavior
min_wait = 3000
max_wait = 6000

UserBehavior类继承TaskSet类,用于描述用户行为。

* @task装饰该方法为一个事务,后面的数字表示请求比例,上面的比例为2:1 默认都是1:1
* test_users()方法表示一个用户行为,这里是请求user接口。
* test_groups()方法表示请求group接口。
* client.get()用于指定请求的路径。

WebsiteUser类用于设置性能测试。

* task_set:指向一个定义的用户行为类。
* min_wait:执行事务之间用户等待时间的下界(单位:毫秒)。
* max_wait:执行事务之间用户等待时间的上界(单位:毫秒)。

执行测试

使用如下命令开始启动测试

1
2
3
locust -f vic\locust\restful_api_locust.py --host=http://127.0.0.1:8000
[2018-07-24 15:39:22,917] LAPTOP-8B5JADC8/INFO/locust.main: Starting web monitor at *:8089
[2018-07-24 15:39:22,917] LAPTOP-8B5JADC8/INFO/locust.main: Starting Locust 0.8.1

在浏览器打开localhost:8089可以看到如下页面:

locust-index

* Number of users to simulate:设置模拟用户数。
* Hatch rate(users spawned/second):每秒产生(启动)的虚拟用户数。
* 单击“Start swarming”按钮,开始运行性能测试

运行之后可以看到主界面如下:

locust-statistics

性能测试参数如下。

* Type:请求的类型,例如GET/POST。
* Name:请求的路径。
* request:当前请求的数量。
* fails:当前请求失败的数量。
* Median:中间值,单位毫秒,一半的服务器响应时间低于该值,而另一半高于该值。
* Average:平均值,单位毫秒,所有请求的平均响应时间。
* Min:请求的最小服务器响应时间,单位毫秒。
* Max:请求的最大服务器响应时间,单位毫秒。
* Content Size:单个请求的大小,单位字节。
* reqs/sec:每秒钟请求的个数。

点击 Charts 菜单可以查看性能图表

locust-charts

图表含义如下:

* Total Request per Second :每秒的请求数
* Average Response Time: 平均响应时间
* Number of Users: 用户数

参数化

测试场景

如果想对如下接口进行并发测试,则可以将id进行参数化设置

* http://127.0.0.1:8000/groups/1/
* http://127.0.0.1:8000/groups/2/
* http://127.0.0.1:8000/users/1/
* http://127.0.0.1:8000/users/2/

代码实现
locust_users_groups.py

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

from locust import HttpLocust,TaskSet,task

class UserBehavior(TaskSet):

def on_start(self):
#设置user和group参数下标初始值
self.users_index=0
self.groups_index=0

@task
def test_users(self):
#读取参数
users_id=self.locust.id[self.users_index]
url="/users/"+str(users_id)+'/'
self.client.get(url,auth=('51zxw','zxw20182018'))
#取余运算循环遍历参数
self.users_index=(self.users_index+1)%len(self.locust.id)

@task
def test_groups(self):
#参数化
groups_id=self.locust.id[self.groups_index]
url="/groups/"+str(groups_id)+"/"
self.client.get(url,auth=('51zxw','zxw20182018'))
self.groups_index=(self.groups_index+1)%len(self.locust.id)

class WebsiteUser(HttpLocust):
task_set = UserBehavior
#参数配置
id=[1,2]
min_wait = 3000
max_wait = 6000
#host配置
host = 'http://127.0.0.1:8000'

运行结果

执行如下命令即可运行测试

1
locust -f vic\locust\locust_users_groups.py

locust-params

运行模式

no-web运行

前面是通过登录web来运行测试的,其实也可以非web状态来运行,如cmd命令来运行。
如果需要非Web形式运行,则需使用–no-web参数,并会用到如下几个参数。

* -c, --clients:指定并发用户数;
* -r, --hatch-rate:指定并发加压速率,默认值位1。
* -t, --run-time:设置运行时间。如(300s,20m, 3h, 1h30m等);

运行命令如下:

1
locust -f vic\locust\locust_users_groups.py --no-web -c 10 -r 2 -t 15s

运行结果如下:

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
locust -f vic\locust\locust_users_groups.py --no-web -c 10 -r 2 -t 15s
[2018-08-21 10:12:59,017] LAPTOP-8B5JADC8/INFO/locust.main: Run time limit set to 15 seconds
[2018-08-21 10:12:59,017] LAPTOP-8B5JADC8/INFO/locust.main: Starting Locust 0.8
[2018-08-21 10:12:59,018] LAPTOP-8B5JADC8/INFO/locust.runners: Hatching and swarming 10 clients at the rate 2 clients/s...
Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------
Total 0 0(0.00%) 0.00

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 2 0(0.00%) 134 122 146 | 120 0.00
GET /users/1/ 2 0(0.00%) 118 100 136 | 100 0.00
--------------------------------------------------------------------------------------------------------------------------------------------
Total 4 0(0.00%) 0.00

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 4 0(0.00%) 135 122 146 | 130 1.00
GET /users/1/ 4 0(0.00%) 115 100 136 | 100 1.00
--------------------------------------------------------------------------------------------------------------------------------------------
Total 8 0(0.00%) 2.00

[2018-08-21 10:13:04,034] LAPTOP-8B5JADC8/INFO/locust.runners: All locusts hatched: WebsiteUser: 10
[2018-08-21 10:13:04,034] LAPTOP-8B5JADC8/INFO/locust.runners: Resetting stats

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 1 0(0.00%) 147 147 147 | 150 0.00
GET /users/1/ 2 0(0.00%) 110 92 128 | 92 0.00
GET /users/2/ 1 0(0.00%) 102 102 102 | 100 0.00
--------------------------------------------------------------------------------------------------------------------------------------------
Total 4 0(0.00%) 0.00

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 3 0(0.00%) 129 101 147 | 140 0.00
GET /users/1/ 3 0(0.00%) 108 92 128 | 100 0.00
GET /users/2/ 1 0(0.00%) 102 102 102 | 100 0.00
--------------------------------------------------------------------------------------------------------------------------------------------
Total 7 0(0.00%) 0.00

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 4 0(0.00%) 125 101 147 | 110 1.00
GET /groups/2/ 2 0(0.00%) 136 116 156 | 120 0.00
GET /users/1/ 3 0(0.00%) 108 92 128 | 100 1.00
GET /users/2/ 2 0(0.00%) 102 102 102 | 100 0.33
--------------------------------------------------------------------------------------------------------------------------------------------
Total 11 0(0.00%) 2.33

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 5 0(0.00%) 123 101 147 | 120 0.75
GET /groups/2/ 3 0(0.00%) 124 100 156 | 120 0.50
GET /users/1/ 3 0(0.00%) 108 92 128 | 100 0.75
GET /users/2/ 4 0(0.00%) 114 102 153 | 100 0.25
--------------------------------------------------------------------------------------------------------------------------------------------
Total 15 0(0.00%) 2.25

Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 6 0(0.00%) 128 101 157 | 120 0.67
GET /groups/2/ 5 0(0.00%) 127 100 156 | 120 0.33
GET /users/1/ 4 0(0.00%) 108 92 128 | 100 0.50
GET /users/2/ 5 0(0.00%) 121 102 153 | 100 0.50
--------------------------------------------------------------------------------------------------------------------------------------------
Total 20 0(0.00%) 2.00

[2018-08-21 10:13:13,691] LAPTOP-8B5JADC8/INFO/locust.main: Time limit reached. Stopping Locust.
[2018-08-21 10:13:13,693] LAPTOP-8B5JADC8/INFO/locust.main: Shutting down (exit code 0), bye.
Name # reqs # fails Avg Min Max | Median req/s
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 6 0(0.00%) 128 101 157 | 120 0.67
GET /groups/2/ 5 0(0.00%) 127 100 156 | 120 0.33
GET /users/1/ 4 0(0.00%) 108 92 128 | 100 0.50
GET /users/2/ 5 0(0.00%) 121 102 153 | 100 0.50
--------------------------------------------------------------------------------------------------------------------------------------------
Total 20 0(0.00%) 2.00

Percentage of the requests completed within given times
Name # reqs 50% 66% 75% 80% 90% 95% 98% 99% 100%
--------------------------------------------------------------------------------------------------------------------------------------------
GET /groups/1/ 6 140 140 150 150 160 160 160 160 157
GET /groups/2/ 5 120 150 150 160 160 160 160 160 156
GET /users/1/ 4 110 110 130 130 130 130 130 130 128
GET /users/2/ 5 100 150 150 150 150 150 150 150 153
--------------------------------------------------------------------------------------------------------------------------------------------
Total 20 120 140 150 150 160 160 160 160 157

分布式运行

上面我们都是单台机器来执行性能测试,但是当单台机器不够模拟足够多的用户时,Locust支持运行在多台机器中进行压力测试。分布式运行一般是一台master 多台slave 如下图所示:

image

首先定义一台 master

1
locust -f vic\locust\locust_users_groups.py --master

然后定义 slave

locust -f D:\api_test\locust\locust_users_groups.py –slave
如果slave与master不在同一台机器上,还需要通过–master-host参数再指定master的IP地址。

C:\Users\Shuqing>locust -f D:\api_test\locust\locust_users_groups.py –slave –master-host
运行之后可以看到web界面显示的SLAVES数量

locust-slave

[TOC]

前言

前面我们已经搭建好了ATX2平台来管理iOS设备,搭建博文参考:ATX2 iOS设备管理平台搭建 接下来分享一下该平台的入门进阶使用(还是基于iOS设备)。

ATX2入门操作

  • 打开登录地址:http://172.28.xx.xx:4000/login 输入用户名登录ATX平台。
  • 登录之后进入ATX2界面,在对应的设备点击点击使用则可以远程操作设备。
  • 如下图所示,点击使用之后可以使用鼠标进行设备远程操作控制。
    示例图

注意: iOS的弹窗不能通过屏幕点击来选择,需要在常用功能中点击“选择弹窗按钮”来进行操作

ATX2 API

ATX2提供了API可以进行调用,可以方便我们在自动化测试过程中进行调用。
API文档地址:https://github.com/openatx/atxserver2/blob/master/API.md

准备工作

token获取

ATX2所有的接口采用token认证,token可以在http://172.28.xx.xx:4000/user页面获取到
也可以点击右上角用户名->用户信息获取,如下图所示:
示例图

获取设备的udid

设备列表界面中的设备编号即为设备的udid,可以使用libimobiledevice命令获取设备的udid,命令如下:

1
2
idevice_id -l  # 显示当前所连接的设备[udid],包括 usb、WiFi 连接
c06e788b2d8dc60004a7015ce5dad782

API封装

为了更好的调用API,我们可以进行封装方法,便于后续的调用。首先如下所示,我们定义一个类AtxiOS 这个类进行远程调用设备的初始化操作,如atx地址和token的初始化。

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
import json,logging
from urllib.parse import urljoin
import requests
from pprint import pprint

logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s%(message)s')

DEVICES_URL = '/api/v1/devices' #获取全部设备信息
USER_DEVICES_URL = '/api/v1/user/devices' #获取单台设备信息

class AtxiOS(object):
'''
封装ATX2接口用于设备远程操作
'''
def __init__(self, atx_url, token):
'''
初始化操作
:param atx_url: atx的url地址
:param token: 账户的token
'''
self.atx_url = atx_url
self.token = token
self.session = requests.Session()
self.headers = dict(Authorization="Bearer {}".format(self.token))
self.apply_devices = {}

def _get_response(self, url, method="GET", headers=None, data=None):
"""
获取API响应结果
:param url:返回的url地址
:param method:请求方法
:param headers:响应header内容
:param data:响应的data内容
:return: 返回响应信息
"""
if not headers:
headers = self.headers

try:
response = self.session.request(method, url, headers=headers, data=data, timeout=20)
info = response.json()
except Exception as error:
logging.error('网址:{}请求失败。{}'.format(url, error))
info = {'success': False, 'description': 'Bad request url: {}'.format(url)}
return info

获取所有iOS设备信息

在上面的AtxiOS类中定义如下方法用于获取所有iOS设备信息。

1
2
3
4
5
6
7
8
9
def get_all_iOS_devices(self):
"""
获取所有iOS设备
:return:
"""
url = urljoin(self.atx_url, DEVICES_URL)
info = self._get_response(url=url)
pprint(info)
return info

然后调用定义好的方法

1
2
3
4
5
6
7
ATX_URL ='http://172.28.xx.xx:4000'
Token='xxxxxxxxx'
udid='2cced5ac6ad06c35e067460dad5ff417f264b8ae' #iphone7

atx=AtxiOS(ATX_URL,Token)
atx.get_all_iOS_devices() #获取所有设备信息

调用之后返回结果如下:

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
{'count': 4,
'devices': [{'colding': False,
'createdAt': '2019-06-11T16:57:17.371000',
'owner': '',
'platform': 'apple',
'present': False,
'properties': {'brand': 'Apple',
'name': '',
'product': 'Unknown'},
'udid': 'ece81864b51f3e0535bf86e9ab24f9209e9727f0',
'updatedAt': '2019-06-11T16:58:33.049000'},
{'colding': False,
'createdAt': '2019-06-11T10:17:43.340000',
'owner': '',
'platform': 'apple',
'present': False,
'properties': {'brand': 'Apple',
'name': '',
'product': 'Unknown'},
'udid': '6dd5e55bf4204cd5ad8042dc4ecb2b703bbdb435',
'updatedAt': '2019-06-11T10:21:28.894000',
'using': False},
{'colding': False,
'createdAt': '2019-06-06T08:54:42.115000',
'idleTimeout': 600,
'lastActivatedAt': '2019-06-10T09:12:46.465000',
'owner': '',
'platform': 'apple',
'present': False,
'properties': {'brand': 'Apple',
'ip': '172.28.212.16',
'name': 'iPhone6',
'product': 'iPhone 6',
'propertyId': '1908090114',
'sdkVersion': '12.2',
'version': '12.1.4'},
'udid': 'fa00fcc158dadb6bd1cc7c618540ebd7c3723520',
'updatedAt': '2019-06-10T10:13:48.662000',
'userId': None,
'using': False,
'usingBeganAt': '2019-06-10T09:02:10.333000',
'usingDuration': 1041.401},
{'colding': False,
'createdAt': '2019-05-30T21:39:47.708000',
'idleTimeout': 600,
'lastActivatedAt': '2019-06-11T16:53:32.250000',
'owner': '',
'platform': 'apple',
'present': True,
'properties': {'brand': 'Apple',
'ip': '172.28.213.231',
'name': 'iPhone 7',
'product': 'iPhone 7',
'propertyId': '1908090112',
'sdkVersion': '12.2',
'version': '12.1.4'},
'udid': '2cced5ac6ad06c35e067460dad5ff417f264b8ae',
'updatedAt': '2019-06-11T16:53:56.737000',
'userId': None,
'using': False,
'usingBeganAt': '2019-06-11T16:53:32.250000',
'usingDuration': 8877.971}],
'success': True}

几个比较重要的字段说明

* platform目前有两个值android和apple
* present代表设备是否在线
* colding代表设备是否正在清理或者自检中, 此时是不能占用设备的
* using代表设备是否有人正在使用
* userId代表使用者的ID,这里的ID其实就是Email
* properties代表设备的一些状态信息,基本都是静态信息

获取单个设备信息

如果我们想查看单个设备的信息,可以封装如下方法来查看。

1
2
3
4
5
6
7
8
9
10
11
def get_iOS_device_by_udid(self, udid):
"""
获取单个设备信息
:param udid:str 设备的 udid 字段
:return:
"""
left_url = "/".join([USER_DEVICES_URL, udid])
url = urljoin(self.atx_url, left_url)
info = self._get_response(url=url)
# pprint(info)
return info

调用方法如下:

1
2
3
4
5
6
7
ATX_URL ='http://172.28.xx.xx:4000'
Token='xxxxxxx'
udid='2cced5ac6ad06c35e067460dad5ff417f264b8ae' #iphone7

atx=AtxiOS(ATX_URL,Token)
device_info=atx.get_iOS_device_by_udid(udid) #获取单个设备信息
pprint(device_info)

返回内容如下:

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
{'device': {'colding': False,
'createdAt': '2019-05-30T21:39:47.708000',
'idleTimeout': 600,
'lastActivatedAt': '2019-06-11T16:53:32.250000',
'owner': '',
'platform': 'apple',
'properties': {'brand': 'Apple',
'ip': '172.28.213.231',
'name': 'iPhone 7',
'product': 'iPhone 7',
'propertyId': '1908090112',
'sdkVersion': '12.2',
'version': '12.1.4'},
'source': {'id': '0681c592-8bf4-11e9-afd1-0242ac120002',
'name': 'pyclient',
'owner': 'nobody@nobody.io',
'priority': 2,
'secret': '',
'url': 'http://172.28.xx.xx:3600',
'wdaUrl': 'http://172.28.xx.xx:20023'},
'sources': {'0681c592-8bf4-11e9-afd1-0242ac120002': {'id': '0681c592-8bf4-11e9-afd1-0242ac120002',
'name': 'pyclient',
'owner': 'nobody@nobody.io',
'priority': 2,
'secret': '',
'url': 'http://172.28.xx.xx:3600',
'wdaUrl': 'http://172.28.xx.xx:20023'}},
'udid': '2cced5ac6ad06c35e067460dad5ff417f264b8ae',
'updatedAt': '2019-06-11T16:53:56.737000',
'userId': None,
'using': False,
'usingBeganAt': '2019-06-11T16:53:32.250000',
'usingDuration': 8877.971},
'success': True}

获取设备远程WDA地址

每台挂载在ATX2平台的iOS设备都有一个唯一的WDA地址,我们可以根据这个地址来远程调用该设备,一般地址格式为:ATX地址IP+端口号

1
2
3
4
5
6
7
8
9
10
def get_wda_url(self,udid):
'''
获取iOS设备远程wda链接
:param udid: 设备udid
:return:
'''
device_info =self.get_iOS_device_by_udid(udid)
wda_url = device_info['device']['source']['wdaUrl']
logging.info('wda_url is %s' %wda_url)
return wda_url

调用方法

1
2
3
4
5
6
ATX_URL ='http://172.28.xx.xx:4000'
Token='a0344xxxxx45e92a396b0530'
udid='2cced5ac6ad06c35e067460dad5ff417f264b8ae' #iphone7

atx=AtxiOS(ATX_URL,Token)
atx.get_wda_url(udid) #获取设备的远程url地址

返回结果

1
http://172.28.xx.xx:200000

设备占用

1
2
3
4
5
6
7
8
9
10
11
12
def using_iOS_device(self, udid):
"""
占用一台设备
:param udid: str 设备的 udid 字段
:return:
"""
url = urljoin(self.atx_url, USER_DEVICES_URL)
data = {"udid": udid}
headers = self.headers
headers["Content-Type"] = "application/json"
info = self._get_response(url, "POST", headers, json.dumps(data))
logging.info(info)

调用方法

1
2
3
4
5
6
ATX_URL ='http://172.28.xx.xx:4000'
Token='a0344xxxxx45e92a396b0530'
udid='2cced5ac6ad06c35e067460dad5ff417f264b8ae' #iphone7

atx=AtxiOS(ATX_URL,Token)
atx.using_iOS_device(udid) #占用设备

调用方法之后会有三种情况,设备占用成功或失败,返回内容如下:

1
2
3
{'description': 'Device successfully added', 'success': True} #设备占用成功
{'description': 'Device add failed: device busy', 'success': False} #设备被其他人占用
{'success': False, 'description': 'Device add failed: device not exist'} #udid值错误

设备释放

使用完设备之后我们需要释放设备,定义方法如下:

1
2
3
4
5
6
7
8
9
10
11
def release_iOS_device(self, udid):
"""
释放iOS设备
:param udid:str 设备的 udid 字段
:return:
"""

left_url = "/".join([USER_DEVICES_URL, udid])
url = urljoin(self.atx_url, left_url)
info = self._get_response(url, "DELETE")
logging.info(info)

调用方法如下:

1
2
3
4
5
6
ATX_URL ='http://172.28.xx.xx:4000'
Token='a0344xxxxx45e92a396b0530'
udid='2cced5ac6ad06c35e067460dad5ff417f264b8ae' #iphone7

atx=AtxiOS(ATX_URL,Token)
atx.release_iOS_device(udid) #释放设备

设备释放成功时返回内容如下:

1
{'success': True, 'description': 'Device successfully released'}

注意:释放接口可以释放非当前账户占用的设备,释放时一定要确认udid是否为自己占用的设备,避免影响他人正常使用!

iOS 真机安装 WebDriverAgent

本文根据 ATX 文档 - iOS 真机如何安装 WebDriverAgent 进行实践
参考资料
https://github.com/openatx/facebook-wda
https://github.com/NetEaseGame/ATX
https://github.com/facebook/WebDriverAgent
https://github.com/appium/WebDriverAgent

准备工作

注意:尽量升级Xcode到最新版本,iphone版本大于9.3

  • Xcode版本:10.2.1
  • iPhone版本:12.2
  • OS版本:10.14.4

实践开始

  • 新建一个目录,从 git 下载 WDA 项目代码到本地
    git clone https://github.com/facebook/WebDriverAgent

  • 运行初始化脚本

    1
    2
    3
    4
    #进入代码目录
    cd xx/xx/WebDriverAgent-master
    #执行初始化脚本
    ./Scripts/bootstrap.sh

  • 根据报错通过 brew 安装以下依赖

    1. carthage依赖:brew install carthage
    2. npm:brew install npm

证书设置

  1. 进入项目路径,双击打开WebDriverAgent.xcodeproj,打开xcode后按照下方数字序号依次点击,勾选第5项目,弹框内点击Enable Automatic。

  2. Team 栏添加帐户(个人账号和开发者账号兼可)选中
    -w1059

  1. 接着在TARGETS里面选中WebDriverAgentRunner,用同样的方法设置好证书
    -w1139

  2. 个人免费版的证书,还需要修改下WebDriverAgent的BundleID,随便加后缀,不要跟别人重复就好

运行和测试

-w784

  • 点击方框选项,分别选择WebDriverAgentRunner和目标设备

  • 运行 Product -> Test
    一切正常的话,手机上会出现一个无图标的WebDriverAgent应用,启动之后,马上又返回到桌面。

  • 个人免费证书第一次运行需要信任证书
    日志信息如下:

    Verify the Developer App certificate for your account is trusted on your device. Open Settings on Vic小叶子 and navigate to General -> Device Management, then select your Developer App certificate to trust it.

原因:开发者人员程序未受信任
解决:进入手机设置-> 通用-> 描述文件与设备管理-> 点击开发者应用,信任一下就好了。

此时控制台界面可以看到设备的IP。如果看不到的话,使用这种方法打开
-w607
-w1248

  • 通过上面给出的IP和端口,加上/status合成一个url地址。例如http://10.0.0.1:8100/status,然后浏览器打开。如果出现一串JSON输出,说明WDA安装成功了。

    实际情况,我到这一步访问这个地址无响应:
    原因:部分国行的iphone机器通过IP和端口还不能访问
    解决:需要将手机的端口转发到Mac上(端口转发见下文)

端口转发

有些国产的iPhone机器通过手机的IP和端口还不能访问,此时需要将手机的端口转发到Mac上。

  • 安装libimobiledevice
1
2
3
4
5
6
7
brew update
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd
brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
  • 转发端口
    iproxy 8100 8100 UDID
    使用iproxy –help 可以查到更具体的用法。

  • 查询UDID
    instruments -s

这时通过访问http://localhost:8100/status,如果出现一串JSON输出,说明WDA安装成功了。

inspector

是用来查看UI的图层,方便编写测试脚本
inspector的地址是:http://localhost:8100/inspector
-w1673

使用终端替代Xcode

通常来说为了持续集成,能够全部自动化比较好一些
启动 WebDriverAgent,官方提供了四种方式:

  1. Xcode
  2. xcodebuild
  3. Using fbsimctl from FBSimulatorControl framework
  4. Using FBSimulatorControl framework directly
1
2
3
4
5
6
7
8
9
# 解锁keychain,以便可以正常的签名应用,
PASSWORD="replace-with-your-password"
security unlock-keychain -p $PASSWORD ~/Library/Keychains/login.keychain

# 获取设备的UDID
UDID=$(idevice_id -l | head -n1)

# 运行测试
xcodebuild -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -destination "id=$UDID" test

[TOC]

前言

前面分享了STF Andriod设备远程管理平台的搭建和使用,但是STF不支持iOS设备连接,虽然现在社区有了STF集成iOS设备的开源方案,但是经过实践不太稳定,后面再Testerhome社区发现了一个新的框架ATX2同时支持Android和iOS设备远程管理,经过实践设备连接非常稳定,所以这里分享下这个平台的搭建和使用

ATX2简介

ATX2是一款可以远程控制Android和iOS设备的设备管理平台。该平台使用的技术栈为:Python3+NodeJS+RethinkDB 项目目前已经开源,作者是阿里的一位大牛:codeskyblue

环境准备

系统环境:Mac Os 10.14.6(因为要使用iOS设备连接WDA,所以需要Mac Os环境)

python3安装

1
brew install python3

rethinkdb安装

1
brew install  rethinkdb

服务部署

启动ATX2服务有2种方式:

  • Docker部署
  • 源码部署

Docker部署

  • 使用Docker部署需要安装Docker,具体安装步骤见:Docker安装
  • 代码Clone到本地
1
git clone https://github.com/openatx/atxserver2
  • 打开命令终端,切换到代码目录,执行一条命令即可
1
docker-compose up

源码部署

先准备好一个rethinkdb服务器,并启动

1
rethinkdb

代码clone到本地

1
git clone git@github.com:openatx/atxserver2.git

打开命令终端,切换到代码目录,然后执行命令下面的命令安装依赖

1
pip3 install -r requirements.txt

启动服务,命令如下

1
2
3
4
5
6
7
8
#默认方式启动,使用默认的登录方式,默认端口
python3 main.py

# 指定认证方式
python3 main.py --auth simple #默认是一个非常simple的认证,输入邮箱就可以

# 设置监听端口
python3 main.py --port 4000 # 默认监听4000端口

打开页面

启动之后,浏览器打开http://localhost(本机ip也可以):4000,登录之后就可以顺利的看到设备列表页了,由于还没有接入设备所以设备列表为空。
框架图

Android设备接入atxserver2-android-provider

依赖 Python3.6+, NodeJS 8, Git-LFS
NodeJS版本太高了也不行,一定要NodeJS 8,推荐使用nvm管理node版本

Install git-lfs

1
brew install git-lfs

Clone代码到本地,切换到代码目录

1
2
3
4
git clone https://github.com/openatx/atxserver2-android-provider
cd atxserver2-android-provider
git lfs install
git lfs pull

安装依赖

1
2
npm install
pip install -r requirements.txt

启动,需要指定atxserver2的地址, 假设地址为 http://localhost:4000

1
python3 main.py --server localhost:4000

Provider可以通过adb track-devices自动发现已经接入的设备,当手机接入到电脑上时,会自动给手机安装minicap, minitouch, atx-agent, app-uiautomator-[test].apk, whatsinput-apk

接入的设备需要配置好开发者选项, 不同设备的设置方案放到了该项目的Issue中, tag: device-settings 如果没有该机型,可以自助添加

iOS设备接入atxserver2-ios-provider

依赖 Python3.6+, NodeJS 8, WebDriverAgent(appium)

安装libimobiledevice工具包

1
2
3
4
5
6
7
8
9
10
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd

brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd

brew install --HEAD libimobiledevice
brew install ideviceinstaller
brew link --overwrite ideviceinstaller

安装atxserver2-ios-provider,

1
2
3
4
git clone https://github.com/openatx/atxserver2-ios-provider --recursive
cd atxserver2-ios-provider
pip3 install -r requirements.txt
npm install

初始化其中的ATX-WebDriverAgent

1
2
3
4
git clone https://github.com/appium/WebDriverAgent
cd ATX-WebDriverAgent
brew install carthage
./Scripts/bootstrap.sh

后找台手机接到苹果电脑上。 按照这个文档https://testerhome.com/topics/7220 对WebDriverAgent项目进行下设置

启动

1
python3 main.py

连接成功后,如下图所示可以看到连接的iOS设备,点击立即使用即可远程控制。
示例图

Ruby环境搭建

查看当前Ruby版本

1
ruby -v

查看rvm版本

1
rvm -v 

显示如下(或者是其他版本)

1
rvm 1.29.3 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]

列出ruby可安装的版本信息

1
rvm list known

显示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# MRI Rubies
[ruby-]1.8.6[-p420]
[ruby-]1.8.7[-head] # security released on head
[ruby-]1.9.1[-p431]
[ruby-]1.9.2[-p330]
[ruby-]1.9.3[-p551]
[ruby-]2.0.0[-p648]
[ruby-]2.1[.10]
[ruby-]2.2[.10]
[ruby-]2.3[.7]
[ruby-]2.4[.4]
[ruby-]2.5[.1] // 重点在这里 重点在这里 重点在这里
[ruby-]2.6[.0-preview2] // 测试版
ruby-head
.....

安装一个ruby版本(这里我选择的是2.5.1版本,当然你也可以选择其他的)

1
2
3
rvm install 2.5.1
// 注意:安装过程中需要两次按下 Enter 键, 第二次按下后需要输入电脑访问密码(不可见,只管输入就行);
// 如果你电脑没有安装Xcode和Command Line Tools for Xcode以及Homebrew 会自动下载安装,建议提前安装这三者.

这里很多小伙伴会遇到错误,大部分是因为没有安装Homebrew造成,所以所以所以要提前安装比较好

1
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

设置为默认版本

1
rvm use 2.5.1 --default

更换源

1
2
3
4
sudo gem update --system
gem sources --remove https://rubygems.org/
gem sources --add https://gems.ruby-china.com/

为了验证你的Ruby镜像是并且仅是ruby-china,执行以下命令查看

1
gem sources -l

如果是以下结果说明正确,如果有其他的请自行百度解决

1
2
*** CURRENT SOURCES ***
https://gems.ruby-china.com/

开始安装CocoaPods

1
sudo gem install -n /usr/local/bin cocoapods

如果安装了多个Xcode使用下面的命令选择(一般需要选择最近的Xcode版本)

1
sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

安装本地库

1
pod setup

执行以上命令后

1
2
3
4
5
6
Setting up CocoaPods master repo
$ /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
Cloning into 'master'...
remote: Counting objects: 1879515, done.
remote: Compressing objects: 100% (321/321), done.
Receiving objects: 21% (404525/1879515), 73.70 MiB | 22.00 KiB/

然后就是漫长的等待,当然,网络好的情况下会更快

如果一直安装不成功请参考这里

查看你是否安裝成功

1
pod

显示

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
Usage:

$ pod COMMAND

CocoaPods, the Cocoa library package manager.

Commands:

+ cache Manipulate the CocoaPods cache
+ deintegrate Deintegrate CocoaPods from your project
+ env Display pod environment
+ init Generate a Podfile for the current directory
+ install Install project dependencies according to versions from a
Podfile.lock
+ ipc Inter-process communication
+ lib Develop pods
+ list List pods
+ outdated Show outdated project dependencies
+ plugins Show available CocoaPods plugins
+ repo Manage spec-repositories
+ search Search for pods
+ setup Setup the CocoaPods environment
+ spec Manage pod specs
+ trunk Interact with the CocoaPods API (e.g. publishing new specs)
+ try Try a Pod!
+ update Update outdated project dependencies and create new
Podfile.lock

Options:

--silent Show nothing
--version Show the version of the tool
--verbose Show more debugging information
--no-ansi Show output without ANSI codes
--help Show help banner of specified command

CocoaPods版本

1
pod --version

安装brew

终端上运行

1
/usr/bin/ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)

安装NVM

brew install nvm

安装完成之后打开shell的配置文件

1
2
cd ~
vim .bash_profile

在文件里添加以下命令

1
2
export NVM_DIR=~/.nvm
source $(brew --prefix nvm)/nvm.sh

然后重新source

1
source .bash_profile

使用nvm安装node

1
2
3
4
nvm ls-remote 查看 所有的node可用版本
nvm install xxx 下载你想要的版本
nvm use xxx 使用指定版本的node
nvm alias default xxx 每次启动终端都使用该版本的node

前言

STF(Smartphone Test Farm)是一个Web应用程序,用于从浏览器中远程调试智能手机,智能手表和其他小工具。
openstf GitHub开源地址

功能:

  1. 支持的系统:Android
    • 版本限制:2.3.3 (SDK 版本 10) to 9.0 (SDK 版本 28)
      
    • Android Wear 5.1(由于权限问题不支持5.0)
      
    • Fire OS, CyanogenMod,基于Android的发行版
      
    • 当前任何功能都不需要root
      
  2. 从浏览器远程控制任何设备
  3. 管理所有挂机设备

环境搭建

需要使用到的工具或环境

  • Node.js >= 6.9 (首选最新稳定版)
  • ADB
  • RethinkDB >= 2.2
  • GraphicsMagick (用来调整屏幕截图的大小)
  • ZeroMQ 自带安装,无需另行安装
  • Protocol Buffers 自带安装,无需另行安装
  • yasm 自带安装,无需另行安装 (为了编译 libjpeg-turbo)
  • pkg-config 安装后Node.js就可以找到自带库

通过brew安装依赖

在终端使用输入命令安装所需的依赖:

1
brew install rethinkdb graphicsmagick zeromq protobuf yasm pkg-config

若提示找不到brew,则需要先安装brew,参照:
Homebrew

Node.js安装

Node.js版本强烈建议选择8.X 版本,如果是安装了最新10.X及以上版本后续安装stf过程中可能会出现报错,参考Mac终端安装指定版本node

1
2
3
4
node -v
v8.16.0
npm -v
6.4.1

由于网络限制,为了保障后续安装顺利,这里我们建议增加 npm淘宝镜像。

1
npm install -g cnpm --registry=https://registry.npm.taobao.org

adb安装

使用brew安装,命令如下:

1
brew cask install android-platform-tools

安装完成后,连上Android 设备(开启usb-debug)输入命令adb –version查看是否安装成功。

1
2
3
4
adb --version
Android Debug Bridge version 1.0.41
Version 29.0.1-5644136
Installed as /usr/local/bin/adb

stf安装

这里我们使用上面安装的cnpm来安装

1
npm install -g stf

安装完成后使用命令stf doctor检查相关依赖是否安装正常。

1
2
3
4
5
6
7
8
9
10
stfs-Mac-mini:~ stf$ stf doctor
2019-08-01T05:17:19.556Z INF/cli:doctor 58578 [*] OS Arch: x64
2019-08-01T05:17:19.558Z INF/cli:doctor 58578 [*] OS Platform: darwin
2019-08-01T05:17:19.558Z INF/cli:doctor 58578 [*] OS Platform: 18.0.0
2019-08-01T05:17:19.558Z INF/cli:doctor 58578 [*] Using Node 8.16.0
2019-08-01T05:17:19.572Z INF/cli:doctor 58578 [*] Using ZeroMQ 4.3.2
2019-08-01T05:17:19.581Z INF/cli:doctor 58578 [*] Using GraphicsMagick 1.3.33
2019-08-01T05:17:19.582Z INF/cli:doctor 58578 [*] Using ADB 1.0.41
2019-08-01T05:17:19.587Z INF/cli:doctor 58578 [*] Using RethinkDB 2.3.6
2019-08-01T05:17:19.589Z INF/cli:doctor 58578 [*] Using ProtoBuf 3.7.1

如果安装过程中出现如下错误,说明Node版本不兼容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
make: *** [Release/obj.target/bufferutil/src/bufferutil.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack at ChildProcess.onExit (/usr/local/lib/node_modules/cnpm/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack at ChildProcess.emit (events.js:198:13)
gyp ERR! stack at Process.ChildProcess._handle.onexit (internal/child_process.js:248:12)
gyp ERR! System Darwin 18.0.0
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/cnpm/node_modules/npminstall/node-gyp-bin/node-gyp.js" "rebuild"
gyp ERR! cwd /usr/local/lib/node_modules/stf/node_modules/_bufferutil@1.3.0@bufferutil
gyp ERR! node -v v10.16.1
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
[npminstall:runscript:error] bufferutil@^1.2.1 has binding.gyp file, run "node-gyp rebuild" error: RunScriptError: Run "sh -c node-gyp rebuild" error, exit code 1
Install fail! RunScriptError: Run "sh -c node-gyp rebuild" error, exit code 1
RunScriptError: Run "sh -c node-gyp rebuild" error, exit code 1
at ChildProcess.proc.on.code (/usr/local/lib/node_modules/cnpm/node_modules/runscript/index.js:96:21)
at ChildProcess.emit (events.js:198:13)
at maybeClose (internal/child_process.js:982:16)
at Process.ChildProcess._handle.onexit (internal/child_process.js:259:5)
npminstall version: 3.22.1
npminstall args: /usr/local/bin/node /usr/local/lib/node_modules/cnpm/node_modules/npminstall/bin/install.js --fix-bug-versions --china --userconfig=/Users/stf/.cnpmrc --disturl=https://npm.taobao.org/mirrors/node --registry=https://r.npm.taobao.org -g stf --unsafe-perm

解决方案:使用如下命令卸载Node,然后安装8.X版本的Node。

启动服务

启动rethinkdb

stf使用的是RethinkDB数据库,所以在启动stf前需要先启动RethinkDB,启动RethinkDB很简单,只需要执行以下命令:

1
rethinkdb

因为默认端口是8080,我已经有其他服务被占用,所以另开一个端口

1
rethinkdb --bind all --cache-size 8192 --http-port 9090

–cache-size指定缓存大小
–http-port 指定端口

启动stf

另开一个终端,执行

1
stf local

如果想让其他机器连接到stf需要加上 –public-ip

1
stf local --public-ip <本机的IP地址>

最后输入http://localhost:7100或者 http://<ip地址>:7100即可访问stf地址,界面如下:
stf-login

输入用户名和邮箱即可登录,用户名和邮箱可以随便填写。
USB链接Android设备,开发者模式打开USB调试,就可以看到设备了

hexo-blog-encrypt插件GitHub官网

安装

博客根目录下运行以下命令

1
npm install --save hexo-blog-encrypt

设置

首先在 站点配置文件 中启用该插件:

1
2
encrypt:
enable: true

##然后在你的文章的头部添加上对应的字段,如 password, abstract, message

1
2
3
4
5
6
7
8
9
10
11
12
---
title: 文章加密
date: 2019-01-04T22:20:13.000Z
category: 教程
tags:
- 博客
- Hexo
keywords: 博客文章密码
password: TloveY
abstract: 密码:TloveY
message: 输入密码,查看文章
---
  • password: 是该博客加密使用的密码
  • abstract: 是该博客的摘要,会显示在博客的列表页
  • message: 这个是博客查看时,密码输入框上面的描述性文字

如果你想对 TOC 也进行加密,则在 article.ejs 中将 TOC 的生成代码修改成如下:

1
2
3
4
5
6
7
8
9
10
11
<% if(post.toc == true){ %>
<div id="toc-div" class="toc-article" <% if (post.encrypt == true) { %>style="display:none" <% } %>>
<strong class="toc-title">Index</strong>
<% if (post.encrypt == true) { %>
<%- toc(post.origin) %>
<% } else { %>
<%- toc(post.content) %>
<% } %>
</div>
<% } %>
<%- post.content %>

自定义

如果你对默认的主题不满意,或者希望修改默认的提示和摘要内容,你可以添加如下配置在 站点配置文件 中。

1
2
3
4
encrypt:
enable: true
default_abstract: 这是一篇加密文章,内容可能是个人情感宣泄或者收费技术。如果你确实想看,请与我联系。
default_message: 输入密码,查看文章。

这样,对于每一篇需要加密的文章就不必都在在头部添加 abstract 和 message 字段了,脚本会自动添加默认的内容填充。

如果你希望对某一篇特定的文章做特殊处理(如本文的 abstract ,可以在对应文章的头部添加

1
2
3
4
5
6
7
8
9
10
11
---
title: 文章加密
date: 2019-01-04T22:20:13.000Z
category: 教程
tags:
- 博客
- Hexo
keywords: 博客文章密码
password: TloveY
abstract: 密码:TloveY
---

此时,博客头部的 abstract 会覆盖 站点配置文件 的 default_abstract 实现自定义。

存在问题

  • 如果你开启了 字数统计功能 的话,那么本文的字数会显得比实际值大。
  • 加密文章内部分脚本会失效,已知 代码复制 失效。

安装插件

1
npm install --save hexo-helper-live2d

复制你喜欢的模型名字:hijiki

Epsilon2.1

Epsilon2.1
![Gantzert_Felixande](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/Gantzert_Felixander.gif)
Gantzert_Felixande
![haru](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/haru.gif)
haru
![haruto](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/haruto.gif)
haruto
![hibiki](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/hibiki.gif)
hibiki
![hijiki](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/hijiki.gif)
hijiki
![koharu](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/koharu.gif)
koharu
![miku](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/miku.gif)
miku
![ni-j](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/ni-j.gif)
ni-j
![nico](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/nico.gif)
nico
![nietzche](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/nietzche.gif)
nietzche
![nipsilon](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/nipsilon.gif)
nipsilon
![nito](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/nito.gif)
nito
![shizuku](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/shizuku.gif)
shizuku
![tororo](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/tororo.gif)
tororo
![tsumiki](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/tsumiki.gif)
tsumiki
![Unitychan](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/Unitychan.gif)
Unitychan
![wanko](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/wanko.gif)
wanko
![z16](/2019/05/26/Hexo%E4%B9%8B%E6%B7%BB%E5%8A%A0%E8%90%8C%E8%90%8C%E5%93%92/z16.gif)
z16

将以下代码添加到主题配置文件_config.yml,修改<你喜欢的模型名字>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
live2d:
enable: true
scriptFrom: local
pluginRootPath: live2dw/
pluginJsPath: lib/
pluginModelPath: assets/
tagMode: false
log: false
model:
use: live2d-widget-model-<你喜欢的模型名字>
display:
position: right
width: 150
height: 300
mobile:
show: true

建配置文件

  • 在站点目录下建文件夹live2d_models,
  • 再在live2d_models下建文件夹<你喜欢的模型名字>,
  • 再在<你喜欢的模型名字>下建json文件:<你喜欢的模型名字>.model.json

安装模型。在命令行(即Git Bash)运行以下命令即可:

1
npm install –save live2d-widget-model-<你喜欢的模型名字>

前言

为了提高博客代码块的用户体验,仅仅代码高亮还不行,最好还能一键复制代码。故此文将讲述Hexo NexT主题博客的代码块复制功能配置。

下载clipboard.js

三方插件 clipboardjs ,相关介绍和兼容性我就不赘述了,去它主页github上看。

下载地址:

clipboardjs 使用

\themes\next\source\js\src目录下,创建clipboard-use.js文件,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*页面载入完成后,创建复制按钮*/
!function (e, t, a) {
/* code */
var initCopyCode = function(){
var copyHtml = '';
copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
copyHtml += ' <i class="fa fa-globe"></i><span>copy</span>';
copyHtml += '</button>';
$(".highlight .code pre").before(copyHtml);
new ClipboardJS('.btn-copy', {
target: function(trigger) {
return trigger.nextElementSibling;
}
});
}
initCopyCode();
}(window, document);

.\themes\next\source\css\_custom\custom.styl文件中添加下面代码:

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
//代码块复制按钮
.highlight{
//方便copy代码按钮(btn-copy)的定位
position: relative;
}
.btn-copy {
display: inline-block;
cursor: pointer;
background-color: #eee;
background-image: linear-gradient(#fcfcfc,#eee);
border: 1px solid #d5d5d5;
border-radius: 3px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-appearance: none;
font-size: 13px;
font-weight: 700;
line-height: 20px;
color: #333;
-webkit-transition: opacity .3s ease-in-out;
-o-transition: opacity .3s ease-in-out;
transition: opacity .3s ease-in-out;
padding: 2px 6px;
position: absolute;
right: 5px;
top: 5px;
opacity: 0;
}
.btn-copy span {
margin-left: 5px;
}
.highlight:hover .btn-copy{
opacity: 1;
}

引用

.\themes\next\layout\_layout.swig文件中,添加引用(注:在 swig 末尾或 body 结束标签()之前添加):

1
2
3
<!-- 代码块复制功能 -->
<script type="text/javascript" src="/js/clipboard.min.js"></script>
<script type="text/javascript" src="/js/clipboard-use.js"></script>

添加位置示例

前言

有了前面几篇博客的介绍,我们就可以很容易的搭建我们的博客了,不过既然是属于自己的博客网站,自然也就想让其更加美观,更有意思,所以呢我下面介绍一下Hexo博客的主题美化操作。

Hexo博客支持很多主题风格,其中Next主题是Github上Star最多的主题,其一直在更新维护,支持非常多的外部插件和功能选项。我目前使用的是NexT.Gemini v7.1.1版本,下面我会介绍基于Next7主题的界面美化。

命名

在Hexo中有2份主要的配置文件,其名称都是_config.yml。 其中,一份位于博客根目录下,主要包含 Hexo 本身的配置;另一份位于themes/next/目录下,用于配置主题相关的选项。

  • 博客配置文件:博客根目录下_config.yml
  • 主题配置文件:themes/next/目录下_config.yml

基础设置

##设置站点名、作者昵称和站点描述等内容
打开博客配置文件,博客根目录下_config.yml

1
2
3
4
5
6
7
8
# Site
title: Vicの博客
subtitle: 测试攻城狮
description: 人生苦短,我学Python
keywords:
author: Vic
language: zh-CN
timezone: Asia/Shanghai

Next主题的安装配置

Next主题的安装方式很简单,只需要在博客根目录下执行:

1
git clone https://github.com/theme-next/hexo-theme-next themes/next

然后设置博客配置文件_config.yml:(博客根目录下的_config.yml)

1
theme: next

即可将我们的Hexo博客主题替换为Next主题。

主题配置

Next主题风格

打开themes/next/下的_config.yml,查找scheme,可以看到如下四种不同的风格方案:

1
2
3
4
#scheme: Muse
#scheme: Mist
#scheme: Pisces
scheme: Gemini

去掉#注释,即启用对应的scheme,博主采用Gemini主题。

设置菜单及对应页面

  • 打开themes/next/下的_config.yml,查找menu

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    menu:
    home: / || home
    about: /about/ || user
    tags: /tags/ || tags
    categories: /categories/ || th
    archives: /archives/ || archive
    #schedule: /schedule/ || calendar
    #sitemap: /sitemap.xml || sitemap
    #commonweal: /404/ || heartbeat
    favorite: /favorite/ || fa-star

    去掉#注释即可显示对应的菜单项,也可自定义新的菜单项。||之前的值是目标链接,之后的是分类页面的图标,图标名称来自于FontAwesome icon。若没有配置图标,默认会使用问号图标。
    例如上面添加:favorite: /favorite/ || fa-star

  • 新建一个页面,命名为 categories

    1
    hexo new page "favorite"

    此时在根目录的source文件夹下会生成一个favorite文件夹,文件中有一个index.md文件,文件内容修改增加 type 属性

    1
    2
    3
    4
    5
    ---
    title: Favorite
    date: 2019-05-20 19:11:41
    type: "favorite"
    ---
  • 新添加的菜单需要翻译对应的中文
    打开hexo/theme/next/languages/zh-CN.yml,在menu下自定义,如:

    1
    2
    menu:
    favorite: 收藏夹

一般配置

  • Next主题一般配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    override:false #表示是否将主题置为默认样式
    cache:
    enable:true #表示添加缓存功能,这样浏览器后续打开我们的博客网站会更快
    menu: #设置博客各个页面的相对路径,默认根路径是blog/source
    home: / || home
    about: /about/ || user
    tags: /tags/ || tags
    categories: /categories/ || th
    archives: /archives/ || archive
    #schedule: /schedule/ || calendar #日历
    #sitemap: /sitemap.xml || sitemap #站点地图,供搜索引擎爬取
    #commonweal: /404/ || heartbeat # 腾讯公益404

    # Enable/Disable menu icons / item badges.
    menu_settings:
    icons: true # 是否显示各个页面的图标
    badges: true # 是否显示分类/标签/归档页的内容量
    # Schemes
    scheme: Gemini
  • 站点配置MyBlog/_config.yml文件的基本配置为

    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
    # Hexo Configuration
    ## Docs: https://hexo.io/docs/configuration.html
    ## Source: https://github.com/hexojs/hexo/

    # Site
    title: Vicの博客 # 网站标题
    subtitle: 测试攻城狮 # 网站子标题
    description: 人生苦短,我学Python # 网站描述
    keywords:
    author: Vic # 网站作者,也就是您的名字
    language: zh-CN # 网站使用的语言
    timezone: Asia/Shanghai # 网站时区。Hexo 预设使用您电脑的时区。

    # URL
    ## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
    url: https://vic.kim
    root: /
    permalink: :year/:month/:day/:title/
    permalink_defaults:

    # Directory
    source_dir: source # 资源文件夹,这个文件夹用来存放内容,例如我们用markdown编写的博文
    public_dir: public # 公共文件夹,这个文件夹用于存放生成的静态博客文件。
    tag_dir: tags # 标签文件夹
    archive_dir: archives # 归档文件夹
    category_dir: categories # 分类文件夹
    code_dir: downloads/code # Include code 文件夹
    i18n_dir: :lang # 国际化(i18n)文件夹
    skip_render: # 跳过指定文件的渲染,您可使用 glob 来配置路径。

    # Search
    search:
    path: search.xml # 索引文件的路径,相对于站点根目录
    field: post # 搜索范围,默认是 post,还可以选择 page、all,设置成 all 表示搜索所有页面
    format: html
    limit: 10000 # 限制搜索的条目数

    # Writing
    new_post_name: :title.md # 默认的新博文名称
    default_layout: post # 默认布局
    titlecase: false # 把标题转换为 titlecase(titlecase指的是将每个单词首字母转换成大写)
    external_link: true # 在新标签中打开链接
    filename_case: 0 #把文件名称转换为 (1) 小写或 (2) 大写, 0表示不变
    render_drafts: false # 是否显示草稿
    post_asset_folder: true #是否启用资源文件夹(用来存放相对路径图片或文件)
    relative_link: false # 把链接改为与根目录的相对位址
    future: true
    highlight:
    enable: true #是否开启代码高亮
    line_number: false #是否增加代码行号
    auto_detect: false #自动判断代码语言
    tab_replace:

    # Home page setting
    # path: Root path for your blogs index page. (default = '')
    # per_page: Posts displayed per page. (0 = disable pagination)
    # order_by: Posts order. (Order by date descending by default)
    index_generator:
    path: '' #博客的默认路径
    per_page: 10 #每页博客数量上限
    order_by: -date #博客排序

    # Category & Tag
    default_category: uncategorized # 默认分类, uncategorized表示未分类
    category_map: # 分类别名
    tag_map: # 标签别名

    # Date / Time format
    ## Hexo uses Moment.js to parse and display date
    ## You can customize the date format as defined in
    ## http://momentjs.com/docs/#/displaying/format/
    date_format: YYYY-MM-DD #博客日期格式
    time_format: HH:mm:ss #博客时间格式

    # Pagination
    ## Set per_page to 0 to disable pagination
    per_page: 10 # 每页显示的文章量,如果设置值为0,则表示禁止分页
    pagination_dir: page # 分页目录

    # Extensions
    ## Plugins: https://hexo.io/plugins/
    ## Themes: https://hexo.io/themes/
    theme: next #选择博客主题,名字为themes中选择的主题文件夹名称

    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    type: git
    repo: https://github.com/HappyVic/HappyVic.github.io
    branch: master

    # hexo-tag-cloud
    tag_cloud:
    textFont: Trebuchet MS, Helvetica
    textColor: '#333'
    textHeight: 25
    outlineColor: '#E2E1D1'
    maxSpeed: 0.1

美化

设置头像

找自己喜欢的图,放至themes/next/source/images/文件夹下
打开themes/next/下的_config.yml,查找Sidebar Avatar。修改url的值

1
2
3
4
# Sidebar Avatar
# in theme directory(source/images): /images/avatar.gif
# in site directory(source/uploads): /uploads/avatar.gif
url: /images/smile.jpg

url的值是图片的链接地址

网站图标设置

我们博客的默认图标是H,不过Next支持修改图标

  • 图标素材网站:easyiconiconfont
  • 下载16x16以及32x32大小的PNG格式图标,置于/themes/next/source/images/
  • 打开themes/next/下的_config.yml,查找favicon
    1
    2
    3
    4
    5
    6
    7
    favicon:
    small: /images/cat-16x16-next.png
    medium: /images/cat-32x32-next.png
    apple_touch_icon: /images/apple-touch-icon-next.png
    safari_pinned_tab: /images/logo.svg
    #android_manifest: /images/manifest.json
    #ms_browserconfig: /images/browserconfig.xml
    修改small和medium的路径为下载的图标路径

鼠标点击特效

  • 鼠标点击红心特效
    /themes/next/source/js/src下新建文件 clicklove.js ,接着把下面的代码拷贝粘贴到clicklove.js文件中:
    1
    !function(e,t,a){function n(){c(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText="left:"+d[e].x+"px;top:"+d[e].y+"px;opacity:"+d[e].alpha+";transform:scale("+d[e].scale+","+d[e].scale+") rotate(45deg);background:"+d[e].color+";z-index:99999");requestAnimationFrame(r)}function o(){var t="function"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement("div");a.className="heart",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement("style");a.type="text/css";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName("head")[0].appendChild(a)}function s(){return"rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document);
    \themes\next\layout\_layout.swig文件末尾添加:
    1
    2
    <!-- 页面点击小红心 -->
    <script type="text/javascript" src="/js/src/clicklove.js"></script>
  • 爆炸红心特效(比较花里胡哨不推荐)
    themes/next/source/js/src里面建一个fireworks.js的文件,接着把下面的代码拷贝粘贴到fireworks.js文件中:
    1
    "use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)};
    打开themes/next/layout/_layout.swig,在</body>上面写下如下代码:(注意和上面的对其)
    1
    2
    3
    4
    5
    {% if theme.fireworks %}
    <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas>
    <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>
    <script type="text/javascript" src="/js/src/fireworks.js"></script>
    {% endif %}
    打开主题配置文件,在文件末尾添加:(themes/next/下的_config.yml
    1
    2
    # Fireworks
    fireworks: true

背景动画(只能选一个)

Canvas-nest风格、JavaScript 3D library风格 动画只能选一个

  • Canvas-nest风格
    进入theme/next目录,执行以下命令
    1
    git clone https://github.com/theme-next/theme-next-canvas-nest source/lib/canvas-nest
    打开themes/next/下的_config.yml文件,搜索Canvas-nest,将canvas_nest的中enable值改为true即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Canvas-nest
    # Dependencies: https://github.com/theme-next/theme-next-canvas-nest
    canvas_nest:
    enable: true
    onmobile: true # display on mobile or not
    color: "0,0,255" # RGB values, use ',' to separate
    opacity: 0.5 # the opacity of line: 0~1
    zIndex: -1 # z-index property of the background
    count: 99 # the number of lines
  • JavaScript 3D library风格
    进入theme/next目录,执行以下命令
    1
    git clone https://github.com/theme-next/theme-next-three source/lib/three
    打开themes/next/下的_config.yml文件,搜索theme-next-three,将想要的效果改为true即可:
    1
    2
    3
    4
    5
    6
    # three_waves
    three_waves: false
    # canvas_lines
    canvas_lines: true
    # canvas_sphere
    canvas_sphere: false

设置侧栏在左侧/右侧

打开themes/next/下的_config.yml,查找sidebar

  • Pisces或Gemini方案
    打开themes/next/下的_config.yml,查找sidebar,将想要的方案打开
    1
    2
    3
    sidebar:
    position: left
    #position: right
  • Mist或Muse方案
    打开next/source/js/src/motion.js,查找paddingRight,把所有(2个)PaddingRight更改为paddingLeft即可。
    打开next/source/css/_custom/custom.styl,添加如下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    //侧边栏置于左侧
    .sidebar {
    left: 0;
    }
    //侧栏开关置于左侧
    .sidebar-toggle {
    left: $b2t-position-right;
    }
    打开next/source/css/_common/components/back-to-top.styl,将right: $b2t-position-right;改为left: $b2t-position-right;

显示侧边栏的时机

打开themes/next/下的_config.yml,查找sidebar

1
2
3
4
5
6
# Sidebar Display, available values (only for Muse | Mist):
# - post expand on posts automatically. Default.
# - always expand for all pages automatically.
# - hide expand only when click on the sidebar toggle icon.
# - remove totally remove sidebar including sidebar toggle.
display: post

文章末尾添加版权声明

打开themes/next/下的_config.yml,查找creative_commons

1
2
3
4
5
creative_commons:
license: by-nc-sa
sidebar: false
post: true # 将false改为true即可显示版权信息
language:

添加本地搜索功能

参考文章Hexo Next 主题中添加本地搜索功能

建立标签云及效果展示

参考文章Hexo博客建立标签云及效果展示

文章添加阴影、透明效果

打开theme/next/source/css/_custom/custom.styl,添加以下代码

1
2
3
4
5
6
7
8
9
// 主页文章添加阴影效果
.post {
margin-top: 60px;
margin-bottom: 60px;
padding: 25px;
background:rgba(255,255,255,0.9) none repeat scroll !important;
-webkit-box-shadow: 0 0 5px rgba(202, 203, 203, .5);
-moz-box-shadow: 0 0 5px rgba(202, 203, 204, .5);
}

设置代码块样式

在博客配置文件中,搜索highlight:

1
2
3
4
5
highlight:
enable: true #是否开启代码高亮
line_number: false #是否增加代码行号
auto_detect: true #自动判断代码语言
tab_replace:

代码块背景。打开themes/next/下的_config.yml,查找highlight_theme

1
2
3
4
# Code Highlight theme
# Available values: normal | night | night eighties | night blue | night bright
# https://github.com/chriskempson/tomorrow-theme
highlight_theme: night eighties

代码块复制功能

参考文章Hexo NexT 代码块复制功能

显示当前浏览进度

打开themes/next/下的_config.yml,查找back2top

1
2
3
4
5
6
back2top:
enable: true
# Back to top in sidebar.
sidebar: true
# Scroll percent label in b2t button.
scrollpercent: true

在右上角或者左上角实现fork me on github

前言

Hexo是一个快速、简洁且高效的博客框架,最近有搭建个人博客的想法,便动手尝试了。
官方的文档

hexo博客系统搭建

Github账号注册及仓库创建

  • 进入Github官网,注册账号
  • 注册成功点击new仓库
    -w1309
  • 创建仓库的名字必须为username.github.io,我的用户名为Vicxiaoyezi,因此我创建的仓库就是Vicxiaoyezi.github.io,这是很关键的一点,很重要。输入名字后,直接点最下面绿色的按钮“Create repository”,创建新仓库。这一部分基本完成了,接下去需要在终端操作。
    -w746

安装Node.js

  • 首先检查是否安装了node.js
    1
    node -v # 是否出现安装版本信息,出现说明已经安装了
  • 如果未安装,去Node.js官网下载相应的安装包,一路安装即可。
    1
    2
    3
    #安装完成之后打开终端,分别输入一下命令,如果出现版本信息,就说明安装成功。
    node-v
    npm -v

安装Git

Mac下安装Xcode就自带Git。

  • 首先检查是否安装了Git

    1
    git --version # 是否出现安装版本信息,出现说明已经安装了
  • 未安装通过Homebrew安装git(Homebrew的安装参考),终端命令输入

    1
    brew install git

配置SSH Key

这一步不是必须的,配置SSH Key的话之后每次更新博客就不用都输入用户名和密码,可以方便一些。
配置参考Mac下配置SSH key

安装Hexo

  • 安装hexo
    如果您的电脑中已经安装上述必备程序,那么恭喜您!接下来只需要使用 npm 即可完成 Hexo 的安装。
    1
    npm install -g hexo-cli
  • 创建博客目录
    在任意位置创建一个文件夹,如MyBlog,cd到该路径下
    1
    2
    3
    4
    # 在你选择的目录下创建一个MyBlog文件夹
    mkdir MyBlog
    # 进入目录
    cd MyBlog
  • 初始化目录,在MyBlog目录下
    1
    hexo init
  • 启动本地服务
    1
    hexo s # 也可是 hexo server
    如果出现以下信息说明成功,浏览器输入http://localhost:4000/ 就可以访问了。
    当然这个博客是本地的,别人是无法访问的,之后我们需要部署到GitHub上。
    ``
    1
    2
    INFO  Start processing
    INFO Hexo is running at http://localhost:4000 . Press Ctrl+C to stop.
    到这里说明你的环境以及没有问题了,成功了搭建起了博客框架

博客关联到Github仓库

博客文件夹MyBlog中的_config.yml文件,这是博客的主要配置文件。

  • 编辑博客配置文件_config.yml中的deploy节点。记得将username(Vicxiaoyezi)换成自己的username
    1
    2
    3
    4
    deploy:
    type: git
    repo: git@github.com:Vicxiaoyezi/Vicxiaoyezi.github.io.git
    branch: master
    注意:repo为这种形式的是配置了SSH Key之后的,如果没有配置则使用Https形式的地址。
  • 为了能够使Hexo部署到GitHub上,需要安装一个插件,在博客目录下运行以下命令
    1
    npm install hexo-deployer-git --save
  • 然后在博客目录中执行
    1
    2
    3
    4
    5
    6
    #清除缓存
    hexo clean
    # 产生静态网页
    hexo g
    # 部署到GitHub page上
    hexo d
    在浏览器输入username.github.io就可以访问你的博客了(例如:Vicxiaoyezi.github.io)。

博客的基本使用

  • 创建新文章,博客目录下执行以下命令
    1
    2
    hexo new '文章标题'
    #例如:hexo new Mac下使用Hexo-Github搭建个人博客
    这样会在本地博客的source->_posts路径下看到新建的文章,是md格式的,找一个markdown文本编辑器进行编辑即可。
  • 编辑完成之后本地地址预览,在博客目录下运行以下命令
    1
    hexo clean && hexo g && hexo s
  • 编辑完成之后上传Github,在博客目录下运行以下命令
    1
    hexo clean && hexo g && hexo d

Hexo博客美化及功能增添

Hexo官网主题
Hexo主题在Github上有很多,如

https://github.com/theme-next/hexo-theme-next
https://github.com/litten/hexo-theme-yilia
https://github.com/viosey/hexo-theme-material
https://github.com/LouisBarranqueiro/hexo-theme-tranquilpeak
https://github.com/pinggod/hexo-theme-apollo
https://github.com/ppoffice/hexo-theme-icarus

挑选自己喜欢的一款。当然自己能够DIY,但是路很长慢慢来。

绑定个人域名

如果你想拥有一个炫酷的域名,那就往下看吧

购买域名

可以去万网买,也可以去其它地方。具体购买过程就不多讲了。

配置DNS地址

进入万网-控制台-域名-具体域名管理-DNS修改
修改DNS为DNSPod的免费DNS地址:

1
2
f1g1ns1.dnspod.net 
f1g1ns2.dnspod.net

-w887

获取自己 github 的二级域名的 IP地址

直接在终端输入以下命令(username换成自己的)

1
2
3
4
5
6
ping username.github.io
#例如:ping Vicxiaoyezi.github.io
```
## 域名解析
注册一个[DNSPod](https://www.dnspod.cn/)账号,登录之后把我们新注册的域名加进去,在域名解析界面添加3条记录

@ A 192.30.252.153
@ A vic.kim.

```
如图所示。
-w782

设置CNAME

在 hexo 项目下,source 文件夹下面创建 CNAME 文件(没有后缀名的),在里面写上购买的域名。比如:
-w375

在github上面,打开username.github.io项目的(Settings)设置,然后在 GitHub Pages的 Custom domain设置里填上购买的域名。比如:
-w770

到这里就绑定域名成功了。

最后晒上我的博客地址:vic.kim
祝大家也早日拥有自己的博客~

背景

2010年,几个搞IT的年轻人,在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说,是和LXC有关的容器技术。
LXC,就是Linux容器虚拟技术(Linux container)

后来,dotCloud公司将自己的容器技术进行了简化和标准化,并命名为——Docker。
Docker

Docker技术诞生之后,并没有引起行业的关注。而dotCloud公司,作为一家小型创业企业,在激烈的竞争之下,也步履维艰。正当他们快要坚持不下去的时候,脑子里蹦出了“开源”的想法。什么是“开源”?开源,就是开放源代码。也就是将原来内部保密的程序源代码开放给所有人,然后让大家一起参与进来,贡献代码和意见。

有的软件是一开始就开源的。也有的软件,是混不下去,创造者又不想放弃,所以选择开源。自己养不活,就吃“百家饭”嘛。2013年3月,dotCloud公司的创始人之一,Docker之父,28岁的Solomon Hykes正式决定,将Docker项目开源。

不开则已,一开惊人。越来越多的IT工程师发现了Docker的优点,然后蜂拥而至,加入Docker开源社区。Docker的人气迅速攀升,速度之快,令人瞠目结舌。开源当月,Docker 0.1 版本发布。此后的每一个月,Docker都会发布一个版本。到2014年6月9日,Docker 1.0 版本正式发布。此时的Docker,已经成为行业里人气最火爆的开源技术,没有之一。甚至像Google、微软、Amazon、VMware这样的巨头,都对它青睐有加,表示将全力支持。Docker火了之后,dotCloud公司干脆把公司名字也改成了Docker Inc. 。

Docker和容器技术为什么会这么火爆?说白了,就是因为它“轻”。在容器技术之前,业界的网红是虚拟机。虚拟机技术的代表,是VMWare和OpenStack。

相信很多人都用过虚拟机。虚拟机,就是在你的操作系统里面,装一个软件,然后通过这个软件,再模拟一台甚至多台“子电脑”出来。在“子电脑”里,你可以和正常电脑一样运行程序,例如开QQ。如果你愿意,你可以变出好几个“子电脑”,里面都开上QQ。“子电脑”和“子电脑”之间,是相互隔离的,互不影响。虚拟机属于虚拟化技术。

而Docker这样的容器技术,也是虚拟化技术,属于轻量级的虚拟化。虚拟机虽然可以隔离出很多“子电脑”,但占用空间更大,启动更慢,虚拟机软件可能还要花钱(例如VMWare)。而容器技术恰好没有这些缺点。它不需要虚拟出整个操作系统,只需要虚拟一个小规模的环境(类似“沙箱”)。

它启动时间很快,几秒钟就能完成。而且,它对资源的利用率很高(一台主机可以同时运行几千个Docker容器)。此外,它占的空间很小,虚拟机一般要几GB到几十GB的空间,而容器只需要MB级甚至KB级。正因为如此,容器技术受到了热烈的欢迎和追捧,发展迅速。
容器和虚拟机的对比

Docker

我们具体来看看Docker。大家需要注意,Docker本身并不是容器,它是创建容器的工具,是应用容器
引擎。想要搞懂Docker,其实看它的两句口号就行。

第一句,是“Build, Ship and Run”
也就是,“搭建、发送、运行”,三板斧。

举个例子:我来到一片空地,想建个房子,于是我搬石头、砍木头、画图纸,一顿操作,终于把这个房子盖好了。

结果,我住了一段时间,想搬到另一片空地去。这时候,按以往的办法,我只能再次搬石头、砍木头、画图纸、盖房子。但是,跑来一个老巫婆,教会我一种魔法。这种魔法,可以把我盖好的房子复制一份,做成“镜像”,放在我的背包里。等我到了另一片空地,就用这个“镜像”,复制一套房子,摆在那
边,拎包入住。怎么样?是不是很神奇?

Docker的第二句口号就是:“Build once,Run anywhere(搭建一次,到处能用)”
Docker技术的三大核心概念,分别是:

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)
    我刚才例子里面,那个放在包里的“镜像”,就是Docker镜像。而我的背包,就是Docker仓库。我在空地上,用魔法造好的房子,就是一个Docker容器

说白了,这个Docker镜像,是一个特殊的文件系统。它除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(例如环境变量)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。也就是说,每次变出房子,房子是一样的,但生活用品之类的,都是不管的。谁住谁负责添置。

每一个镜像可以变出一种房子。那么,我可以有多个镜像呀!也就是说,我盖了一个欧式别墅,生成了镜像。另一个哥们可能盖了一个中国四合院,也生成了镜像。还有哥们,盖了一个非洲茅草屋,也生成了镜像。。。这么一来,我们可以交换镜像,你用我的,我用你的,岂不是很爽?

于是乎,就变成了一个大的公共仓库。负责对Docker镜像进行管理的,是Docker Registry服务(类似仓库管理员)。不是任何人建的任何镜像都是合法的。万一有人盖了一个有问题的房子呢?所以,Docker Registry服务对镜像的管理是非常严格的。最常使用的Registry公开服务,是官方的Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。

K8S

好了,说完了Docker,我们再把目光转向K8S。

就在Docker容器技术被炒得热火朝天之时,大家发现,如果想要将Docker应用于具体的业务实现,是存在困难的——编排、管理和调度等各个方面,都不容易。于是,人们迫切需要一套管理系统,对Docker及容器进行更高级更灵活的管理。

就在这个时候,K8S出现了。

K8S,就是基于容器的集群管理平台,它的全称,是kubernetes。

Kubernetes 这个单词来自于希腊语,含义是舵手或领航员。K8S是它的缩写,用“8”字替代了了“ubernete”这8个字符。和Docker不同,K8S的创造者,是众人皆知的行业巨头——Google。然而,K8S并不是一件全新的发明。它的前身,是Google自己捣鼓了十多年的Borg系统。K8S是2014年6月由Google公司正式公布出来并宣布开源的。同年7月,微软、Red Hat、IBM、Docker、CoreOS、 Mesosphere和Saltstack 等公司,相继加入K8S。之后的一年内,VMware、HP、Intel等公司,也陆续加入。2015年7月,Google正式加入OpenStack基金会。与此同时,Kuberentes v1.0正式发布。目前,kubernetes的版本已经发展到V1.13。K8S的架构,略微有一点复杂,我们简单来看一下。

一个K8S系统,通常称为一个K8S集群(Cluster)。
这个集群主要包括两个部分:
* 一个Master节点(主节点)
* 一群Node节点(计算节点)

一看就明白:Master节点主要还是负责管理和控制。Node节点是工作负载节点,里面是具体的容器。深入来看这两种节点。首先是Master节点。

Master节点包括API Server、Scheduler、Controller manager、etcd。

  • API Server是整个系统的对外接口,供客户端和其它组件调用,相当于“营业厅”。
  • Scheduler负责对集群内部的资源进行调度,相当于“调度室”。
  • Controller manager负责管理控制器,相当于“大总管”。
    然后是Node节点。

Node节点包括Docker、kubelet、kube-proxy、Fluentd、kube-dns(可选),还有就是Pod。

Pod是Kubernetes最基本的操作单元。一个Pod代表着集群中运行的一个进程,它内部封装了一个或多个紧密相关的容器。除了Pod之外,K8S还有一个Service的概念,一个Service可以看作一组提供相同服务的Pod的对外访问接口。这段不太好理解,跳过吧。

Docker,不用说了,创建容器的。
Kubelet,主要负责监视指派到它所在Node上的Pod,包括创建、修改、监控、删除等。
Kube-proxy,主要负责为Pod对象提供代理。
Fluentd,主要负责日志收集、存储与查询。
是不是有点懵?唉,三言两语真的很难讲清楚,继续跳过吧。

Docker和K8S都介绍完了,然而文章并没有结束。

接下来的部分,是写给核心网工程师甚至所有通信工程师看的

从几十年前的1G,到现在的4G,再到将来的5G,移动通信发生了翻天覆地的变化,核心网亦是如此。

但是,如果你仔细洞察这些变化,会发现,所谓的核心网,其实本质上并没有发生改变,无非就是很多的服务器而已。不同的核心网网元,就是不同的服务器,不同的计算节点。

变化的,是这些“服务器”的形态和接口:形态,从机柜单板,变成机柜刀片,从机柜刀片,变成X86通用刀片服务器;接口,从中继线缆,变成网线,从网线,变成光纤。

就算变来变去,还是服务器,是计算节点,是CPU。

既然是服务器,那么就势必会和IT云计算一样,走上虚拟化的道路。毕竟,虚拟化有太多的优势,例如前文所说的低成本、高利用率、充分灵活、动态调度,等等。

前几年,大家以为虚拟机是核心网的终极形态。目前看来,更有可能是容器化。这几年经常说的NFV(网元功能虚拟化),也有可能改口为NFC(网元功能容器化)。

以VoLTE为例,如果按以前2G/3G的方式,那需要大量的专用设备,分别充当EPC和IMS的不同网元。而采用容器之后,很可能只需要一台服务器,创建十几个容器,用不同的容器,来分别运行不同网元的服务程序。这些容器,随时可以创建,也可以随时销毁。还能够在不停机的情况下,随意变大,随意变小,随意变强,随意变弱,在性能和功耗之间动态平衡。简直完美!

5G时代,核心网采用微服务架构,也是和容器完美搭配——单体式架构(Monolithic)变成微服务架构(Microservices),相当于一个全能型变成N个专能型。每个专能型,分配给一个隔离的容器,赋予了最大程度的灵活。
精细化分工

按照这样的发展趋势,在移动通信系统中,除了天线,剩下的部分都有可能虚拟化。核心网是第一个,但不是最后一个。虚拟化之后的核心网,与其说属于通信,实际上更应该归为IT。核心网的功能,只是容器中普通一个软件功能而已。

至于说在座的各位核心网工程师,恭喜你们,马上就要成功转型啦!

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