-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsearch.xml
More file actions
325 lines (325 loc) · 241 KB
/
Copy pathsearch.xml
File metadata and controls
325 lines (325 loc) · 241 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>基于Django和TencentOS tiny的IoT智能灯系统设计</title>
<url>/2019/08/17/%E5%9F%BA%E4%BA%8EDjango%E5%92%8CTencentOS%20tiny%E7%9A%84IoT%E6%99%BA%E8%83%BD%E7%81%AF%E7%B3%BB%E7%BB%9F%E8%AE%BE%E8%AE%A1/</url>
<content><![CDATA[<p>如今,工业,农业,生活里面的很多设备已经接入物联网,这些设备通过同一的平台进行管理。但是,每家厂商都自己接入自己的物联网设备,造成了设备与平台的割裂。所以打通设备与云平台成为非常重要的一环。而腾讯云物联网开发平台正是为次孕育而生,为整个行业推出一套解决方案。本文主要依据开发文档,一步步实现基于Django web控制智能灯软硬件系统。<br>本设计包括3部分组成,用户可以通过web界面进与IoT设备进行简单操作。包括Django web平台,腾讯云平台,IoT设备,其系统架构图如下:</p><a id="more"></a><h2 id="内容:"><a href="#内容:" class="headerlink" title="内容:"></a>内容:</h2><ul><li>硬件简介</li><li>软件简介</li><li>系统功能概述</li><li>Demo</li><li>TODO</li><li>使用感想</li></ul><p><img data-src="https://s1.ax1x.com/2020/03/16/88vN79.png" alt="系统架构图"></p><h3 id="硬件简介"><a href="#硬件简介" class="headerlink" title="硬件简介"></a>硬件简介</h3><p>用过STM32板子的同学对于开发这个肯定不陌生,但是对于只用过树莓派的同学,还是有很大区别的。<br>这个板子长这个样子,看上去跟普通的板子没啥区别,但是盒子里面的东西,以及外设的设置都比较良心。列出以下几点:</p><ol><li>是烧录工具,贴心的4连杜邦线,直接插入即可。妈妈再也不用担心因为插错而烧坏板子!</li><li>串口直接采用usb串口,无需购买额外ttl转串口工具。</li><li>板载电池,更适合物联网需求。</li><li>各种外设,需要的都在,最关键的是支持各种联网外设,符合各种传输需求。</li></ol><p><img data-src="https://s1.ax1x.com/2020/03/16/88vakR.png" alt="外设1"><br><img data-src="https://s1.ax1x.com/2020/03/16/88vdt1.png" alt="外设2"></p><h3 id="软件简介"><a href="#软件简介" class="headerlink" title="软件简介"></a>软件简介</h3><p>内核开发环境:</p><ul><li>Keil5 推荐</li><li>IAR</li><li>gcc<br>云平台API</li><li>数据查询接口</li><li>设备管理接口<br>软件开发:</li><li>Django</li><li>Django REST framework</li></ul><h3 id="系统功能概述"><a href="#系统功能概述" class="headerlink" title="系统功能概述"></a>系统功能概述</h3><h4 id="内核功能修改"><a href="#内核功能修改" class="headerlink" title="内核功能修改"></a>内核功能修改</h4><p>添加智能灯设备初始化与控制,自定义修改OLED显示内容<br><img data-src="https://s1.ax1x.com/2020/03/16/88vt0J.png" alt="初始化GPIO"><br><img data-src="https://s1.ax1x.com/2020/03/16/88vYm4.png" alt="控制灯"></p><h4 id="云平台配置"><a href="#云平台配置" class="headerlink" title="云平台配置"></a>云平台配置</h4><p>云平台提供相应的模版,可以配置需要的状态以及字段,通过上下行数据进行同步。并且可以通过手动去测试api,上手十分方便。<br><img data-src="https://s1.ax1x.com/2020/03/16/88vwfx.png" alt="图形化测试平台"><br><img data-src="https://s1.ax1x.com/2020/03/16/88vBp6.png" alt="api在线测试平台"></p><h4 id="web服务开发"><a href="#web服务开发" class="headerlink" title="web服务开发"></a>web服务开发</h4><p>通过Django搭建基于web服务,通过REST framework存储状态。前端处理用户的控制以及状态、地图的显示。后端处理请求结果,状态同步等工作。</p><h4 id="数据同步"><a href="#数据同步" class="headerlink" title="数据同步"></a>数据同步</h4><p>借助controller进行硬件、平台和数据库之间状态的转移与同步。并完成发送命令到云平台,进行下发控制。用户根据web界面进行控制操作,并将状态同步到数据库中。控制界面图如下所示:<br><img data-src="https://s1.ax1x.com/2020/03/16/88vD1K.png" alt="关灯"><br><img data-src="https://s1.ax1x.com/2020/03/16/88vr6O.png" alt="开灯"></p><h3 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h3><p>时间有限,所以录了一个小视频。</p><p>链接地址(Youtube):<a href="https://www.youtube.com/watch?v=ctormFkCIqU" target="_blank" rel="noopener">https://www.youtube.com/watch?v=ctormFkCIqU</a></p><p><img data-src="https://s1.ax1x.com/2020/03/16/88vsXD.png" alt="88vsXD.png"></p><p><img data-src="https://s1.ax1x.com/2020/03/16/88vb7j.png" alt="88vb7j.png"></p><h3 id="后续改进TODO"><a href="#后续改进TODO" class="headerlink" title="后续改进TODO"></a>后续改进TODO</h3><p>由于只开发了2天,所以有多功能每加,列举了之后会加入的想法和功能:</p><ul><li>根据光照强度调节</li><li>定时开关灯</li><li>微信接入,远程控制</li></ul><h3 id="试用感受"><a href="#试用感受" class="headerlink" title="试用感受"></a>试用感受</h3><p>本次内测由于外设没有那么多,只能够用到现成的资源,修改少部分内核,大部分实现功能在web上面。我之前使用过STM32F103,对于Keil的使用以及配置没有什么太多的问题,如果需要更深修改内核,只需要参考芯片手册即可。所以包括硬件连接,已有内核上代码修改都感觉手难度比较容易。对于腾讯云平台的体验还是不错的,但是网络环境的影响还是稍有些延迟。总体上对于腾讯云平台的调试工具基于好评,支持自动生成代码。生产环境安全性很重要,但对于调试认证过期问题,希望腾讯云平台可以改进、优化调试认证流程。</p><p>最后,希望这个开发版可以开发出更多好的应用,希望自己的实践可以为之后的人提供一些思路。</p><h3 id="联系方式"><a href="#联系方式" class="headerlink" title="联系方式"></a>联系方式</h3><p>邮箱:<a href="mailto:gzr2008@vip.qq.com">gzr2008@vip.qq.com</a><br>个人主页 WEB PAGE: <a href="http://www.ranxiaolang.com" target="_blank" rel="noopener">http://www.ranxiaolang.com</a> | 博客 BLOG: <a href="https://nature1995.club">https://nature1995.club</a></p>]]></content>
<categories>
<category>IOT</category>
</categories>
<tags>
<tag>物联网</tag>
<tag>云API</tag>
</tags>
</entry>
<entry>
<title>前后端分离——JWT用户认证</title>
<url>/2020/02/25/%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB%E2%80%94%E2%80%94JWT%E7%94%A8%E6%88%B7%E8%AE%A4%E8%AF%81/</url>
<content><![CDATA[<p>在前后端分离开发时为什么需要用户认证呢?原因是由于 HTTP 协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个 request 请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是谁,就要再验证一次。所以为了保证系统安全,我们就需要验证用户否处于登录状态。</p><a id="more"></a><h2 id="传统方式"><a href="#传统方式" class="headerlink" title="传统方式"></a>传统方式</h2><p>前后端分离通过 Restful API 进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个 token,并保存这个 token 和对应的用户 id 到数据库或 Session 中,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个 cookie,后端根据这个 cookie 值来查询用户,验证是否过期。<br>但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。<br>在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。<br>httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。<br>另外,如果将验证信息保存在数据库中,后端每次都需要根据 token 查出用户 id,这就增加了数据库的查询和存储开销。若把验证信息保存在 session 中,有加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?如果我们生成 token 遵循一定的规律,比如我们使用对称加密算法来加密用户 id 形成 token,那么服务端以后其实只要解密该 token 就可以知道用户的 id 是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造 token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。</p><h2 id="Json-Web-Token(JWT)"><a href="#Json-Web-Token(JWT)" class="headerlink" title="Json Web Token(JWT)"></a>Json Web Token(JWT)</h2><p>JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:<br>简洁(Compact)<br>可以通过 URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快<br>自包含(Self-contained)<br>负载中包含了所有用户所需要的信息,避免了多次查询数据库</p><h3 id="JWT-组成"><a href="#JWT-组成" class="headerlink" title="JWT 组成"></a>JWT 组成</h3><p><a href="https://imgchr.com/i/3NrqFU" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3NrqFU.md.jpg" alt="3NrqFU.md.jpg"></a></p><ul><li>Header 头部</li></ul><p>头部包含了两部分,token 类型和采用的加密算法</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"alg"</span>: <span class="string">"HS256"</span>,</span><br><span class="line"> <span class="attr">"typ"</span>: <span class="string">"JWT"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>它会使用 Base64 编码组成 JWT 结构的第一部分,如果你使用 Node.js,可以用 Node.js 的包 base64url 来得到这个字符串。</p><blockquote><p>Base64 是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。</p></blockquote><ul><li>Payload 负载</li></ul><p>这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"iss"</span>: <span class="string">"lion1ou JWT"</span>,</span><br><span class="line"> <span class="attr">"iat"</span>: <span class="number">1441593502</span>,</span><br><span class="line"> <span class="attr">"exp"</span>: <span class="number">1441594722</span>,</span><br><span class="line"> <span class="attr">"aud"</span>: <span class="string">"www.example.com"</span>,</span><br><span class="line"> <span class="attr">"sub"</span>: <span class="string">"lion1ou@163.com"</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>同样的,它会使用 Base64 编码组成 JWT 结构的第二部分</p><ul><li>Signature 签名</li></ul><p>前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。<br>三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。</p><p><code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s</code></p><p>其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。</p><ul><li>签名的目的</li></ul><p>最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的 JWT 的话,那么服务器端会判断出新的头部和负载形成的签名和 JWT 附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。</p><ul><li>信息暴露</li></ul><p>在这里大家一定会问一个问题:Base64 是一种编码,是可逆的,那么我的信息不就被暴露了吗?<br>是的。所以,在 JWT 中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的 User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在 JWT 中了。如果将用户的密码放在了 JWT 中,那么怀有恶意的第三方通过 Base64 解码就能很快地知道你的密码了。<br>因此 JWT 适合用于向 Web 应用传递一些非敏感信息。JWT 还经常用于设计用户认证和授权系统,甚至实现 Web 应用的单点登录。</p><h2 id="JWT-使用"><a href="#JWT-使用" class="headerlink" title="JWT 使用"></a>JWT 使用</h2><p><a href="https://imgchr.com/i/3NrHoT" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3NrHoT.md.jpg" alt="3NrHoT.md.jpg"></a></p><ol><li>首先,前端通过 Web 表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个 HTTP POST 请求。建议的方式是通过 SSL 加密的传输(https 协议),从而避免敏感信息被嗅探。</li><li>后端核对用户名和密码成功后,将用户的 id 等其他信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名,形成一个 JWT。形成的 JWT 就是一个形同 lll.zzz.xxx 的字符串。</li><li>后端将 JWT 字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在 localStorage 或 sessionStorage 上,退出登录时前端删除保存的 JWT 即可。</li><li>前端在每次请求时将 JWT 放入 HTTP Header 中的 Authorization 位。(解决 XSS 和 XSRF 问题)</li><li>后端检查是否存在,如存在验证 JWT 的有效性。例如,检查签名是否正确;检查 Token 是否过期;检查 Token 的接收方是否是自己(可选)。</li><li>验证通过后后端使用 JWT 中包含的用户信息进行其他逻辑操作,返回相应结果。</li></ol><h3 id="和-Session-方式存储-id-的差异"><a href="#和-Session-方式存储-id-的差异" class="headerlink" title="和 Session 方式存储 id 的差异"></a>和 Session 方式存储 id 的差异</h3><p>Session 方式存储用户 id 的最大弊病在于 Session 是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些 KV 数据库和一系列缓存机制来实现 Session 的存储。</p><p>而 JWT 方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户 id 之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。虽说 JWT 方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。</p><ul><li>单点登录<br>Session 方式来存储用户 id,一开始用户的 Session 只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:<code>www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com</code>。所以如果要实现在<code>login.taobao.com</code>登录后,在其他的子域名下依然可以取到 Session,这要求我们在多台服务器上同步 Session。使用 JWT 的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>JWT 的主要作用在于(一)可附带用户信息,后端直接通过 JWT 获取相关信息。(二)使用本地保存,通过 HTTP Header 中的 Authorization 位提交验证。但其实关于 JWT 存放到哪里一直有很多讨论,有人说存放到本地存储,有人说存 cookie。个人偏向于放在本地存储,如果你有什么意见和看法欢迎提出。</p><blockquote><p>原文链接: <a href="http://lion1ou.win/2017/01/18" target="_blank" rel="noopener">http://lion1ou.win/2017/01/18</a><br>版权声明: 本作品采用 知识共享署名 4.0 国际许可协议 进行许可。</p></blockquote>]]></content>
<categories>
<category>Django</category>
</categories>
<tags>
<tag>Django</tag>
<tag>JWT</tag>
<tag>HTTP</tag>
</tags>
</entry>
<entry>
<title>django从请求到响应全过程详解</title>
<url>/2020/02/24/django%E4%BB%8E%E8%AF%B7%E6%B1%82%E5%88%B0%E5%93%8D%E5%BA%94%E5%85%A8%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3/</url>
<content><![CDATA[<h2 id="django启动"><a href="#django启动" class="headerlink" title="django启动"></a>django启动</h2><p>我们在启动一个django项目的时候,无论你是在命令行执行还是在pycharm直接点击运行,其实都是执行’runserver’的操作,而ruserver是使用django自带的的web server,主要用于开发和调试中,而在正式的环境中,一般会使用nginx+uwsgi模式。<br>无论是哪种方式,当启动一个项目,都会做2件事:</p><ul><li>创建一个WSGIServer类的实例,接受用户的请求。</li><li>当一个用户的http请求到达的时,为用户指定一个WSGIHandler,用于处理用户请求与响应,这个Handler是处理整个request的核心。<a id="more"></a></li></ul><h2 id="WSGI"><a href="#WSGI" class="headerlink" title="WSGI"></a>WSGI</h2><p>WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,也不用于与程序交互的API,更不是代码,而只是定义了一个接口,用于描述web server如何与web application通信的规范。 当客户端发送一次请求后,最先处理请求的实际上是 web 服务器就是我们经常说的 nginx、Apache 这类的 web 服务器,然后web服务器再把请求交给web应用程序(如django)处理,这中间的中介就是WSGI,它把 web 服务器和 web 框架 (Django) 连接起来。</p><p><a href="https://imgchr.com/i/3Ngpge" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3Ngpge.png" alt="3Ngpge.png"></a></p><p>简单介绍一下WSGI的一些内容,它规定应用是可调用对象(函数/方法),然后它接受2个固定参数:一个是含有服务器端的环境变量,另一个是可调用对象,这个对象用来初始化响应,给响应加上status code状态码和httpt头部,并且返回一个可调用对象。可以看个简单的例子</p><figure class="highlight ruby"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 这段代码来自python核心编程</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">simplr_wsgi_app</span><span class="params">(environ, start_response)</span></span><span class="symbol">:</span></span><br><span class="line"> <span class="comment"># 固定两个参数,django中也使用同样的变量名</span></span><br><span class="line"> status = <span class="string">'200 OK'</span></span><br><span class="line"> headers = [{<span class="string">'Content-type'</span>: <span class="string">'text/plain'</span>}]</span><br><span class="line"> <span class="comment"># 初始化响应, 必须在返回前调用</span></span><br><span class="line"> start_response(status, headers)</span><br><span class="line"> <span class="comment"># 返回可迭代对象</span></span><br><span class="line"> <span class="keyword">return</span> [<span class="string">'hello world!'</span>]</span><br></pre></td></tr></table></figure><p>django中,实现同样逻辑的是通过WSGIHandler这个类,下面我们也会重点介绍它! 如果对WSGI与uWSGI有兴趣的,推荐大家看这篇文章,WSGI & uwsgi ,大赞!</p><h2 id="中间件基本概念"><a href="#中间件基本概念" class="headerlink" title="中间件基本概念"></a>中间件基本概念</h2><p>顾名思义,中间件是位于Web服务器端和Web应用之间的,它可以添加额外的功能。当我们创建一个django项目(通过pycharm),它会自动帮我们设置一些必要的中间件。</p><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">MIDDLEWARE_CLASSES</span> = [</span><br><span class="line"> <span class="string">'django.middleware.security.SecurityMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.contrib.sessions.middleware.SessionMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.middleware.common.CommonMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.middleware.csrf.CsrfViewMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.contrib.auth.middleware.AuthenticationMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.contrib.auth.middleware.SessionAuthenticationMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.contrib.messages.middleware.MessageMiddleware'</span>,</span><br><span class="line"> <span class="string">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span>,</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>中间件要么对来自用户的数据进行预处理,然后发送给应用;要么在应用将响应负载返回给用户之前,对结果数据进行一些最终的调整。通俗一点,在django中,中间能够帮我们准备好request这个对象,然后应用可以直接使用request对象获取到各类数据,也帮我们将response添加头部,状态码等。</p><h2 id="数据流"><a href="#数据流" class="headerlink" title="数据流"></a>数据流</h2><p>当django接受到一个请求时,会初始化一个WSGIHandler,可以在项目下的wsgi.py文件进行跟踪,你就会发现这一个类。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">WSGIHandler</span><span class="params">(base.BaseHandler)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__call__</span><span class="params">(self, environ, start_response)</span>:</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><p>这个类遵循WSGI应用的规定,它接受2个参数:一个是含有服务器端的环境变量,另一个是可调用对象,返回一个可迭代对象。 这个handler控制了从请求到响应的整个过程,主要流程:</p><p><a href="https://imgchr.com/i/3NgPud" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3NgPud.md.png" alt="3NgPud.md.png"></a></p><p>在网上看到另外一张图,更为完整:</p><p><a href="https://imgchr.com/i/3NgiDA" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3NgiDA.png" alt="3NgiDA.png"></a></p><p>大致几个步骤:</p><ol><li>用户通过浏览器请求一个页面</li><li>请求到达Request Middlewares,中间件对request做一些预处理或者直接response请求</li><li>URLConf通过urls.py文件和请求的URL找到相应的View</li><li>View Middlewares被访问,它同样可以对request做一些处理或者直接返回response</li><li>调用View中的函数</li><li>View中的方法可以选择性的通过Models访问底层的数据</li><li>所有的Model-to-DB的交互都是通过manager完成的</li><li>如果需要,Views可以使用一个特殊的Context</li><li>Context被传给Template用来生成页面<br>a.Template使用Filters和Tags去渲染输出<br>b.输出被返回到View<br>c.HTTPResponse被发送到Response Middlewares<br>d.任何Response Middlewares都可以丰富response或者返回一个完全不同的response<br>e.Response返回到浏览器,呈现给用户</li></ol><h2 id="中间类中的顺序与方法"><a href="#中间类中的顺序与方法" class="headerlink" title="中间类中的顺序与方法"></a>中间类中的顺序与方法</h2><p>django 的中间件类至少含有以下四个方法中的一个:<br>process_request、 process_view、process_exception、process_response<br>WSGIHandler通过load_middleware将这个些方法分别添加到_request_middleware、_view_middleware、_response_middleware 和 _exception_middleware四个列表中。<br>并不是每个中间件都有这4个方法,如果不存在某个方法,那么在加载的过程中,这个类就被跳过。</p><figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line"><span class="keyword">for</span> middleware_path <span class="built_in">in</span> settings.MIDDLEWARE_CLASSES:</span><br><span class="line"> ···</span><br><span class="line"> <span class="keyword">if</span> hasattr(mw_instance, <span class="string">'process_request'</span>):</span><br><span class="line"> request_middleware.<span class="built_in">append</span>(mw_instance.process_request)</span><br><span class="line"> <span class="keyword">if</span> hasattr(mw_instance, <span class="string">'process_view'</span>):</span><br><span class="line"> self.<span class="variable">_view_middleware</span>.<span class="built_in">append</span>(mw_instance.process_view)</span><br><span class="line"> <span class="keyword">if</span> hasattr(mw_instance, <span class="string">'process_template_response'</span>):</span><br><span class="line"> self.<span class="variable">_template_response_middleware</span>.insert(<span class="number">0</span>, mw_instance.process_template_response)</span><br><span class="line"> <span class="keyword">if</span> hasattr(mw_instance, <span class="string">'process_response'</span>):</span><br><span class="line"> self.<span class="variable">_response_middleware</span>.insert(<span class="number">0</span>, mw_instance.process_response)</span><br><span class="line"> <span class="keyword">if</span> hasattr(mw_instance, <span class="string">'process_exception'</span>):</span><br><span class="line"> self.<span class="variable">_exception_middleware</span>.insert(<span class="number">0</span>, mw_instance.process_exception)</span><br></pre></td></tr></table></figure><p>我们可以从源码看出,process request 和 process response的执行加载顺序正好是相反,在循环中,process_request是被append到列表的末尾,而process_request是被insert到最前面的。</p><p><a href="https://imgchr.com/i/3NgEUP" target="_blank" rel="noopener"><img data-src="https://s2.ax1x.com/2020/02/26/3NgEUP.png" alt="3NgEUP.png"></a></p><p>(可能有些情况Comment中间件在Session前面,了解加载的顺序就好了)</p><h3 id="process-request"><a href="#process-request" class="headerlink" title="process_request"></a>process_request</h3><p>举几个中间件的例子</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CommonMiddleware</span><span class="params">(object)</span>:</span></span><br><span class="line"><span class="comment"># 伪代码</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">process_request</span><span class="params">(self, request)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="comment"># Check for denied User-Agents</span></span><br><span class="line"> <span class="keyword">if</span> <span class="string">'HTTP_USER_AGENT'</span> <span class="keyword">in</span> request.META:</span><br><span class="line"> <span class="keyword">for</span> user_agent_regex <span class="keyword">in</span> settings.DISALLOWED_USER_AGENTS:</span><br><span class="line"> <span class="keyword">if</span> user_agent_regex.search(request.META[<span class="string">'HTTP_USER_AGENT'</span>]):</span><br><span class="line"> <span class="keyword">raise</span> PermissionDenied(<span class="string">'Forbidden user agent'</span>)</span><br><span class="line"> host = request.get_host()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> settings.PREPEND_WWW <span class="keyword">and</span> host <span class="keyword">and</span> <span class="keyword">not</span> host.startswith(<span class="string">'www.'</span>):</span><br><span class="line"> host = <span class="string">'www.'</span> + host</span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><p>CommonMiddleware的process_request主要是判断用户代理是否符合要求以及在完善URL,如增加www或者末尾加/。</p><figure class="highlight reasonml"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="constructor">SessionMiddleware(<span class="params">object</span>)</span>:</span><br><span class="line"> def process<span class="constructor">_request(<span class="params">self</span>, <span class="params">request</span>)</span>:</span><br><span class="line"> session_key = request.<span class="module-access"><span class="module"><span class="identifier">COOKIES</span>.</span></span>get(settings.SESSION_COOKIE_NAME)</span><br><span class="line"> request.session = self.<span class="constructor">SessionStore(<span class="params">session_key</span>)</span></span><br></pre></td></tr></table></figure><p>SessionMiddleware的process_request是把session_key从cookies中取出来然后放到request.session中。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AuthenticationMiddleware</span><span class="params">(MiddlewareMixin)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">process_request</span><span class="params">(self, request)</span>:</span></span><br><span class="line"> <span class="keyword">assert</span> hasattr(request, <span class="string">'session'</span>), (</span><br><span class="line"> <span class="string">"The Django authentication middleware requires session middleware "</span></span><br><span class="line"> <span class="string">"to be installed. Edit your MIDDLEWARE%s setting to insert "</span></span><br><span class="line"> <span class="string">"'django.contrib.sessions.middleware.SessionMiddleware' before "</span></span><br><span class="line"> <span class="string">"'django.contrib.auth.middleware.AuthenticationMiddleware'."</span></span><br><span class="line"> ) % (<span class="string">"_CLASSES"</span> <span class="keyword">if</span> settings.MIDDLEWARE <span class="keyword">is</span> <span class="keyword">None</span> <span class="keyword">else</span> <span class="string">""</span>)</span><br><span class="line"> request.user = SimpleLazyObject(<span class="keyword">lambda</span>: get_user(request))</span><br></pre></td></tr></table></figure><p>在前面提过,中间件的加载是按照一定顺序(正反序), AuthenticationMiddleware的process_request方法基于session中间件被加载过了,然后通过request的session,将用户取出来放入到request.user 。<br>process_request 应该返回 None 或者 HTTPResponse 对象。当返回 None 时,WSGI handler 会继续加载 process_request 里面的方法,如果是后一种情况,那么Handlers会直接加载_response_middleware的列表,然后直接response。</p><h3 id="解析-url"><a href="#解析-url" class="headerlink" title="解析 url"></a>解析 url</h3><p>当_request_middleware列表中的 process_request 被遍历完,会得到一个经过处理的request对象(加入了request.session,request.user等属性)。<br>django将按顺序进行对url进行正则匹配,如果匹配不成功,就会抛出异常。如果request的中间件返回None,那么Django会去解析用户请求的URL。<br>在setting中有一个ROOT_URLCONF,它指向urls.py文件,根据这个文件可以生产一个urlconf,本质上,他就是url与视图函数之间的映射表,然后通过resolver解析用户的url,找到第一个匹配的view。</p><h3 id="process-view"><a href="#process-view" class="headerlink" title="process_view"></a>process_view</h3><p>经过url的匹配,会获得视图函数以及相关参数。在调用view函数之前,django会先加载_view_middleware中的各个process_view方法。<br>逐个默认的中间件看了一遍,只看到csrf有这个方法</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 伪代码</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CsrfViewMiddleware</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">process_view</span><span class="params">(self, request, callback, callback_args, callback_kwargs)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> getattr(request, <span class="string">'csrf_processing_done'</span>, <span class="keyword">False</span>):</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">None</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> csrf_token = _sanitize_token(</span><br><span class="line"> request.COOKIES[settings.CSRF_COOKIE_NAME])</span><br><span class="line"> <span class="comment"># Use same token next time</span></span><br><span class="line"> request.META[<span class="string">'CSRF_COOKIE'</span>] = csrf_token</span><br><span class="line"> <span class="keyword">except</span> KeyError:</span><br><span class="line"> csrf_token = <span class="keyword">None</span></span><br><span class="line"> <span class="keyword">if</span> getattr(callback, <span class="string">'csrf_exempt'</span>, <span class="keyword">False</span>):</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">None</span></span><br><span class="line"> <span class="keyword">pass</span></span><br></pre></td></tr></table></figure><p>这个方法的作用是判断cookiers中是否存在csrf的字段,如果不存在,会直接抛出异常,如果存在,返回None。 view中间件和requst中间件一样,必须返回None或一个httpResponse,如果返回一个httpresponse,那么Handlers会直接加载_response_middleware的列表,然后返回HttpResponse,那么Handlers会直接加载_response_middleware的列表,然后直接response</p><h3 id="执行view逻辑"><a href="#执行view逻辑" class="headerlink" title="执行view逻辑"></a>执行view逻辑</h3><p>view函数需要满足:</p><ol><li>基于函数(FBV)或者基于类的(CVB)的视图。</li><li>接受的参数第一个必须为request,并且需要返回一个response对象。<br>如果视图函数抛出一个异常,Handler 将会循环遍历_exception_middleware 列表,如果有一个异常被抛出,后面的 process_exception 将不会被执行。</li></ol><h3 id="process-response"><a href="#process-response" class="headerlink" title="process_response"></a>process_response</h3><p>在这个阶段,我们得到了一个 HTTPResponse 对象,这个对象可能是 process_view 返回的,也可能是视图函数返回的。现在我们将循环访问响应中间件。这是中间件调整数据的最后的机会。举个例子:</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">XFrameOptionsMiddleware</span><span class="params">(object)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">process_response</span><span class="params">(self, request, response)</span>:</span></span><br><span class="line"> <span class="comment"># Don't set it if it's already in the response</span></span><br><span class="line"> <span class="keyword">if</span> response.get(<span class="string">'X-Frame-Options'</span>) <span class="keyword">is</span> <span class="keyword">not</span> <span class="keyword">None</span>:</span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Don't set it if they used @xframe_options_exempt</span></span><br><span class="line"> <span class="keyword">if</span> getattr(response, <span class="string">'xframe_options_exempt'</span>, <span class="keyword">False</span>):</span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"></span><br><span class="line"> response[<span class="string">'X-Frame-Options'</span>] = self.get_xframe_options_value(request,</span><br><span class="line"> response)</span><br><span class="line"> <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure><p>XFrameOptionsMiddleware将X-Frame-Options加入到response当中,防止网站被嵌套、被劫持。</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CsrfViewMiddleware</span><span class="params">(object)</span>:</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">process_response</span><span class="params">(self, request, response)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> getattr(response, <span class="string">'csrf_processing_done'</span>, <span class="keyword">False</span>):</span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> request.META.get(<span class="string">"CSRF_COOKIE_USED"</span>, <span class="keyword">False</span>):</span><br><span class="line"> <span class="keyword">return</span> response</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Set the CSRF cookie even if it's already set, so we renew</span></span><br><span class="line"> <span class="comment"># the expiry timer.</span></span><br><span class="line"> response.set_cookie(settings.CSRF_COOKIE_NAME,</span><br><span class="line"> request.META[<span class="string">"CSRF_COOKIE"</span>],</span><br><span class="line"> max_age=settings.CSRF_COOKIE_AGE,</span><br><span class="line"> domain=settings.CSRF_COOKIE_DOMAIN,</span><br><span class="line"> path=settings.CSRF_COOKIE_PATH,</span><br><span class="line"> secure=settings.CSRF_COOKIE_SECURE,</span><br><span class="line"> httponly=settings.CSRF_COOKIE_HTTPONLY</span><br><span class="line"> )</span><br><span class="line"> <span class="comment"># Content varies with the CSRF cookie, so set the Vary header.</span></span><br><span class="line"> patch_vary_headers(response, (<span class="string">'Cookie'</span>,))</span><br><span class="line"> response.csrf_processing_done = <span class="keyword">True</span></span><br><span class="line"> <span class="keyword">return</span> response</span><br></pre></td></tr></table></figure><p>CsrfViewMiddleware在response中设置csrf cookies</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>当response的中间件加载完,系统在返回之前会调用WSGI服务器端传过来的start_response方法对象,初始化响应,然后进行response响应。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文重点在于:</p><ol><li>django启动时,启动了一个WSGIserver以及为每个请求的用户生成一个handler。</li><li>理解WSGI协议,并且WSGIHandler这个类控制整个请求到响应的流程,以及整个流程的基本过程。</li><li>中间件的概念,以及每一个process_request, process_response, process_view, process_exception方法在哪个步骤发挥着什么样的作用。</li><li>中间价的执行时有顺序的,request与view是按照顺序去执行的,而response和exception是反序的,这一步实在WSGIHandler在加载到它的各个列表的时候完成的。</li></ol><p><strong>参考博客:</strong><br><a href="http://blog.csdn.net/alvine008/article/details/50249777" target="_blank" rel="noopener">1,Django教程笔记:六、中间件middleware</a><br><a href="https://www.jianshu.com/p/679dee0a4193" target="_blank" rel="noopener">2,做python Web开发你要理解:WSGI & uwsgi</a><br><a href="https://juejin.im/post/5a6951cdf265da3e591e782d" target="_blank" rel="noopener">3,从请求到响应 django 都做了哪些处理</a> <br><a href="http://projectsedu.com/2016/10/17/django%E4%BB%8E%E8%AF%B7%E6%B1%82%E5%88%B0%E8%BF%94%E5%9B%9E%E9%83%BD%E7%BB%8F%E5%8E%86%E4%BA%86%E4%BB%80%E4%B9%88/" target="_blank" rel="noopener">4,django从请求到返回都经历了什么</a></p><blockquote><p>原文作者: __奇犽犽lv-2<br>原文链接: <a href="https://juejin.im/post/5a6c4cc2f265da3e4c080605" target="_blank" rel="noopener">https://juejin.im/post/5a6c4cc2f265da3e4c080605</a></p></blockquote>]]></content>
<categories>
<category>Django</category>
</categories>
<tags>
<tag>Django</tag>
</tags>
</entry>
<entry>
<title>如何度量测试开发的价值产出</title>
<url>/2020/01/30/%E5%A6%82%E4%BD%95%E5%BA%A6%E9%87%8F%E6%B5%8B%E8%AF%95%E5%BC%80%E5%8F%91%E7%9A%84%E4%BB%B7%E5%80%BC%E4%BA%A7%E5%87%BA/</url>
<content><![CDATA[<p>每到年底的时候,不管是个人还是团队,总是避免不了要对这一年的工作成果进行总结和汇报。而对于测试开发岗位来说,通常会面临一个共性的问题:做了这么多事情,究竟产出了多大的业务价值?</p><p>在很长一段时间内,我对这个问题也是非常困惑。困惑的原因倒不是觉得工作内容没有价值,而是对于测试开发类的工作,通常没有明确的业务需求方,对于工作成果度量也没有统一的方式。</p><p>为什么测试开发岗位会面临这个问题呢?</p><a id="more"></a><p>这应该和测试岗位的职责和工作内容有很大的关系。关于测试开发工程师的定义,在《Google测试之道》一书中已经有了很全面的解释,我也很是认同。测试开发工程师(SDET,Software Development Engineer in Testing)首先应该是开发角色,只是相比于业务开发工程师,他们的目标用户更多的是公司内部的测试人员(也包括其他岗位的项目组成员),而核心工作内容就是提供通用测试技术解决方案,开发实现测试工具或平台,协助测试人员更好地完成测试工作和项目交付,而效率和质量也是他们最为关注的方面。</p><p>从岗位职责和工作内容可以看出,测试开发通常不会直接参与业务交付,并且他们通常也不会隶属于具体的项目组,因此对于他们的工作到底产出了多少实际的价值收益,在上面的领导或老板看来就不是那么明确,最终他们面临价值产出度量的问题也就在所难免了。</p><p>本文就围绕测试开发价值产出度量的问题,谈下我的一些思考和建议。</p><h2 id="何为业务价值?"><a href="#何为业务价值?" class="headerlink" title="何为业务价值?"></a>何为业务价值?</h2><p>我们总是在说业务价值,那业务价值究竟指的是什么?为什么同样是写代码开发系统平台,大家通常会觉得开发电商、售后平台是产出业务价值,而开发测试工具平台就不产生业务价值呢?这种想法是否正确?</p><p>其实当我们回归商业的本质,就会得知问题的答案了。对于商业公司来说,通常是以盈利为目标的,而为了达成这个目标,就需要通过业务手段,对用户提供价值,最终获得用户的买单。从这个角度来讲,决定是否对公司产生业务价值与岗位类型无关,也与开发实现了什么系统或平台无关。例如,对于提供测试类服务的公司或项目组来说,例如听云、WeTest,开发出的测试工具平台就直接面向客户,并以此获得盈利,那么参与该类项目的测试开发工程师就直接产出了业务价值。而在绝大多数非测试服务类商业公司中,测试工具平台更多是提供一种辅助手段,帮助项目组更好更快地完成业务需求交付,而并不直接创造业务价值。当然,这个问题不仅在测试开发岗位上存在,对于某些开发岗位也是同样存在的,例如开发公司内部即时通讯工具、流程审批工具、消息网关、中间件等等。</p><p>因此,对于测试开发岗位来说,不必揪着“业务价值”不放,我们完全可以从其它角度来对工作成果产出进行度量和展现。</p><h2 id="节省人天数?"><a href="#节省人天数?" class="headerlink" title="节省人天数?"></a>节省人天数?</h2><p>那要使用什么度量指标呢?</p><p>在很多时候,大家可能会想到使用“节省人天数”这样一个指标。因为测试开发的主要职责之一就是提升测试效率,那如果能度量出在使用测试工具平台后减少了多少人力投入,那么就能很好地体现该工具平台的价值。</p><p>那么要怎么计算“节省人天数”呢?之前我们使用过的方式如下:</p><ul><li>统计出项目的回归测试场景,以及在固定周期内的发版次数(假设为N次);</li><li>估算出通过人工去执行这些测试场景的耗时(假设为M人天);</li><li>统计出工具平台执行测试的耗时(通常该耗时可忽略不计);</li><li>那么节省的人天数就为:N * M</li></ul><p>乍一看,这个思路没啥问题,也能计算出具体的节省人天数。但在实际项目中尝试运作之后,我们发现该计算方式存在比较大的漏洞。</p><p>例如,某测试工具平台在 A 项目组投入使用后,通过计算,每月节省了人力10人天。可是,A 项目组的发版频率并没有改变,项目组人员编制也没有缩减,甚至根据招聘需求,人员编制还出现了增长的情况。那在这种情况下,通过计算得出节省的人力去哪儿了?</p><p>对此我们并不能给出很好的回答。事实上,测试人员借助测试工具平台从之前的重复手工工作解放出来后,他们可能花了更多的时间在需求分析上,也可能花了更多的时间在测试策略设计上。这都是我们所期望的结果,但问题在于,这些内容我们并不能很好地去统计和量化。这也就导致我们统计出的“节省人天数”缺乏说服力。</p><p>而且从更宏观的层面来看,度量项目组的质量情况时,更多是会关注交付效率和线上质量(漏测率)两个维度。交付效率,可以通过“交付需求数/投入人天数”进行计算,而线上质量(漏测率),可以通过“线上bug数/测试发现总bug数”得出。可以看出,线上质量(漏测率)与“节省人天数”基本没有关系,而交付效率方面,除非项目投入人天数真的减少了(通常不大可能),那么交付效率也很难通过“节省人天数”提升。</p><p>因此,“节省人天数”并不是一个可行的度量指标。</p><h2 id="建议的方案"><a href="#建议的方案" class="headerlink" title="建议的方案"></a>建议的方案</h2><p>那有没有其它更合适的度量指标呢?其实我也没法给出绝对正确的答案。</p><p>针对这个问题,我也请教了多位测试行业大佬,收获了诸多不错的建议。</p><p>其中,茹炳晟给出的一个观点给了我比较大的启发。我们可以反过来看,现在有了这些测试工具平台各个项目组可能都在用,那假如没有了这些测试工具平台会怎么样?是毫无影响?是变得有点不大方便?还是无法正常开展工作?问题越严重,说明工具平台本身的价值就更大。这也可以作为我们不断自我衡量工作成果产出价值大小的一种思路。</p><p>但要更好地进行量化,用户使用率会是一个比较不错的度量方式。</p><p>回归工具的属性,假如一个工具真的能帮助项目组带来价值,不管是效率优化还是质量提升方面,那么项目组成员肯定会更多地使用该工具;否则,项目组成员完全没有理由在这些测试工具平台上投入时间,因为使用也是有人力时间成本的。特别是在没有强制要求项目组使用的前提下,最终工具的覆盖用户范围和使用频率更能充分说明问题。这和当前各商业工具平台追求的用户数和日活数也是同样的思路。</p><p>因此,在 2019 年,我们也打算改变下思路:</p><ol><li><p>在质量部总体层面,不再对各项目组制定自动化测试覆盖率的目标要求,对于项目组测试人员的考核方式也不再关注测试工具平台使用的情况,最终只重点关注交付效率和线上质量两大维度(统计方式同上)。</p></li><li><p>对于测试开发团队,测试工具平台的价值展现将更多地通过覆盖用户范围和使用频率进行展现;若要更多的提升用户范围,那么就需要更主动地去挖掘业务项目组的痛点,让开发出的工具平台能帮助更多的人(目标也不再局限于测试人员)解决实际工作中遇到的问题;而要达到比较高的使用频率(日活数),那么就势必要提升平台的可靠性,对问题反馈进行更快地响应,以及进行更多的宣传和推广。</p></li></ol><p>当然,除了用户使用率(覆盖使用人数、日活数)这一类最核心的指标,我们也会关注其它的一些指标,包括:故障响应效率、平台可靠性、发现问题数、口碑评价反馈、响应需求数等。总之,这些指标都是可以明确度量和展现的,并且所有指标最终都将指向用户的实际使用情况(Adoption Rate)。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>有时候我不禁在想,做测试开发这个岗位也真挺不容易的。我们不仅需要负责需求规划和交互设计(想清楚要做什么),然后是开发和测试(将想法实现落地),并且要花费较多的时间和精力去进行推广(获取反馈及时调整),最后还要对工作成果进行度量和展现(收获价值认可,获得更多资源),只有我们开发出的工具平台最终在各业务项目中得到了很好的应用,才能说明我们的工作成果产出了价值。</p><p>这个过程跟创业真的挺像的,我也一直都是希望我所在的测试开发团队能更多地用创业的心态来对待我们的工作,而整个经历的过程,也许就是最大的乐趣所在吧。</p><blockquote><p>本文作者: debugtalk<br>本文链接: <a href="https://debugtalk.com/post/SDET-Value-Measurement/" target="_blank" rel="noopener">https://debugtalk.com/post/SDET-Value-Measurement/</a><br>版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!</p></blockquote>]]></content>
<categories>
<category>测试开发</category>
</categories>
<tags>
<tag>测试开发</tag>
</tags>
</entry>
<entry>
<title>Git命令小结</title>
<url>/2019/10/21/Git%E5%91%BD%E4%BB%A4%E5%B0%8F%E7%BB%93/</url>
<content><![CDATA[<h3 id="一、设置账户名和账户邮箱"><a href="#一、设置账户名和账户邮箱" class="headerlink" title="一、设置账户名和账户邮箱"></a>一、设置账户名和账户邮箱</h3><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">git<span class="built_in"> config </span>--global user.name <span class="string">"name"</span></span><br><span class="line">git<span class="built_in"> config </span>--global user.email <span class="string">"name@gmail.com</span></span><br><span class="line"><span class="string">git config --global http.proxy http://example.com:port</span></span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="二、验证账户名和账户邮箱"><a href="#二、验证账户名和账户邮箱" class="headerlink" title="二、验证账户名和账户邮箱"></a>二、验证账户名和账户邮箱</h3><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">git<span class="built_in"> config </span>--global user.name</span><br><span class="line">git<span class="built_in"> config </span>--global user.email</span><br></pre></td></tr></table></figure><h3 id="三、初始化仓库"><a href="#三、初始化仓库" class="headerlink" title="三、初始化仓库"></a>三、初始化仓库</h3><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">git init 会生成 <span class="selector-class">.git</span> 文件夹,关于这个仓库的配置</span><br></pre></td></tr></table></figure><h3 id="四、添加文件及文件夹"><a href="#四、添加文件及文件夹" class="headerlink" title="四、添加文件及文件夹"></a>四、添加文件及文件夹</h3><figure class="highlight dockerfile"><table><tr><td class="code"><pre><span class="line">git <span class="keyword">add</span><span class="bash"> name</span></span><br><span class="line"><span class="bash">添加所有文件</span></span><br><span class="line"><span class="bash">git add .</span></span><br></pre></td></tr></table></figure><h3 id="五、添加描述-必须添加"><a href="#五、添加描述-必须添加" class="headerlink" title="五、添加描述(必须添加)"></a>五、添加描述(必须添加)</h3><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">git</span> commit -m <span class="string">"hello world"</span></span><br></pre></td></tr></table></figure><h3 id="六、查看状态"><a href="#六、查看状态" class="headerlink" title="六、查看状态"></a>六、查看状态</h3><figure class="highlight ebnf"><table><tr><td class="code"><pre><span class="line"><span class="attribute">git status</span></span><br></pre></td></tr></table></figure><h3 id="七、查看修改的文件"><a href="#七、查看修改的文件" class="headerlink" title="七、查看修改的文件"></a>七、查看修改的文件</h3><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line">git diff <span class="comment">-- filename</span></span><br></pre></td></tr></table></figure><h3 id="八、撤销修改"><a href="#八、撤销修改" class="headerlink" title="八、撤销修改"></a>八、撤销修改</h3><figure class="highlight ebnf"><table><tr><td class="code"><pre><span class="line"><span class="attribute">git checkout filename</span></span><br></pre></td></tr></table></figure><h3 id="九、撤销修改-针对git-add提交之后的"><a href="#九、撤销修改-针对git-add提交之后的" class="headerlink" title="九、撤销修改(针对git add提交之后的)"></a>九、撤销修改(针对git add提交之后的)</h3><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">git <span class="keyword">reset</span> <span class="keyword">HEAD</span> filename</span><br><span class="line">git <span class="keyword">reset</span> [<span class="keyword">commit</span> <span class="keyword">ID</span>] </span><br><span class="line">git checkout filename</span><br></pre></td></tr></table></figure><h3 id="十、查看提交记录"><a href="#十、查看提交记录" class="headerlink" title="十、查看提交记录"></a>十、查看提交记录</h3><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">git log</span><br><span class="line">git log logid -<span class="number">1</span> -p</span><br><span class="line">-<span class="number">1</span> 表示只看到上面的一条记录</span><br><span class="line">-<span class="selector-tag">p</span> 表示查看这条记录的所有信息</span><br></pre></td></tr></table></figure><h3 id="十一、上传文件"><a href="#十一、上传文件" class="headerlink" title="十一、上传文件"></a>十一、上传文件</h3><figure class="highlight maxima"><table><tr><td class="code"><pre><span class="line">git remote add <span class="built_in">origin</span> github仓库地址</span><br><span class="line">如遇<span class="string">"fatal: remote origin already exists."</span>此问题</span><br><span class="line">输入 git remote rm <span class="built_in">origin</span></span><br><span class="line">git <span class="built_in">push</span> <span class="built_in">origin</span> master</span><br></pre></td></tr></table></figure><h3 id="十二、分支"><a href="#十二、分支" class="headerlink" title="十二、分支"></a>十二、分支</h3><figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">查看分支</span><br><span class="line">git branch</span><br><span class="line">当前分支前面会有一个*</span><br><span class="line">创建分支</span><br><span class="line">git branch version1.<span class="number">0</span></span><br><span class="line">切换分支</span><br><span class="line">git checkout version1.<span class="number">0</span></span><br><span class="line">将version1.<span class="number">0</span>同步到当前分支</span><br><span class="line">git merge version1.<span class="number">0</span></span><br><span class="line">删除分支</span><br><span class="line">git branch -D version1.<span class="number">0</span></span><br><span class="line">克隆远程项目</span><br><span class="line">git <span class="keyword">clone</span> <span class="title">git</span>@github.com:name/项目名.git</span><br><span class="line">将当前修改上传到服务器</span><br><span class="line">git push origin <span class="literal">master</span></span><br><span class="line">将远程修改同步到本地</span><br><span class="line">git fetch origin <span class="literal">master</span></span><br><span class="line">注:此方法不会将修改同步到当前分支,而是会同步到origin/<span class="literal">master</span>分支上,需要使用git merge origin/<span class="literal">master</span>同步到当前分支</span><br><span class="line">git pull origin <span class="literal">master</span></span><br><span class="line">此方法是fetch和merge两者的合并</span><br></pre></td></tr></table></figure><h3 id="十三、其他"><a href="#十三、其他" class="headerlink" title="十三、其他"></a>十三、其他</h3><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">创建密钥</span><br><span class="line"><span class="selector-tag">ssh-keygen</span> <span class="selector-tag">-t</span> <span class="selector-tag">rsa</span> <span class="selector-tag">-C</span> "<span class="selector-tag">name</span>@<span class="keyword">gmail</span>.<span class="keyword">com</span>"</span><br><span class="line">将公钥添加到<span class="keyword">github</span></span><br><span class="line"><span class="keyword">cat</span> .ssh/id_rsa.pub</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>Mac iTerm 2 + oh-my-zsh + agnoster教程(好用的终端神器)</title>
<url>/2019/07/06/Mac%20iTerm%202%20+%20oh-my-zsh%20+%20agnoster%E6%95%99%E7%A8%8B%EF%BC%88%E5%A5%BD%E7%94%A8%E7%9A%84%E7%BB%88%E7%AB%AF%E7%A5%9E%E5%99%A8%EF%BC%89/</url>
<content><![CDATA[<h2 id="Mac-Appium详细安装教程"><a href="#Mac-Appium详细安装教程" class="headerlink" title="Mac Appium详细安装教程"></a>Mac Appium详细安装教程</h2><p>一个好看的终端,先上效果图。</p><a id="more"></a><p><img data-src="https://s2.ax1x.com/2019/07/06/Z0r0Yj.png" alt="Z0r0Yj.png"></p><h4 id="下载-iTerm-2"><a href="#下载-iTerm-2" class="headerlink" title="下载 iTerm 2"></a>下载 iTerm 2</h4><p><a href="https://www.iterm2.com/" target="_blank" rel="noopener">https://www.iterm2.com/</a></p><h4 id="安装-oh-my-zsh-官网"><a href="#安装-oh-my-zsh-官网" class="headerlink" title="安装 oh-my-zsh 官网"></a>安装 oh-my-zsh 官网</h4><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sh -c <span class="string">"<span class="variable">$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)</span>"</span></span><br></pre></td></tr></table></figure><h4 id="更换默认的-shell"><a href="#更换默认的-shell" class="headerlink" title="更换默认的 shell"></a>更换默认的 shell</h4><ol><li><p>终端执行命令<br><code>chsh -s /bin/zsh</code></p></li><li><p>更换zsh之后默认启动执行脚本变为了<code>~/.zshrc</code><br>如果你之前使用的<code>.bash_profile</code>需要:<br>修改<code>~/.zshrc</code>文件,在其中添加:<code>source ~/.bash_profile</code> 和 <code>source ~/.bashrc</code></p></li></ol><h4 id="更换-agnoster-主题"><a href="#更换-agnoster-主题" class="headerlink" title="更换 agnoster 主题"></a>更换 agnoster 主题</h4><ol><li><p>打开.zshrc文件<br><code>open ~/.zshrc</code></p></li><li><p>修改<br><code>ZSH_THEME="agnoster"</code></p></li><li><p>保存重新打开 iTerm 2</p></li></ol><h4 id="下载-Powerline-fonts-字体-官网"><a href="#下载-Powerline-fonts-字体-官网" class="headerlink" title="下载 Powerline fonts 字体 官网"></a>下载 Powerline fonts 字体 官网</h4><ol><li><p>克隆<br><code>git clone https://github.com/powerline/fonts.git --depth=1</code></p></li><li><p>安装<br>cd fonts<br><code>./install.sh</code></p></li><li><p>清理<br><code>cd ..</code><br><code>rm -rf fonts</code></p></li></ol><h4 id="修改-iTerm-2-配置"><a href="#修改-iTerm-2-配置" class="headerlink" title="修改 iTerm 2 配置"></a>修改 iTerm 2 配置</h4><p>进入 Preferences… Profiles Text Font 选择 Powerline 字体<br><img data-src="https://s2.ax1x.com/2019/07/06/Z0rrpn.png" alt="Z0rrpn.png"></p><p>修改 Solarized 配色方案<br><img data-src="https://s2.ax1x.com/2019/07/06/Z0rBfs.png" alt="Z0rBfs.png"></p><h4 id="去掉前面的用户名称"><a href="#去掉前面的用户名称" class="headerlink" title="去掉前面的用户名称"></a>去掉前面的用户名称</h4><ol><li><p>打开agnoster.zsh-theme文件</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line">open ~/.oh-<span class="keyword">my</span>-zsh/themes/agnoster.zsh-theme</span><br></pre></td></tr></table></figure></li><li><p>注释 prompt_context</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">build_prompt() {</span><br><span class="line"> <span class="attribute">RETVAL</span>=$?</span><br><span class="line"> prompt_status</span><br><span class="line"> prompt_virtualenv</span><br><span class="line"><span class="comment"># prompt_context</span></span><br><span class="line"> prompt_dir</span><br><span class="line"> prompt_git</span><br><span class="line"> prompt_bzr</span><br><span class="line"> prompt_hg</span><br><span class="line"> prompt_end</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ol><p>保存重新打开 iTerm 2</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Mac</tag>
<tag>Shell</tag>
</tags>
</entry>
<entry>
<title>Mac Appium详细安装教程</title>
<url>/2019/07/06/Mac%20Appium%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B/</url>
<content><![CDATA[<h2 id="Mac-Appium详细安装教程"><a href="#Mac-Appium详细安装教程" class="headerlink" title="Mac Appium详细安装教程"></a>Mac Appium详细安装教程</h2><h3 id="1-直接安装:"><a href="#1-直接安装:" class="headerlink" title="1. 直接安装:"></a>1. 直接安装:</h3><p>Appium官网下载:<a href="https://bitbucket.org/appium/appium.app/downloads/" target="_blank" rel="noopener">https://bitbucket.org/appium/appium.app/downloads/</a></p><p>这里mac系统升级到 Sierra后,无法安装不被认可的软件,必须恢复“任何来源”,运行命令 sudo spctl –master-disable,成功后如图所示:</p><a id="more"></a><p><img data-src="https://s2.ax1x.com/2019/07/06/Z0B2vV.png" alt="Z0B2vV.png"></p><p>注意⚠️:<br>如果允许appium-doctor遇到问题,由于Appium无法探测到Mac OS 10.11以上的版本,解决办法如下所示:</p><figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"><span class="keyword">grep</span> -rl <span class="string">"Could not detect Mac OS X Version from sw_vers output:"</span> <span class="regexp">/Applications/</span>Appium.app<span class="regexp">/</span></span><br></pre></td></tr></table></figure><h4 id="终端输出如下信息"><a href="#终端输出如下信息" class="headerlink" title="终端输出如下信息"></a>终端输出如下信息</h4><p>Terminal will show : /Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/build/lib/system.js /Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/lib/system.js /Applications/Appium.app//Contents/Resources/node_modules/appium-support/build/lib/system.js /Applications/Appium.app//Contents/Resources/node_modules/appium-support/lib/system.js</p><h4 id="通过vim按照如下所示对每个文件进行编辑"><a href="#通过vim按照如下所示对每个文件进行编辑" class="headerlink" title="通过vim按照如下所示对每个文件进行编辑"></a>通过vim按照如下所示对每个文件进行编辑</h4><p><code>vim /Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/build/lib/system.js</code></p><figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line">Change <span class="keyword">From</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> <span class="variable">_arr</span> = [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>];</span><br><span class="line"> <span class="variable">_i</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">To</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> <span class="variable">_arr</span> = [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>,<span class="string">'10.12'</span>, <span class="string">'10.13'</span>, <span class="string">'10.14'</span>];</span><br><span class="line"> <span class="variable">_i</span> = <span class="number">0</span>;</span><br></pre></td></tr></table></figure><p><code>vim /Applications/Appium.app//Contents/Resources/node_modules/appium/node_modules/appium-support/lib/system.js</code></p><figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line">Change <span class="keyword">From</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">stdout = (await <span class="built_in">exec</span>(<span class="string">'sw_vers'</span>, [<span class="string">'-productVersion'</span>])).stdout;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">To</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">stdout = (await <span class="built_in">exec</span>(<span class="string">'sw_vers'</span>, [<span class="string">'-productVersion'</span>])).stdout.trim();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight livescript"><table><tr><td class="code"><pre><span class="line">Change From<span class="function"> -></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> v <span class="keyword">of</span> [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>]) {</span><br><span class="line"><span class="keyword">if</span> (stdout.indexOf(v) === <span class="number">0</span>) { <span class="keyword">return</span> v; }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">To<span class="function"> -></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> v <span class="keyword">of</span> [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>,<span class="string">'10.12'</span>, <span class="string">'10.13'</span>, <span class="string">'10.14'</span>]) {</span><br><span class="line"><span class="keyword">if</span> (stdout.indexOf(v) === <span class="number">0</span>) { <span class="keyword">return</span> v; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>vim /Applications/Appium.app//Contents/Resources/node_modules/appium-support/build/lib/system.js</code></p><figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line">Change <span class="keyword">From</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> <span class="variable">_arr</span> = [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>];</span><br><span class="line"> <span class="variable">_i</span> = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">To</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> <span class="variable">_arr</span> = [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>, <span class="string">'10.12'</span>, <span class="string">'10.13'</span>, <span class="string">'10.14'</span>];</span><br></pre></td></tr></table></figure><p><code>vim /Applications/Appium.app//Contents/Resources/node_modules/appium-support/lib/system.js</code></p><figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line">Change <span class="keyword">From</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">stdout = (await <span class="built_in">exec</span>(<span class="string">'sw_vers'</span>, [<span class="string">'-productVersion'</span>])).stdout;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">To</span> -></span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">stdout = (await <span class="built_in">exec</span>(<span class="string">'sw_vers'</span>, [<span class="string">'-productVersion'</span>])).stdout.trim();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight livescript"><table><tr><td class="code"><pre><span class="line">Change From<span class="function"> -></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> v <span class="keyword">of</span> [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>]) {</span><br><span class="line"><span class="keyword">if</span> (stdout.indexOf(v) === <span class="number">0</span>) { <span class="keyword">return</span> v; }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">To<span class="function"> -></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> v <span class="keyword">of</span> [<span class="string">'10.8'</span>, <span class="string">'10.9'</span>, <span class="string">'10.10'</span>, <span class="string">'10.11'</span>,<span class="string">'10.12'</span>, <span class="string">'10.13'</span>, <span class="string">'10.14'</span>]) {</span><br><span class="line"><span class="keyword">if</span> (stdout.indexOf(v) === <span class="number">0</span>) { <span class="keyword">return</span> v; }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-命令行安装"><a href="#2-命令行安装" class="headerlink" title="2. 命令行安装"></a>2. 命令行安装</h3><blockquote><p>brew install node # get node.js 或者直接安装:Node.js官网下载:<a href="https://nodejs.org/zh-cn/download/(直接安装的node中直接包含了npm,且路径正确,不存在关联错误的情况)" target="_blank" rel="noopener">https://nodejs.org/zh-cn/download/(直接安装的node中直接包含了npm,且路径正确,不存在关联错误的情况)</a></p></blockquote><blockquote><p>npm install -g appium # get appium</p></blockquote><blockquote><p>npm install wd # get appium client</p></blockquote><p>直接用npm下载往往不成功,这是需要通过代理来下载,我参考的文章是:<a href="https://segmentfault.com/q/1010000006025361/a-1020000006252474" target="_blank" rel="noopener">https://segmentfault.com/q/1010000006025361/a-1020000006252474</a></p><p>具体方法如下:</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">> npm i cnpm -g <span class="attribute">--registry</span>=http://registry.npm.taobao.org</span><br><span class="line"></span><br><span class="line">> cnpm i appium -g # <span class="builtin-name">get</span> appium</span><br><span class="line"></span><br><span class="line">> cnpm i wd -g # <span class="builtin-name">get</span> appium client</span><br><span class="line"></span><br><span class="line">> cnpm i appium-doctor # <span class="builtin-name">get</span> appium-doctor</span><br></pre></td></tr></table></figure><p>3.检查Appium成功安装</p><p><code>appium-doctor</code></p><p>以上有部分软件未安装,接下来弥补上。</p><h2 id="Appium配置"><a href="#Appium配置" class="headerlink" title="Appium配置"></a>Appium配置</h2><ol><li>AppStore下载Xcode直接安装即可;</li></ol><p>或者命令行安装: <code>$ xcode-select --install</code></p><ol start="2"><li><p>Android SDK 下载地址:<a href="http://tools.android-studio.org/index.php/sdk" target="_blank" rel="noopener">http://tools.android-studio.org/index.php/sdk</a></p><p>将下载的 Android SDK 解压,将得到如下目录。(具体安装步骤可以阅读 SDK Readme.txt)</p></li></ol><p> 点击运行文件tools/android, 安装步骤一步一步安装并下载完SDK后,配置环境变量</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">> vim ~/.bash_profile </span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">ANDROID_HOME</span>=/usr/local/android-sdk-macosx</span><br><span class="line"></span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">PATH</span>=<span class="variable">${PATH}</span>:${ANDROID_HOME}/tools</span><br><span class="line"></span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">PATH</span>=<span class="variable">${PATH}</span>:${ANDROID_HOME}/platform-tools</span><br></pre></td></tr></table></figure><ol start="3"><li><p>网上下载java安装后,配置环境变量 (详细参照:<a href="http://www.micmiu.com/lang/java/set-javahome-on-mac-os-x/" target="_blank" rel="noopener">http://www.micmiu.com/lang/java/set-javahome-on-mac-os-x/</a>)</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">> vim ~/.bash_profile </span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">JAVA_HOME</span>=$(/usr/libexec/java_home) # <span class="builtin-name">get</span> latest version </span><br><span class="line"></span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">PATH</span>=<span class="variable">${JAVA_HOME}</span>/bin:$PATH</span><br><span class="line"></span><br><span class="line"><span class="builtin-name">export</span> <span class="attribute">CLASSPATH</span>=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar</span><br></pre></td></tr></table></figure></li><li><p>检查Appium成功安装</p></li></ol><p><img data-src="https://s2.ax1x.com/2019/07/06/Z0Bc3q.png" alt="Z0Bc3q.png"></p><p>所有配置全部打对勾才算成功,若有打叉的,依次配置,直到全部对勾为止</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Mac</tag>
<tag>Appium</tag>
</tags>
</entry>
<entry>
<title>Google-API构建Web实时语音转文字情感分析</title>
<url>/2019/02/24/Google-API%E6%9E%84%E5%BB%BAWeb%E5%AE%9E%E6%97%B6%E8%AF%AD%E9%9F%B3%E8%BD%AC%E6%96%87%E5%AD%97%E6%83%85%E6%84%9F%E5%88%86%E6%9E%90/</url>
<content><![CDATA[<p>本教程中,我们将使用Google API中语音转文字以及自然语言分析功能,这是一个非常强大的人工智能平台,可以用来记录语音并将其转换为文本,并对文本进行实时情感分析。(Google API可能需要科学上网,但可以在文末选择国内的腾讯AI、百度AI平台进行替换)</p><img data-src="https://i.loli.net/2019/03/05/5c7d4d4beb7f1.jpg" width="400" height="400"><p>首先罗列这个app的功能</p><ul><li><input checked disabled type="checkbox"> 创建Flask Web服务器</li><li><input checked disabled type="checkbox"> 构建前端和后端</li><li><input checked disabled type="checkbox"> 通过语音录入或者键盘输入的方式保存笔记</li><li><input checked disabled type="checkbox"> 实时语音转文本</li><li><input checked disabled type="checkbox"> 实时情绪识别</li><li><input checked disabled type="checkbox"> 同时进行实时语音,文本,情感识别。</li><li><input checked disabled type="checkbox"> 在任何云服务器上面上运行Angry-Ducks语音转文字情感分析助手</li></ul><p>完整项目地址:<a href="https://github.com/angryducks/angry-ducks" target="_blank" rel="noopener">https://github.com/angryducks/angry-ducks</a><br>Youtube: <a href="https://www.youtube.com/watch?v=lHrlYdO8gLY" target="_blank" rel="noopener">https://www.youtube.com/watch?v=lHrlYdO8gLY</a><br>B站:<a href="https://www.bilibili.com/video/av44429125" target="_blank" rel="noopener">https://www.bilibili.com/video/av44429125</a></p><a id="more"></a><p>项目截图:</p><div align="center"><img data-src="https://i.loli.net/2019/02/19/5c6afef001185.png" height="500"> <img data-src="https://i.loli.net/2019/02/19/5c6aff8ace255.png" height="500"></div><h2 id="Google-API语音转文本"><a href="#Google-API语音转文本" class="headerlink" title="Google API语音转文本"></a>Google API语音转文本</h2><h3 id="下载gcloud工具并配置"><a href="#下载gcloud工具并配置" class="headerlink" title="下载gcloud工具并配置"></a>下载gcloud工具并配置</h3><ol><li>准备工作<br>设置GCP控制台项目。<br>设置一个项目<br>创建或选择一个项目。<br>为该项目启用Speech-to-Text API。<br>创建服务帐户。<br>下载私钥作为JSON。</li></ol><p>您可以随时在GCP控制台中查看和管理这些资源。</p><ol start="2"><li>暗转和初始化Cloud SDK.</li><li>身份验证</li></ol><p>对于身份验证,我们建议您使用服务帐号:即与您的 GCP 项目(而不是与特定用户)关联的 Google 帐号。无论代码在何处(本地、Compute Engine、App Engine 或内部等)运行,都可以使用服务帐号进行身份验证。如需详细了解其他身份验证类型,请参阅身份验证概览。<br>参考:<a href="https://cloud.google.com/docs/authentication/getting-started#auth-cloud-implicit-python" target="_blank" rel="noopener">https://cloud.google.com/docs/authentication/getting-started#auth-cloud-implicit-python</a></p><p><strong>发出音频转录请求</strong><br>现在您可以使用 Speech-to-Text 将音频文件转录为文字。使用以下代码示例向 Speech-to-Text API 发送 recognize 请求。</p><p>打开命令行 shell 并运行以下命令。</p><figure class="highlight stata"><table><tr><td class="code"><pre><span class="line">gcloud <span class="keyword">ml</span> speech recognize 'gs:<span class="comment">//cloud-samples-tests/speech/brooklyn.flac' \</span></span><br><span class="line"> --language-code='<span class="keyword">en</span>-<span class="keyword">US</span>'</span><br></pre></td></tr></table></figure><p>此命令请求 Speech-to-Text 转录一个 FLAC 中包含的音频,该 FLAC 在一个可公开访问的位置进行托管。</p><p>如果请求成功,服务器将返回 JSON 格式的响应:</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"results"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"alternatives"</span>: [</span><br><span class="line"> {</span><br><span class="line"> <span class="attr">"confidence"</span>: <span class="number">0.9840146</span>,</span><br><span class="line"> <span class="attr">"transcript"</span>: <span class="string">"how old is the Brooklyn Bridge"</span></span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line"> }</span><br><span class="line"> ]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>恭喜!您已向 Cloud Speech-to-Text 发送了您的第一个请求!</p><h2 id="Google-API情感分析"><a href="#Google-API情感分析" class="headerlink" title="Google API情感分析"></a>Google API情感分析</h2><p>点击链接开通即可<br><a href="https://cloud.google.com/natural-language/?_ga=2.224506522.-145586126.1544583292" target="_blank" rel="noopener">https://cloud.google.com/natural-language/?_ga=2.224506522.-145586126.1544583292</a></p><h2 id="本项目语音转文本部分"><a href="#本项目语音转文本部分" class="headerlink" title="本项目语音转文本部分"></a>本项目语音转文本部分</h2><p>Cloud Speech-to-Text 可实现将 Google 语音识别技术轻松集成到开发者应用中。向 Speech-to-Text API服务发送音频,即可收到文字转录结果。</p><p><strong>注意:</strong> setting.py里面的GOOGLE_API = ‘google-api.json’需要配置成自己的json文件,文件名可以修改</p><p>首先我们需要借助Recorder.js进行录音。建议使用Chrome和Firefox浏览器</p><p>然后通过aduck.js进行Recorder按钮的控制</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">$( <span class="built_in">document</span> ).ready( <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> aduck = {};</span><br><span class="line"></span><br><span class="line"> aduck.init = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> aduck.createWebSocket();</span><br><span class="line"> aduck.initRecordButton();</span><br><span class="line"> aduck.initSentimentButtom();</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.getRecorder = <span class="function"><span class="keyword">function</span> (<span class="params"> s </span>) </span>{</span><br><span class="line"> context = <span class="keyword">new</span> AudioContext();</span><br><span class="line"> source = context.createMediaStreamSource( s );</span><br><span class="line"> recorder = <span class="keyword">new</span> Recorder( source, {</span><br><span class="line"> numChannels: <span class="number">1</span>,</span><br><span class="line"> } );</span><br><span class="line"> recorder.record();</span><br><span class="line"> aduck.setInterval();</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.createWebSocket = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> socket = io.connect( <span class="string">'//'</span> + <span class="built_in">document</span>.domain + <span class="string">':'</span> + location.port );</span><br><span class="line"> socket.on( <span class="string">'connect'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log( <span class="string">'Connected!'</span> );</span><br><span class="line"> } );</span><br><span class="line"></span><br><span class="line"> socket.on( <span class="string">'transcript'</span>, <span class="function"><span class="keyword">function</span> (<span class="params"> data </span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'data: '</span>, data);</span><br><span class="line"> $( <span class="string">'.recognized-text'</span> ).append( <span class="string">' '</span> + data );</span><br><span class="line"> } );</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.setInterval = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="string">'undefined'</span> !== <span class="keyword">typeof</span> recorder ) {</span><br><span class="line"> exportInterval = setInterval( <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> recorder.exportWAV( <span class="function"><span class="keyword">function</span> (<span class="params"> blob </span>) </span>{</span><br><span class="line"> recorder.clear();</span><br><span class="line"> <span class="keyword">if</span> ( <span class="string">'undefined'</span> !== <span class="keyword">typeof</span> socket ) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"blob: "</span>, blob);</span><br><span class="line"> socket.emit( <span class="string">'stream'</span>, blob );</span><br><span class="line"> }</span><br><span class="line"> } );</span><br><span class="line"> }, <span class="number">5000</span> );</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.clearInterval = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> clearInterval( exportInterval );</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.initRecorder = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> ( <span class="string">'undefined'</span> === <span class="keyword">typeof</span> recorder ) {</span><br><span class="line"> navigator.mediaDevices.getUserMedia( {</span><br><span class="line"> audio: <span class="literal">true</span>,</span><br><span class="line"> video: <span class="literal">false</span></span><br><span class="line"> } ).then( aduck.getRecorder );</span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> aduck.initRecordButton = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> $( <span class="string">'.recorder'</span> ).click( <span class="function"><span class="keyword">function</span> (<span class="params"> e </span>) </span>{</span><br><span class="line"> e.preventDefault();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ( $( <span class="keyword">this</span> ).hasClass( <span class="string">'is-recording'</span> ) ) { <span class="comment">// Stop Recording</span></span><br><span class="line"> $( <span class="keyword">this</span> ).removeClass( <span class="string">'active is-recording btn-danger'</span> ).text( <span class="string">'Start Recording'</span> );</span><br><span class="line"> <span class="keyword">if</span> ( <span class="string">'undefined'</span> !== <span class="keyword">typeof</span> recorder ) {</span><br><span class="line"> recorder.stop();</span><br><span class="line"> }</span><br><span class="line"> aduck.clearInterval();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// Start Recording</span></span><br><span class="line"> $( <span class="keyword">this</span> ).addClass( <span class="string">'active is-recording btn-danger'</span> ).text( <span class="string">'Stop Recording'</span> );</span><br><span class="line"> <span class="keyword">if</span> ( <span class="string">'undefined'</span> !== <span class="keyword">typeof</span> recorder ) {</span><br><span class="line"> recorder.record();</span><br><span class="line"> aduck.setInterval();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> aduck.initRecorder();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } );</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> $( aduck.init() );</span><br><span class="line">} );</span><br></pre></td></tr></table></figure><p>后端通过processData函数进行语音转文字处理,并显示在前端界面:</p><figure class="highlight nix"><table><tr><td class="code"><pre><span class="line"><span class="attr">app</span> = Flask(__name__)</span><br><span class="line"></span><br><span class="line"><span class="attr">socketio</span> = SocketIO(app)</span><br><span class="line"></span><br><span class="line"><span class="comment"># credentials, project = google.auth.default()</span></span><br><span class="line"><span class="attr">credentials</span> = service_account.Credentials.from_service_account_file(</span><br><span class="line"> setting.GOOGLE_API)</span><br><span class="line"><span class="attr">credentials</span> = credentials.with_scopes(</span><br><span class="line"> ['https://www.googleapis.com/auth/cloud-platform'])</span><br><span class="line"></span><br><span class="line"><span class="attr">client</span> = speech.SpeechClient(<span class="attr">credentials=credentials)</span></span><br><span class="line"></span><br><span class="line">def processData(data):</span><br><span class="line"> <span class="attr">content</span> = data</span><br><span class="line"> <span class="attr">audio</span> = types.RecognitionAudio(<span class="attr">content=content)</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">config</span> = types.RecognitionConfig(</span><br><span class="line"> <span class="attr">encoding=enums.RecognitionConfig.AudioEncoding.LINEAR16,</span></span><br><span class="line"> <span class="attr">language_code='en-US')</span></span><br><span class="line"></span><br><span class="line"> try:</span><br><span class="line"> <span class="attr">response</span> = client.recognize(<span class="attr">config=config,</span> <span class="attr">audio=audio)</span></span><br><span class="line"> for result <span class="keyword">in</span> response.results:</span><br><span class="line"> print('transcript: ', result.alternatives)</span><br><span class="line"> emit('transcript', result.alternatives[<span class="number">0</span>].transcript)</span><br><span class="line"> except RetryError as e:</span><br><span class="line"> print(<span class="string">"Error: {0}"</span>.format(e))</span><br><span class="line"> except:</span><br><span class="line"> print(<span class="string">"Error:"</span>, sys.exc_info()[<span class="number">0</span>])</span><br></pre></td></tr></table></figure><p>一开始调用的时候浏览器将会弹出提示框,请求允许获得麦克风权限。<br>至此,语音转文本部分已经完成,下面我们继续实现文本情感。</p><h2 id="实时文本情感"><a href="#实时文本情感" class="headerlink" title="实时文本情感"></a>实时文本情感</h2><p>首先在aduck.js里面添加动态按钮,并对情感进行动态显示</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">aduck.initSentimentButtom = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> $( <span class="string">'.sentiment'</span> ).click( <span class="function"><span class="keyword">function</span> (<span class="params"> e </span>) </span>{</span><br><span class="line"> e.preventDefault();</span><br><span class="line"> <span class="comment">//log</span></span><br><span class="line"> <span class="built_in">console</span>.log($(<span class="keyword">this</span>))</span><br><span class="line"> <span class="keyword">var</span> reconginized_text = $( <span class="string">'.recognized-text'</span> ).val();</span><br><span class="line"> <span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line"> <span class="comment">//log</span></span><br><span class="line"> <span class="built_in">console</span>.log( $( <span class="string">'.recognized-text'</span> ).val());</span><br><span class="line"> <span class="comment">// xhr.open("POST", '/sentiment?data=' + reconginized_text, true);</span></span><br><span class="line"> <span class="comment">// xhr.send().then(function() {</span></span><br><span class="line"> <span class="comment">// console.log('sentiment1: ', xhr.response);</span></span><br><span class="line"> <span class="comment">// });</span></span><br><span class="line"> <span class="comment">// console.log('sentiment: ', xhr.response)</span></span><br><span class="line"> <span class="keyword">const</span> url = <span class="string">"/sentiment?data="</span> + reconginized_text;</span><br><span class="line"> fetch(url, {<span class="attr">method</span>: <span class="string">'POST'</span>})</span><br><span class="line"> .then( <span class="function"><span class="params">response</span> =></span> response.json())</span><br><span class="line"> .then( <span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="keyword">typeof</span> res);</span><br><span class="line"> <span class="keyword">if</span> (<span class="built_in">parseFloat</span>(res.score) > <span class="number">0.0</span>) {</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">"duck-img"</span>).src=<span class="string">"./static/images/happy.png"</span>;</span><br><span class="line"> <span class="comment">// document.getElementById("angry-img").style.visibility="hidden";</span></span><br><span class="line"> <span class="comment">// document.getElementById("neu-img").style.visibility="hidden";</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">parseFloat</span>(res.score) < <span class="number">0.0</span>) {</span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">"duck-img"</span>).src=<span class="string">"./static/images/angry.png"</span>;</span><br><span class="line"> <span class="comment">// document.getElementById("happy-img").style.visibility="hidden";</span></span><br><span class="line"> <span class="comment">// document.getElementById("neu-img").style.visibility="hidden";</span></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">parseFloat</span>(res.score) == <span class="number">0.0</span>) {</span><br><span class="line"> <span class="comment">// document.getElementById("angry-img").style.visibility="hidden";</span></span><br><span class="line"> <span class="comment">// document.getElementById("happy-img").style.visibility="hidden";</span></span><br><span class="line"> <span class="built_in">document</span>.getElementById(<span class="string">"duck-img"</span>).src=<span class="string">"./static/images/neu.png"</span>;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>实时文本情感分析detect_sentiment函数实现:</p><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">def detect_sentiment(<span class="type">text</span>):</span><br><span class="line"> credentials = service_account.Credentials.from_service_account_file(</span><br><span class="line"> setting.GOOGLE_API)</span><br><span class="line"> credentials = credentials.with_scopes(</span><br><span class="line"> [<span class="string">'https://www.googleapis.com/auth/cloud-platform'</span>])</span><br><span class="line"> client = <span class="keyword">language</span>.LanguageServiceClient(credentials=credentials)</span><br><span class="line"></span><br><span class="line"> document = <span class="keyword">types</span>.Document(</span><br><span class="line"> content=<span class="type">text</span>,</span><br><span class="line"> <span class="keyword">type</span>=enums.Document.<span class="keyword">Type</span>.PLAIN_TEXT)</span><br><span class="line"></span><br><span class="line"> # Detects the sentiment <span class="keyword">of</span> the <span class="type">text</span></span><br><span class="line"> sentiment = client.analyze_sentiment(document=document).document_sentiment</span><br><span class="line"></span><br><span class="line"> print(<span class="string">'Text: {}'</span>.format(<span class="type">text</span>))</span><br><span class="line"> print(<span class="string">'Sentiment: {}, {}'</span>.format(sentiment.score, sentiment.magnitude))</span><br><span class="line"> <span class="keyword">return</span> sentiment</span><br></pre></td></tr></table></figure><p>当此函数被调用时,会返回文本情感分析结果。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在人工智能发展的今天,情感分析可以帮助我们对我们所说的话进行实时分析,协助我们对我们的情感进行判断。语音助手逐渐走进日常的生产和生活中。这样一个app可以帮助我们快速构建可以部署的Web人工智能服务。</p><p><strong>如果你需要使用更多API实现更多的功能,可以试试下面的几个第三方API:</strong><br><a href="https://www.faceplusplus.com.cn" target="_blank" rel="noopener">Face++人工智能开放平台</a><br><a href="https://ai.qq.com" target="_blank" rel="noopener">腾讯AI开放平台</a><br><a href="http://ai.baidu.com" target="_blank" rel="noopener">百度AI开放平台</a></p><h2 id="作者:"><a href="#作者:" class="headerlink" title="作者:"></a>作者:</h2><ol><li><a href="https://github.com/nature1995" target="_blank" rel="noopener">nature1995</a> | 龚子然</li><li><a href="https://github.com/zfz" target="_blank" rel="noopener">zfz</a> | 张方舟</li><li><a href="https://github.com/zzdqqqq" target="_blank" rel="noopener">zzdqqqq</a> | 张子洞</li><li><a href="https://github.com/zlaomin" target="_blank" rel="noopener">zlaomin</a> | 曾宇晨</li></ol><h2 id="License"><a href="#License" class="headerlink" title="License"></a>License</h2><p>本软件根据GNU通用公共许可证v3.0许可证授权。 有关更多信息,请阅读该文件LICENSE.<br>请遵守开源协议,即便是在中国!!!</p>]]></content>
<categories>
<category>AI</category>
</categories>
<tags>
<tag>Google</tag>
<tag>Speech to Text</tag>
<tag>Sentiment analysis</tag>
</tags>
</entry>
<entry>
<title>5G安全性规范与协议漏洞的全面分析</title>
<url>/2019/01/16/5G%E5%AE%89%E5%85%A8%E6%80%A7%E8%A7%84%E8%8C%83%E4%B8%8E%E5%8D%8F%E8%AE%AE%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%85%A8%E9%9D%A2%E5%88%86%E6%9E%90/</url>
<content><![CDATA[<p>本文对当前的5G协议安全规范进行了全面分析。作为本次研究的一部分,我们列举了协议存在的漏洞,并将其带入实际场景,分析潜在的安全威胁。</p><h2 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h2><p>第三代合作计划(3GPP)在2018年3月,发布了第15版移动通信系统规范,为第五代移动通信(5G)奠定了基础。5G网络的目标是提供无处不在的高速、低延迟网络连接,以更好的应用于高清视频和物联网的场景,同时服务于大规模通信以及实时控制的需求。因此,最新一代移动通信的设计原则是支持更高承载量、支持更高数据速率、减少延迟、支持大规模设备连接,从而实现触觉互联网(Tactile Internet)、增强现实(AR)、虚拟现实(VR)以及智慧车辆互联。</p><p>与此前的2G、3G和4G(LTE)一样,安全性对于5G通信网络和服务至关重要,因为目前,移动系统正在为全世界数十亿人提供连接。此外,蜂窝通信网络是当前新型关键基础设施的连接基石,包括智能电网、第一响应单元(First Responder Units)和先进的军事自组织网络。5G的出现将诞生新的网络和应用,包括用于辅助驾驶或自动驾驶的下一代车载网络,以及智慧城市和智慧社区。</p><p>传统的2G网络存在着一些安全弱点,最严重的一个问题就是网络和用户设备(UE)之间缺乏相互认证。正因如此,从3G开始,安全性便始终成为了移动通信的关键设计考虑要素之一。LTE实现了强大的加密和完整性保护算法,支持基于对称密钥的相互认证系统,该系统安全的存储在通用用户识别模块(USIM)或SIM中,以及运营商的归属用户服务器(HSS)上。然而,近年来的安全研究又发现了LTE协议固有的一系列漏洞。例如,大量的预身份验证消息以明文形式发送,并且该消息可以被利用发起拒绝服务(DoS)攻击,并从用户设备(UE)处获得移动用户的位置信息。</p><a id="more"></a><p>LTE规范的第一版,Release 8,发布于2007年。在很久之后,安全研究人员才发现其主要的安全漏洞,并首次在公开文献中报告。之所以时隔这么久,一个原因在于没有可用且价格合理的LTE安全研究工具。直到最近几年,在个人计算机上运行的LTE软件库和能够使用现有商业软件定义的无线电外设才达到足够的成熟度。只有这些环境可以被安全研究者使用,他们才能对LTE进行深入的研究,从而也就能够取得良好的研究成果,发现许多协议上的漏洞。</p><p><img data-src="https://i.loli.net/2019/01/17/5c3feecd5bb16.jpg" alt="1.jpg"></p><p>在5G网络的定义和规范建立过程中,安全性被作为一个关键的考虑要素和核心问题。自从NR和5GC的通信协议建立以来,他们已经在解决已知的LTE协议漏洞方面付出了大量的努力,特别是在防范国际移动用户标识符(IMSI)捕获器Stingrays这一方面。因此,5G协议引入了用户永久标识符(Subscriber Permanent Identifier,SUPI)和用户隐藏标识符(Subscriber Concealed Identifier,SUCI)的概念。更重要的是,5G规范中引入了基于公钥基础设施(PKI)的安全体系结构,允许验证和鉴别源自5GC的控制面消息(Control Plane Messages)。</p><p>尽管5G协议在努力设计其安全架构,但仍然存在许多不安全协议的边缘案例。攻击者可利用这些案例,来对用户实现拒绝服务攻击,或者拦截敏感用户信息。此外,5G协议规范所基于的场景可能难以符合实际,从而导致不安全的网络配置。具体而言,5G所有安全性的基础都是SIM卡中保存的运营商公钥,只有在“全世界所有移动设备的所有SIM卡中都保存了全世界所有运营商的公钥”这一种情况下,才能保证该协议的绝对安全。此外,所有的运营商也必须负责保证自己的私钥是安全的。在全世界范围内,密钥管理和轮换机制显然是不可行的,并且也不在标准规范之中。并且,受政治因素影响,密钥分配机制可能不会被全球所有国家和地区所采用。一旦上述过程中出现了任何问题,都可能会导致不安全的协议实现或ISMI捕获的风险。</p><p>本文对当前的5G协议安全规范进行了全面分析。作为本次研究的一部分,我们列举了协议存在的漏洞,并将其带入实际场景,分析潜在的安全威胁。此外,我们还讨论了5G安全性的要求和前提条件,重点关注了全球采用(Global Adoption)及其产生的结果。最后,我们还分析了无线电接入网安全相关的体系结构和过程。但是,关于5GC安全性的分析不在本文的讨论范畴之内。</p><p>本文主要内容如下:第二章中介绍了5G安全体系结构和组成部分,第三章讨论了3GPP Release 15规范中提出的5G安全要求和过程,第四章详细分析了5G规范的主要安全挑战,重点说明了其存在的协议漏洞利用和敏感信息泄漏的潜在风险,第五章分析已知LTE协议漏洞是否会影响到5G的安全性,最后在第六章中总结了我们的研究结果。</p><h2 id="二、5G安全体系结构"><a href="#二、5G安全体系结构" class="headerlink" title="二、5G安全体系结构"></a>二、5G安全体系结构</h2><p>5G的安全体系结构由用户设备(UE)、无线电接入网络(RAN)、核心网络(CN)和应用组成。该体系结构可以简化为应用层、服务层和传输层。下图为服务层和传输层的简化图:</p><p><img data-src="https://i.loli.net/2019/01/17/5c3feecbef301.jpg" alt="2.jpg"></p><p>在整个网络和终端用户组件之中,分别定义了不同的安全特性,这些特性组合在一起,共同创建了一个安全的系统设计方案:</p><ul><li>网络访问安全性:(I):使用户设备(UE)能够经过认证,并且安全的访问网络服务的一系列功能或机制。</li><li>网络域安全(II):使核心网络节点能够安全地交换信令数据(Signaling Data)和用户平面数据(User Plane Data)的一系列功能。</li><li>用户域安全(III):保护用户访问移动设备和移动服务的一系列功能。</li><li>基于服务的体系结构(SBA)和域安全性(IV):SBA域中的一系列功能,包括网元注册、发现和授权,以及对基于服务的接口的保护。</li><li>安全性的可见与可配置(未在图1标明):使用户能够获知安全功能是否正在运行,同时使用户能够对安全功能进行配置。</li></ul><p>5G规范定义了许多网络功能及其接口,允许无线电接入网络(RAN)、核心网络(CN)和外部网络之间的数据流传递。下图展示了简化后的5G网络结构。5G网络功能和安全功能为5G移动通信系统的开发提供了灵活而又安全的设计。</p><p><img data-src="https://i.loli.net/2019/01/17/5c3feecd1dd95.jpg" alt="3.jpg"></p><p>下面列举了一些重要的3GPP网络概念和组件,用于定义和分析5G的安全体系结构、要求和过程:</p><ul><li>5G-GUTI:5G全球唯一临时标识符(5G Global Unique Temporary Identifier),考虑到安全需要,用于限制SUPI的信令。</li><li>5GC:5G核心网(5G Core)。</li><li>AKA:认证和密钥协议(Authentication and Key Agreement),用于蜂窝网络中无线网络接入的安全协议。</li><li>ARPF:认证凭据存储和处理功能(Authentication Credential Repository and Processing Function),将长期安全证书和密钥存储在5G-C中。</li><li>AUSF:认证服务器功能(Authentication Server Function),用于归属网络的5G安全过程。</li><li>CP:控制面(Control Plane),用于控制数据信令层面。</li><li>EAP:可扩展身份验证协议(Extensible Authentication Protocol),认证框架,用于密钥和参数的传输。</li><li>EPC:演进分组核心网(Evolved Packet Core),LTE无线核心网络。</li><li>EPS:演进分组系统(Evolved Packet System),LTE无线电接入和核心网络。</li><li>HE:归属环境(Home Environment),包含用户配置文件、标识符和订阅信息的数据库。</li><li>IMSI:国际移动用户识别码(International Mobile Subscriber Identity),用于2G、3G、4G系统,作为识别用户的唯一方式。</li><li>MCC-MNC:移动国家代码-移动网络代码(Mobile Country Code – Mobile Network Code),由基站广播,作为识别运营商的唯一方式。</li><li>ME:移动设备(Mobile Equipment),移动终端。</li><li>NAS:非接入层(Non-Access Stratum),与认证、其他安全功能和控制面机制相关的网络层。</li><li>PKI:公钥基础设施(Public Key Infrastructure),分层密钥的一种,用于创建、管理、分发和使用数字证书以及管理公钥加密。</li><li>RRC:无线资源控制(Radio Resource Control),在RAN管理上层的无线电资源。</li><li>SBA:基于服务的体系结构(Service-Based Architecture),支持切片(Slicing)和虚拟化,从而在每个服务的基础上优化网络性能。</li><li>SEAF:安全锚功能(Security Anchor Function),是5G-C功能之一,创建统一的锚键,提供给UE使用,在网络中用于主要的认证和后续通信保护。</li><li>SMF:会话管理功能(Session Management Function),是5G-C中SBA的一部分,负责协议数据单元与UE进行交换,包括策略和计费。</li><li>SN:服务网络(Serving Network),为UE的网络,可以是归属网络或访客网络。</li><li>SUCI:用户隐藏标识符(Subscription Concealed Identifier),使用运营商的公钥对SUPI进行加密。</li><li>SUPI:用户永久标识符(Subscription Permanent Identifier),是5G用户的永久身份,相当于IMSI。</li><li>TAU:跟踪区域更新(Tracking Area Update),由处于空闲状态的UE发送的消息,以通知网络其跟踪区域(TA)。</li><li>UDM:统一数据管理(Unified Data Management),支持ARPF的5GC功能。</li><li>UE:用户设备(User Equipment),由ME和USIM组成,提供对用户服务的访问。</li><li>UP:用户面(User Plane),用户数据信令层面。</li><li>USIM:通用用户识别模块(Universal Subscriber Identity Module),也称为SIM,硬件安全存储,其中包含IMSI(或SUPI),以及用于用户身份验证和其他AKA功能的密钥。</li></ul><h2 id="三、3GPP-5G规范安全需求及实现"><a href="#三、3GPP-5G规范安全需求及实现" class="headerlink" title="三、3GPP 5G规范安全需求及实现"></a>三、3GPP 5G规范安全需求及实现</h2><p>5G的安全体系结构是在定义了一系列安全要求、功能和实现的基础之上构建的。因此,我们将继续进行分析。下面列出了5G无线电接入网络(RAN)的主要安全要求和相应的实现。在其中,包含了一些可能导致安全漏洞的要求和实现,我们将会在后续章节中进一步讨论这些安全漏洞及其潜在影响。</p><h3 id="1-通用安全需求:"><a href="#1-通用安全需求:" class="headerlink" title="1. 通用安全需求:"></a>1. 通用安全需求:</h3><p>缓解Bidding Down攻击。<br>相互进行认证。用户设备、访问和服务网络授权。<br>允许未经认证的紧急服务。实现:在身份验证过程中,使用EAP-AKA和5G AKA方法。</p><h3 id="2-用户设备与5G基站-UE-and-gNB"><a href="#2-用户设备与5G基站-UE-and-gNB" class="headerlink" title="2. 用户设备与5G基站(UE and gNB)"></a>2. 用户设备与5G基站(UE and gNB)</h3><p>安全需求:</p><ul><li>通过加密,保护用户和信令数据。一旦连接到5G基站,就考虑启用用户设备安全功能和服务网络的安全功能。支持零加密(Null Encryption)。保密性保护可以选择是否启用。</li><li>用户和信令数据完整性保护和重放保护。一旦连接到5G基站,就考虑启用用户设备安全功能和服务网络的安全功能。支持零完整性保护(Null Integrity Protection)。用户数据的完整性保护可以选择是否启用。RRC和NAS信令保护强制启用,但存在例外,比如未经认证的紧急会话。</li></ul><p>实现:从密钥体系中导出、分发和协商密钥,支持128位和256位密钥加密。对于网络实体中的每一个密钥,用户设备负责存储相应的密钥,根密钥存储在USIM中。</p><h3 id="3-用户设备-UE"><a href="#3-用户设备-UE" class="headerlink" title="3. 用户设备(UE)"></a>3. 用户设备(UE)</h3><p>安全需求:</p><ul><li>通过使用防篡改的安全硬件组件,实现对用户凭据的安全存储和安全处理。</li><li>通过使用临时或隐藏的用户标识符(5G-GUTI和SUPI)获取用户隐私。支持零计划(Null-scheme)方案,在归属网络未提供公钥时使用,该归属网络控制用户隐私及密钥的提供和更新。</li></ul><p>实现:如果由归属运营商提供,USIM将存储用于隐藏SUPI的归属网络公钥。</p><h3 id="4-5G基站-gNB"><a href="#4-5G基站-gNB" class="headerlink" title="4. 5G基站(gNB)"></a>4. 5G基站(gNB)</h3><p>安全需求:</p><ul><li>通过证书授权设置和配置,属于可选项。</li><li>密钥管理,可选用基于5G PKI的架构。</li><li>密钥的安全环境,UP和CP数据存储及处理。<br>实现:认证和密钥导出可以由网络发起,因为操作方决定什么时间存在活动的NAS连接。</li></ul><h4 id="1-核心框架"><a href="#1-核心框架" class="headerlink" title="(1) 核心框架"></a>(1) 核心框架</h4><p>5G的安全实现基于分层的密钥派生、分发和管理框架之上。密钥存储在很多网络实体之中。长期密钥K由UDM层的ARPF负责存储,USIM保留该对称密钥在用户那里的副本。其他的所有密钥,都是从该密钥派生而来的。</p><h4 id="2-认证和归属控制"><a href="#2-认证和归属控制" class="headerlink" title="(2) 认证和归属控制"></a>(2) 认证和归属控制</h4><p>3GPP建立了EAP-AKA和5G AKA的认证方法,并要求5G UE和5GC必须支持这两种认证方法。这些安全模式用于相互身份验证和后续服务安全性保证。5G UE在其注册请求中需要使用安全的5G-GUTI或SUCI,并从中选择一种认证方法来启动认证过程。当使用EAP-AKA时,UE作为对等体,而5GC SEAF和AUSF分别作为传递服务器和后端认证服务器。5G AKA则是通过向归属网络提供UE从访客网络成功认证的证明,来增强EPS AKA的安全性。</p><p>增加的归属控制(Home Control)被认为能够有效防止某些类型的欺骗。拟定的5G框架中支持实施这样的过程,但它们被认为超出了标准规范的范围:归属网络采取什么样的方式进行认证确认(或不进行认证确认)取决于运营商的策略,而不是标准化的过程。针对其他的安全实现,也有很多功能超出了协议规范的范围,可能会导致不安全的边缘情况发生。</p><h4 id="3-安全上下文"><a href="#3-安全上下文" class="headerlink" title="(3) 安全上下文"></a>(3) 安全上下文</h4><p>5G安全规范为不同的场景定义了许多安全上下文,包括单个5G服务网络、跨多个服务网络、5G和EPS网络之间。当UE向两个服务网络注册时,这两个网络必须独立地维护和使用其自身的安全上下文。当UE注册到同一个公共陆地移动网(Public Land Mobile Network)中的两个服务网络时(3GPP和非3GPP),UE会与这些网络建立两个独立的NAS平面连接,但会使用由一组密钥和安全算法组成的公共NAS安全上下文。</p><h4 id="4-状态转换和网络切换"><a href="#4-状态转换和网络切换" class="headerlink" title="(4) 状态转换和网络切换"></a>(4) 状态转换和网络切换</h4><p>此外,还定义了在状态转换和网络切换的过程中,维持或忽略安全上下文的实现方式。规范中提出,如何配置切换过程中的安全性,取决于运营商的策略。这一部分实际上要在运营商的安全需求中体现,因此在切换期间的安全性是一个可选项,没有通过标准来强制执行,这可能导致许多运营商实施不安全的切换过程。</p><h4 id="5-非接入层"><a href="#5-非接入层" class="headerlink" title="(5) 非接入层"></a>(5) 非接入层</h4><p>在公共NAS安全上下文中,具有其中每个NAS连接的参数,支持对两个活动NAS连接的加密分离和重放保护。NAS使用128位加密算法来保证完整性和机密性。但是需要注意的是,这里也支持零加密和零完整性保护。如果UE不存在NAS安全上下文,那么初始的NAS消息将会以明文发送,其中包含用户标识符(例如SUCI或GUTI)和UE安全特性等内容。</p><h4 id="6-无线资源控制"><a href="#6-无线资源控制" class="headerlink" title="(6) 无线资源控制"></a>(6) 无线资源控制</h4><p>RRC的完整性和机密性保护由UE和nGB之间的分组数据汇聚协议(Packet Data Convergence Protocol,PDCP)层提供,并且PDCP下面的层不会受到完整性保护。当完整性保护启用时,除非所选的完整性保护算法是NIA0(零完整性保护),否则应该同时启用重放保护。RRC完整性检查会同时在ME和gNB中执行。如果在完整性保护启动后,发现有消息没有通过完整性检查,那么相关消息会被丢弃。</p><h4 id="7-用户层"><a href="#7-用户层" class="headerlink" title="(7) 用户层"></a>(7) 用户层</h4><p>在PDU会话建立过程中,SMF应为gNB的协议数据单元(PDU)会话提供用户面(UP)安全策略。如果没有为数据无线承载(DRB)激活用户面完整性保护,那么gNB和UE就不会为DRB实现完整性保护。如果没有为DRB激活用户面加密,那么gNB和UE就不会加密DRB业务的流量。本地SMF能够覆盖从归属SMF接收的用户面安全策略中的机密性选项。</p><h4 id="8-用户ID隐私保护"><a href="#8-用户ID隐私保护" class="headerlink" title="(8) 用户ID隐私保护"></a>(8) 用户ID隐私保护</h4><p>SUCI是5G永久用户标识SUPI的隐藏版本,从而防止暴露SUPI。SUCI使用运营商的公钥,由SUPI生成。零保护方案适用于三种情况:未认证的紧急会话、归属网络进行了相应配置、尚未提供运营商公钥。</p><p>5G规范还定义了临时标识符5G-GUTI,从而尽最大程度防止SUPI或SUCI的泄露。5G-GUTI将由UE触发重新分配,而重分配的时间间隔是在具体实现中确定的。</p><h2 id="四、5G的安全挑战和潜在漏洞"><a href="#四、5G的安全挑战和潜在漏洞" class="headerlink" title="四、5G的安全挑战和潜在漏洞"></a>四、5G的安全挑战和潜在漏洞</h2><p>在第二章和第三章中我们已经分析过,5G移动网络实现了类似于LTE系统的安全架构,并在建立信任和安全性方面与之相比有了明显的提升。准5G通信系统的所有安全功能都基于对称密钥,这些密钥安全地存储在SIM和HSS中。基于共享密钥Ks,4G UE可以认证网络的合法性,同时网络也可以认证UE。加密保护和完整性保护的密钥是从Ks派生的。基于此的对称密钥安全体系结构会导致通信终端(UE)在NAS附加密码握手(NAS Attach Cryptographic Handshake)之前无法验证交换的任何信息的真实性和有效性。这一点,也被广泛认为是造成大多数已知LTE协议漏洞利用的根本原因。</p><p>在5G安全体系结构中,有效应对了这一预认证消息的挑战,并使得IMSI捕获器在5G通信环境中失效。通过引入运营商公钥和证书的概念,5G系统基于5G PKI架构的保护,提供了终端用户和移动运营商建立根信任的工具。借助烧录到SIM卡中的公钥和证书,运营商可以使用他们的密钥来生成和签署消息,并且这些消息可以被UE验证。此外,5G UE能对其自身进行识别,而无需完全公开SUPI。</p><p>这种新的安全框架和体系结构被认为是保护新兴5G移动网络的基础。但是,我们对其安全体系结构进行了更深入的分析,随后发现了仍需解决的一些安全漏洞。下面的表格中列出了一些5G的安全挑战,以及其根本原因和造成的影响。包括LTE在内的任何通信协议,无论其安全体系有多强大,无论加密算法的复杂程度有多高,但只要存在一个边缘情况或不安全功能,整个系统的安全性都会瞬间瓦解。例如,在LTE中,IMSI应该仅在移动电话第一次开机时才会发送,但是存在这样的情况:网络可以通过预认证消息,请求UE使用IMSI识别其自身。</p><p><img data-src="https://i.loli.net/2019/01/17/5c3feecd627ed.jpg" alt="4.jpg"></p><p>只有在全球范围内都保证5G规范的合规,5G的安全基础架构才具有可靠保障。这要求运营商必须在每一张SIM卡中内置所有国家中全部运营商的公钥或证书。然而,更有可能的是,一些运营商不会实施全部5G安全功能,或者根据3GPP TR 33.899的要求严格执行,不实施其范围之外的要求。此外,预计还有一些国家会禁用某些特定国家或运营商的证书,目前已经有这样实践的案例。综上所述,全球采用并严格实施5G安全功能和实现的可能性极小。</p><p>由于SIM卡不会保存所有移动运营商和国家的公钥或证书,因此UE和运营商有两个选择:一是明确阻止不存在相应信息的运营商,同时也要处理由此产生的公关和舆论影响;二是允许这种边缘情况存在,同时也就意味着对整体的安全性产生了一定影响。而5G安全规范采用的是后者,明确规定:如果没有为用户的USIM提供服务网络,那么用户身份将不受傲虎。值得注意的是,这也就意味着,在5G中仍有可能捕获到IMSI或SUPI。</p><p>此外,除了是否能有效实施PKI之外,研究人员已经发现了5G所定义的加密操作中的一个漏洞。研究人员使用验证工具来分析5G AKA算法,最终证明该协议未能满足要求中明确的若干安全目标。该研究还表明,5G协议缺乏其他一些关键的安全属性。这些发现都给5G带来了压力。与LTE的情况不同,大多数安全研究过程中发现的协议缺陷都是在协议被定义、实施、全球范围内部署之后才发现的,而此次5G的漏洞则是在规范编写期间发现的漏洞,并且许多安全研究社区仍然在致力于漏洞的寻找。</p><h2 id="五、LTE协议漏洞利用对5G的影响"><a href="#五、LTE协议漏洞利用对5G的影响" class="headerlink" title="五、LTE协议漏洞利用对5G的影响"></a>五、LTE协议漏洞利用对5G的影响</h2><p>LTE安全架构旨在应对前几代规范中存在的问题。第一代移动网络(1G)缺乏对加密的支持,这是引入2G数字移动通信的主要原因之一。旧版2G网络不支持相互身份验证,并且使用了过时的加密算法。如今,随着基站和UE协议栈的开源,越来越多研究人员可以比以前更轻松地发现移动通信系统漏洞。LTE使用了更强的加密算法,并加入了UE与eNodeB之间的显式相互认证,从而实现特定功能,以保证移动网络和消息的机密性和不可抵赖性。这使得4G LTE与前几代相比更加安全。</p><p>然而,LTE中也存在漏洞,并且该漏洞已经存在了一段时间,尽管直到近期才被公开讨论。由于该标准具有开放性,同时也有很多SDR软件库可以被研究人员使用,所以产生了许多出色的LTE安全性分析成果。尽管LTE的加密算法和相互认证更为强大,但UE和基站会交换大量的预认证信息,这些消息可被利用来发起拒绝服务(DoS)攻击、捕获IMSI或将连接降级到不安全的GSM链路。此外,研究人员还发现了LTE中新的隐私泄露和位置泄露问题。下表中总结了过去几年中确认的一些主要LTE协议漏洞,并分析这些漏洞会如何影响5G网络。</p><p><img data-src="https://i.loli.net/2019/01/17/5c3feecd5cd30.jpg" alt="5.jpg"></p><p>大多数LTE协议的安全漏洞,是由3GPPP的安全工作组进行研究和分析。作为该研究的成果,5G移动网络的具体安全目标是:解决IMSI捕获问题、预认证消息漏洞和位置泄露漏洞。但5G移动网络中没有考虑利用无线网络临时标识符(RNTI)的设备和用户跟踪,因为RNTI理论上是用于防止隐私泄露的临时ID。然而,最近的研究表明,通过RNTI可以对用户进行跟踪。</p><p>在LTE协议规范中,还发现了存在漏洞的边缘情况,尽管这种情况很少出现,但仍然受到协议的支持。举例来说,尽管UE不太可能使用其IMSI作为标识符来发送附加请求信息,但协议描述了发生这种情况的特定场景。例如,当EPC丢失了UE的TMSI时,在网络从错误中恢复的过程中,网络就可以触发移动设备,使其能够以明确的IMSI重新发送附加请求消息。简而言之,大多数常用的LTE协议漏洞利用都是由于支持不安全边缘情况的协议,并且在建立安全连接之前对UE默认信任而产生的。</p><p>同样,正如本文所分析的,5G规范根据前几代中存在的挑战,定义了新的安全功能。尽管这些安全功能非常复杂,并且能够抵御攻击者,但5G协议中依然包含许多支持的边缘情况,在这些情况下可以绕过所有安全功能。如第四章中所述,只要有一个运营商或国家不遵守规范,那么全世界范围内的5G UE都有可能遭受到欺骗攻击,从而进入到不安全的通信模式。</p><p>5G的安全规范忽略了一些大多数安全功能都实现的细节。例如,如何对用户USIM中运营商公钥进行管理,这一点就不在规范的范围之内。同样,证书的结构、是否对密钥进行旋转以及如何旋转,这些也没有包含在规范内。此外,由于5G支持零加密和零完整性保护,导致进入到不安全的通信模式,攻击者可以像LTE那样对其进行漏洞利用。</p><h2 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a>六、总结</h2><p>随着通信技术的不断发展,如今即将步入5G时代,无线通信安全也显得至关重要。最常用的语音通话、大量的即时数据传输和控制通信系统,这些都对用户数据的完整性和隐私性有比较严格的要求。此外的一些应用,例如战术通信、第一响应自组织网络和执行关键任务的物联网,同样有较高安全性的需求。</p><p>本文对近期发布的5G规范进行了深入分析,并强调了在规范中存在不能实际落地的要求,从而会导致一些不安全的边缘情况。尽管其目标是解决LTE网络已知的安全漏洞,但实际上5G规范仍然可能受到相同类型的对抗性攻击。</p><p>最重要的一点,是要保证5G标准不支持不安全的边缘情况。特别是,在任何情况下都不应该允许零认证、零加密、降级攻击和IMSI/SUPI捕获。安全机制如果想要取得成功,不能依赖于不可实际落地的要求,也不能允许可选的实现。尽管5G安全架构已经在防止隐私泄露这一方面取得了重大飞跃,但仍需要安全研究人员和开发人员来解决移动通信系统的已知漏洞和新发现漏洞。标准化机构、研究人员、监管机构、通信行业需要共同努力,以实现未来移动通信与控制系统的安全设计、安全开发和安全部署。</p>]]></content>
<categories>
<category>5G</category>
</categories>
<tags>
<tag>5G安全</tag>
</tags>
</entry>
<entry>
<title>树莓派Raspberry Pi 3搭建PLEX多媒体服务器</title>
<url>/2018/12/12/%E6%A0%91%E8%8E%93%E6%B4%BERaspberry%20Pi%203%E6%90%AD%E5%BB%BAPLEX%E5%A4%9A%E5%AA%92%E4%BD%93%E6%9C%8D%E5%8A%A1%E5%99%A8/</url>
<content><![CDATA[<h2 id="先看效果图"><a href="#先看效果图" class="headerlink" title="先看效果图"></a>先看效果图</h2><p>支持账户远程访问和本地访问,断电重启自动恢复功能。</p><p><img data-src="https://i.loli.net/2018/12/13/5c119b2c3f236.png" alt="效果图"></p><h2 id="教程"><a href="#教程" class="headerlink" title="教程"></a>教程</h2><h3 id="一、在树莓派下安装PLEX媒体服务器"><a href="#一、在树莓派下安装PLEX媒体服务器" class="headerlink" title="一、在树莓派下安装PLEX媒体服务器"></a>一、在树莓派下安装PLEX媒体服务器</h3><p>1.在终端,将你的树莓派更新至最新</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt update</span><br><span class="line">sudo apt upgrade</span><br><span class="line">sudo apt dist-upgrade</span><br></pre></td></tr></table></figure><a id="more"></a><p>2.重启树莓派</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo reboot</span><br></pre></td></tr></table></figure><p>3.添加https存储库的功能,如果你已安装了,系统会报告“现在的版本已经是最新版”</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install apt-transport-https</span><br></pre></td></tr></table></figure><p>4.添加Plex Media Server存储库</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="string">"deb https://dev2day.de/pms/ jessie main"</span> | sudo tee /etc/apt/sources.list.d/pms.list</span><br></pre></td></tr></table></figure><p>5.为存储库添加GPG密钥</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> /tmp</span><br><span class="line">wget https://dev2day.de/pms/dev2day-pms.gpg.key </span><br><span class="line">ls (<span class="comment">#查看是否已经下载好dev2day-pms.gpg.key文件)</span></span><br><span class="line">sudo apt-key add dev2day-pms.gpg.key</span><br></pre></td></tr></table></figure><p>6.回到根目录</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span></span><br></pre></td></tr></table></figure><p>7.更新源</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt update</span><br></pre></td></tr></table></figure><p>8.安装Plex媒体服务器</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt install plexmediaserver-installer</span><br></pre></td></tr></table></figure><p>注意:如果无法找到安装包,请重启树莓派,并执行update命令</p><p>9.创建默认配置文件,以便Plex知道要在哪个用户下操作</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo nano /etc/default/plexmediaserver.prev</span><br></pre></td></tr></table></figure><p>将里面的的</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">PLEX_MEDIA_SERVER_USER=plex</span><br></pre></td></tr></table></figure><p>修改为</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">PLEX_MEDIA_SERVER_USER=pi</span><br></pre></td></tr></table></figure><p>10.重启PLEX服务,看到如下界面说明已经安装成功</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">systemctl restart plexmediaserver</span><br><span class="line">systemctl status plexmediaserver</span><br></pre></td></tr></table></figure><p>11.重启树莓派(可选)</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo reboot</span><br></pre></td></tr></table></figure><h3 id="二、安装USB外挂硬盘,实现电影资源存放"><a href="#二、安装USB外挂硬盘,实现电影资源存放" class="headerlink" title="二、安装USB外挂硬盘,实现电影资源存放"></a>二、安装USB外挂硬盘,实现电影资源存放</h3><p>1.检查插入的USB硬盘已安装到哪里</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo cat /proc/mounts</span><br></pre></td></tr></table></figure><p>2.安装NTFS驱动器</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo apt-get install ntfs-3g</span><br></pre></td></tr></table></figure><p>3.查询已插入硬盘的UUID</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">ls -l /dev/disk/by-uuid</span><br></pre></td></tr></table></figure><p>4.现在我们创建一个新的目录来挂载USB</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo mkdir /media/usb1</span><br></pre></td></tr></table></figure><p>5.接下来我们查询硬盘的UID和GID</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">id -g pi</span><br><span class="line">id -u pi</span><br></pre></td></tr></table></figure><p>6.我们需要对fstab文件进行编辑。这是启动时调用的文件,用于设置驱动器</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo nano /etc/fstab</span><br></pre></td></tr></table></figure><p>7.将以下代码的UUID、UID和GID更改为你查询到的填入</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">UUID=你的UUID /media/usb1 auto nofail,uid=输入你的UID,gid=输入你的UID,noatime 0 0</span><br></pre></td></tr></table></figure><p>8.卸载之前挂载好的USB硬盘</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo umount /dev/sda1</span><br></pre></td></tr></table></figure><p>9.重新挂载USB硬盘</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo mount -a</span><br></pre></td></tr></table></figure><p>10.重启树莓派,完成全部安装</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">sudo reb</span><br></pre></td></tr></table></figure><br><br><blockquote><p>参考:<a href="https://www.vediotalk.com/?p=215" target="_blank" rel="noopener">https://www.vediotalk.com/?p=215</a></p></blockquote>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Raspberry Pi</tag>
</tags>
</entry>
<entry>
<title>5G核心网架构基础概念</title>
<url>/2018/12/11/5G%E6%A0%B8%E5%BF%83%E7%BD%91%E6%9E%B6%E6%9E%84%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5/</url>
<content><![CDATA[<blockquote><p>5G无线接入网络架构,主要包括 5G 接入网和 5G 核心网,其中 NG-RAN 代表 5G 接入网,5GC 代表 5G 核心网。</p></blockquote><p><img data-src="https://i.loli.net/2018/12/12/5c100a118e98c.jpg" alt="1.jpg"></p><p><strong>5G核心网主要包括哪些呢?先说一下关键的 AMF,SMF,UPF。</strong></p><a id="more"></a><p><img data-src="https://i.loli.net/2018/12/12/5c100a11f1b20.jpg" alt="2.jpg"></p><p>AMF:全称 Access and Mobility Management Function,接入和移动管理功能,终端接入权限和切换等由它来负责。<br>SMF:全称 Session Management Function,会话管理功能,提供服务连续性,服务的不间断用户体验,包括IP地址和/或锚点变化的情况。<br>UPF:全称 User Plane Function,用户面管理功能,与UPF关联的PDU会话可以由(R)AN节点通过(R)AN和UPF之间的N3接口服务的区域,而无需在其间添加新的UPF或移除/重新 - 分配UPF。</p><p><strong>我们看一下5G的系统构架图:</strong></p><p>5G的系统构架图</p><p><img data-src="https://i.loli.net/2018/12/12/5c100a11bd0db.jpg" alt="3.jpg"></p><p>AMF,SMF,UPF 处于主体的作用。</p><p><img data-src="https://i.loli.net/2018/12/12/5c100a1188066.jpg" alt="4.jpg"></p><p>参考点表示中的非漫游 5G 系统体系结构</p><h3 id="接入和移动管理功能-AMF-承载以下主要功能:"><a href="#接入和移动管理功能-AMF-承载以下主要功能:" class="headerlink" title="接入和移动管理功能(AMF)承载以下主要功能:"></a>接入和移动管理功能(AMF)承载以下主要功能:</h3><p>接入和移动管理功能(AMF)包括以下功能。 在AMF的单个实例中可以支持部分或全部AMF功能:</p><p>终止RAN CP接口(N2)。<br>终止NAS(N1),NAS加密和完整性保护。<br>注册管理。<br>连接管理。<br>可达性管理。<br>流动性管理。<br>合法拦截(适用于AMF事件和LI系统的接口)。<br>为UE和SMF之间的SM消息提供传输。<br>用于路由SM消息的透明代理。<br>接入身份验证。<br>接入授权。<br>在UE和SMSF之间提供SMS消息的传输。<br>安全锚功能(SEAF)。<br>监管服务的定位服务管理。<br>为UE和LMF之间以及RAN和LMF之间的位置服务消息提供传输。<br>用于与EPS互通的EPS 承载 ID分配。<br>UE移动事件通知。<br>无论网络功能的数量如何,UE和CN之间的每个接入网络只有一个NAS接口实例,终止于至少实现NAS安全性和移动性管理的网络功能之一。</p><p>除了上述AMF的功能之外,AMF还可以包括以下功能以支持非3GPP 接入网络:</p><p>支持N2接口与N3IWF。 在该接口上,可以不应用通过3GPP 接入定义的一些信息(例如,3GPP 小区标识)和过程(例如,与切换相关),并且可以应用不适用于3GPP接入的非3GPP 接入特定信息。<br>通过N3IWF上的UE支持NAS信令。 由3GPP 接入上的NAS信令支持的一些过程可能不适用于不可信的非3GPP(例如寻呼)接入。<br>支持通过N3IWF连接的UE的认证。<br>管理通过非3GPP 接入连接或通过3GPP和非3GPP同时连接的UE的移动性,认证和单独的安全上下文状态。<br>支持协调的RM管理上下文,该上下文对3GPP和非3GPP访问有效。<br>支持针对UE的专用CM管理上下文,用于通过非3GPP 接入进行连接。<br>注意:并非所有功能都需要在网络片的实例中得到支持。</p><h3 id="会话管理功能-UPF-承载以下主要功能"><a href="#会话管理功能-UPF-承载以下主要功能" class="headerlink" title="会话管理功能(UPF)承载以下主要功能"></a>会话管理功能(UPF)承载以下主要功能</h3><p>用户平面功能(UPF)包括以下功能。 在UPF的单个实例中可以支持部分或全部UPF功能:</p><p>用于RAT内/ RAT间移动性的锚点(适用时)。<br>外部PDU与数据网络互连的会话点。<br>分组路由和转发(例如,支持上行链路分类器以将业务流路由到数据网络的实例,支持分支点以支持多宿主PDU会话)。<br>数据包检查(例如,基于服务数据流模板的应用程序检测以及从SMF接收的可选PFD)。<br>用户平面部分策略规则实施,例如门控,重定向,流量转向)。<br>合法拦截(UP收集)。<br>流量使用报告。<br>用户平面的QoS处理,例如UL / DL速率实施,DL中的反射QoS标记。<br>上行链路流量验证(SDF到QoS流量映射)。<br>上行链路和下行链路中的传输级分组标记。<br>下行数据包缓冲和下行数据通知触发。<br>将一个或多个“结束标记”发送和转发到源NG-RAN节点。<br>ARP代理和/或以太网PDU的IPv6 Neighbor Solicitation Proxying。 UPF通过提供与请求中发送的IP地址相对应的MAC地址来响应ARP和/或IPv6邻居请求请求。</p><h3 id="会话管理功能-SMF-承载以下主要功能"><a href="#会话管理功能-SMF-承载以下主要功能" class="headerlink" title="会话管理功能(SMF)承载以下主要功能"></a>会话管理功能(SMF)承载以下主要功能</h3><p>会话管理功能(SMF)包括以下功能。 在SMF的单个实例中可以支持部分或全部SMF功能:</p><p>会话管理,例如会话建立,修改和释放,包括UPF和AN节点之间的隧道维护。<br>UE IP地址分配和管理(包括可选的授权)。<br>DHCPv4(服务器和客户端)和DHCPv6(服务器和客户端)功能。<br>ARP代理和/或以太网PDU的IPv6 Neighbor Solicitation Proxying。 SMF通过提供与请求中发送的IP地址相对应的MAC地址来响应ARP和/或IPv6邻居请求请求。<br>选择和控制UP功能,包括控制UPF代理ARP或IPv6邻居发现,或将所有ARP / IPv6邻居请求流量转发到SMF,用于以太网PDU会话。<br>配置UPF的流量控制,将流量路由到正确的目的地。<br>终止接口到策略控制功能。<br>合法拦截(用于SM事件和LI系统的接口)。<br>收费数据收集和支持计费接口。<br>控制和协调UPF的收费数据收集。<br>终止SM消息的SM部分。<br>下行数据通知。<br>AN特定SM信息的发起者,通过AMF通过N2发送到AN。<br>确定会话的SSC模式。</p><p><strong>漫游功能:</strong></p><p>处理本地实施以应用QoS SLA(VPLMN)。<br>计费数据收集和计费接口(VPLMN)。<br>合法拦截(在SM事件的VPLMN和LI系统的接口)。<br>支持与外部DN的交互,以便通过外部DN传输PDU会话授权/认证的信令。</p>]]></content>
<categories>
<category>5G</category>
</categories>
<tags>
<tag>5G核心网</tag>
</tags>
</entry>
<entry>
<title>5G核心网将采用SBA架构</title>
<url>/2018/12/11/5G%E6%A0%B8%E5%BF%83%E7%BD%91%E5%B0%86%E9%87%87%E7%94%A8SBA%E6%9E%B6%E6%9E%84/</url>
<content><![CDATA[<h3 id="SBA架构-Service-based-architecture-基于服务的网络架构-作为统一基础架构"><a href="#SBA架构-Service-based-architecture-基于服务的网络架构-作为统一基础架构" class="headerlink" title="SBA架构(Service-based architecture 基于服务的网络架构)作为统一基础架构"></a>SBA架构(Service-based architecture 基于服务的网络架构)作为统一基础架构</h3><p>终于迎来了一个确定的消息,在杭州结束的国际移动通信标准组织3GPP专业会议上,3GPP正式确认5G核心网采用中国移动牵头并联合26家公司提出的SBA架构(Service-based architecture 基于服务的网络架构)作为统一基础架构。</p><p>据了解,该架构由中国移动牵头联合全球14家运营商及华为等12家网络设备商联合提出。</p><p>近半年来,5G标准化项目一直在讨论5G网络是不是该采用SBA新型架构。此次,3GPP将SBA确定为5G网络唯一基础架构,是5G系统架构标准化立项以来的重要进展。</p><a id="more"></a><p>据悉,SBA采用“服务”来设计移动通的网元及主要功能,以“服务调用”取代传统的信令交流。这种设计有助于网络快速升级、提升资源利用率、加速新能力引入、便于网内和网外的能力开放,使得5G系统从架构上全面云化,利用快速扩缩容。</p><p>而其实早在2016年11月在美国里诺举行的3GPP SA2#118次会议,在,3GPP决定正式启动5G系统设计,并确认中国移动牵头5G系统架构标准项目。</p><p>那时在中国IMT-2020推进组、中国移动、AT&T、德电等运营商及华为、诺基亚、中兴、爱立信等厂商的积极推动下,来自全球的通信运营商、设备商已经对5G系统总体架构形成共识,要将“基于服务”的模块化设计将成为5G系统架构的主要特征。改变传统基于“请求—应答”的网络流程,5G系统架构将基于“微服务”的设计理念。</p><p>基于服务的网络架构借鉴IT领域的“微服务”设计理念,将网络功能定义为多个相对独立可被灵活调用的服务模块。基于此,运营商可以按照业务需求进行灵活定制组网,使得5G网络真正面向云化(Cloud Native)设计,具备多方面优点,如便于网络快速升级、提升网络资源利用率、加速网络新能力引入,以及在授权的情况下开放给第三方等。</p><h3 id="决定5G网络整体形态"><a href="#决定5G网络整体形态" class="headerlink" title="决定5G网络整体形态"></a>决定5G网络整体形态</h3><p>5G系统架构标准项目的名称为R15“5G System Architecture”,简称5GS,是整个5G设计的第一个技术标准,也是事关5G全系统设计的基础性标准。</p><p>5GS项目唯一报告人、中国移动研究院网络技术研究所孙韬博士也曾向媒体公开表示,5G整体系统的设计包括顶层设计、无线网设计、核心网设计等方面,5G系统架构标准项目主要是进行顶层设计和核心网设计,而架构和核心网的设计对5G网络管理运营至关重要,对运营商及整个产业链来说也有非常重要的意义。同时,项目将对5G系统架构、功能、接口关系、流程、漫游、与现有网络共存关系等进行标准化。也是从系统设计和端到端角度对5G进行定义和规范,基本确定了5G网络的面貌、组网形态。</p><p>据悉, 5GS项目预计会在今年12月前,制定《5G系统总体架构及功能》及《5G系统基本流程》两个基础性标准。</p><p>孙滔表示,这两个规范是5G系统架构(5GS)项目拟输出的正式标准,是定义和形成5G网络系统的基础性标准,决定了5G网络架构的整体形态。这两个标准也为5G相关的技术标准,如网管、协议实现、安全等提供参考和依据。</p><p>《5G系统总体架构及功能》主要定义5G系统总体架构、主要功能、逻辑关系和接口、漫游架构、与现存共存关系等。《5G系统基本流程》将定义端到端的网络流程、涉及终端和网络互通、不同网络功能间的交互等。这两份规范是5G运营商组网、厂家设备研制、互通互联的基本依据,是后续5G技术进一步完善和增强的基础。</p><p>他认为这两份规范是5G运营商组网、厂家设备研制、互通互联的基本依据,是后续5G技术进一步完善和增强的基础。</p><h3 id="面向服务化——形神兼备"><a href="#面向服务化——形神兼备" class="headerlink" title="面向服务化——形神兼备"></a>面向服务化——形神兼备</h3><p>在孙滔看来,5G网络的架构是一个具有长远影响力和创新性的新架构,如“面向服务化”,很多控制面相关的业务逻辑将以“服务”来定义,使得网络功能的配置更加灵活、开放;“面向网络切片”,可以同时形成不同特性的相对独立的网络能力;“接入无关性”,5G从设计的一开始就考虑统一的网络架构,同时考虑对5G接入及非蜂窝网接入的支持。</p><p>中国移动研究院网络所所长段晓东也曾在公开演讲中表示,中国移动对5G基本架构已有初步共识,就是要支持多种接入、降低不同接入方式和核心网的耦合、实现按需的组网、借力NFV/SDN技术。</p><p>他表示,5G网络化已经到了关键时期,中国移动代表IMT-2020(5G推进组)提出5G网络架构三大设计原则。</p><p>首先是从刚性到软性,就是从固定网络(网元、固定连接、固定部署)到动态网络(动态部署、配置、灵活连接),网络资源虚拟化、网络功能的解耦和服务化。</p><p>其次是移动网络IP化、互联网化,就是实现与IT网络互通融合,引入互联网技术,优化网络设计。</p><p>最后是集中化智能和分布化处理,集中化智能-功能集中优化,为垂直行业提供个性化增值服务,并分布化处理-移动网络功能靠近用户,提高网络吞吐量,降低时延。</p><p>以SDN/NFV技术实现网络云化,加上微服务设计,这意味着5G网络真正走向开放化、服务化、软件化方向,有利于实现5G与垂直行业融合发展。某知名行业自媒体评论,5G网络以NFV/SDN这个“形”作为平台基础,加上服务化的“神”实现5G能力,将真正“形神兼备”</p>]]></content>
<categories>
<category>5G</category>
</categories>
<tags>
<tag>5G核心网</tag>
</tags>
</entry>
<entry>
<title>Mac最好的系统监控工具-iStat Menus 6最新下载地址及注册码</title>
<url>/2018/12/11/Mac%E6%9C%80%E5%A5%BD%E7%9A%84%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7-iStat%20Menus%206%E6%9C%80%E6%96%B0%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%E5%8F%8A%E6%B3%A8%E5%86%8C%E7%A0%81/</url>
<content><![CDATA[<h2 id="一、Mac最好的系统状态监控工具-iStat-Menus-6-简介"><a href="#一、Mac最好的系统状态监控工具-iStat-Menus-6-简介" class="headerlink" title="一、Mac最好的系统状态监控工具 : iStat Menus 6 简介"></a>一、Mac最好的系统状态监控工具 : iStat Menus 6 简介</h2><p>这是Mac平台最好的系统状态监控工具,主要是监控系统运行状态的,功能很强大,装机必备。这款软件让您可以从菜单栏中监控您的系统,包括8个额外的菜单,让您监视您的系统的每个方面。</p><h2 id="二、Mac最好的系统状态监控工具-iStat-Menus-6-截图"><a href="#二、Mac最好的系统状态监控工具-iStat-Menus-6-截图" class="headerlink" title="二、Mac最好的系统状态监控工具 : iStat Menus 6 截图"></a>二、Mac最好的系统状态监控工具 : iStat Menus 6 截图</h2><a id="more"></a><p><img data-src="https://i.loli.net/2018/12/11/5c0f428884fb9.png" alt="Mac mini 1"></p><p><img data-src="https://i.loli.net/2018/12/11/5c0f42df2da0c.png" alt="Mac mini 2"></p><p><img data-src="https://i.loli.net/2018/12/11/5c0f42f1675e4.png" alt="Mac mini 3"></p><h2 id="三、Mac最好的系统状态监控工具-iStat-Menus-6-下载与注册方法"><a href="#三、Mac最好的系统状态监控工具-iStat-Menus-6-下载与注册方法" class="headerlink" title="三、Mac最好的系统状态监控工具 : iStat Menus 6 下载与注册方法"></a>三、Mac最好的系统状态监控工具 : iStat Menus 6 下载与注册方法</h2><p>使用下面链接下载最新版本</p><p><a href="https://bjango.com/mac/istatmenus/" target="_blank" rel="noopener">最新版本</a></p><p>安装完成后输入下面邮箱和激活码进行激活</p><figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line"># 注册码: </span><br><span class="line">Email: <span class="symbol">982092332@</span>qq.com </span><br><span class="line">SN: GAWAE-FCWQ3-P8NYB-C7GF7-NEDRT-Q5DTB-MFZG6<span class="number">-6</span>NEQC-CRMUD<span class="number">-8</span>MZ2K<span class="number">-66</span>SRB-SU8EW-EDLZ9-TGH3S<span class="number">-8</span>SGA</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>Mac</category>
</categories>
<tags>
<tag>Software</tag>
</tags>
</entry>
<entry>
<title>Centos7服务器安装Anaconda</title>
<url>/2018/12/07/Centos7%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%AE%89%E8%A3%85Anaconda/</url>
<content><![CDATA[<h1 id="Anaconda3下载"><a href="#Anaconda3下载" class="headerlink" title="Anaconda3下载"></a>Anaconda3下载</h1><p>从Anaconda官网下载linux版本:<a href="https://www.anaconda.com/download/#linux" target="_blank" rel="noopener">https://www.anaconda.com/download/#linux</a></p><h1 id="Anaconda3安装"><a href="#Anaconda3安装" class="headerlink" title="Anaconda3安装"></a>Anaconda3安装</h1><p>将下载好的文件Anaconda3-5.0.1-Linux-x86_64.sh执行如下命令:</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line"># <span class="selector-tag">bash</span> <span class="selector-tag">Anaconda3-5</span><span class="selector-class">.0</span><span class="selector-class">.1-Linux-x86_64</span><span class="selector-class">.sh</span></span><br></pre></td></tr></table></figure><a id="more"></a><p>安装过程中修改Anaconda3的安装路径为/opt/modules/anaconda3:</p><figure class="highlight ada"><table><tr><td class="code"><pre><span class="line"><span class="keyword">Do</span> you <span class="keyword">accept</span> the license terms? [yes|no]</span><br><span class="line">Please answer <span class="symbol">'yes</span>' <span class="keyword">or</span> <span class="symbol">'no</span><span class="string">':'</span></span><br><span class="line">>>> yes </span><br><span class="line"></span><br><span class="line">Anaconda3 will now be installed into this location:</span><br><span class="line">/root/anaconda3</span><br><span class="line"></span><br><span class="line"> - Press ENTER to confirm the location</span><br><span class="line"> - Press CTRL-C to <span class="keyword">abort</span> the installation</span><br><span class="line"> - <span class="keyword">Or</span> specify a different location below</span><br><span class="line"></span><br><span class="line">[/root/anaconda3] >>> /opt/modules/anaconda3</span><br><span class="line">PREFIX=/opt/modules/anaconda3</span><br></pre></td></tr></table></figure><p>等待安装完成提示信息,询问是否要将Anaconda3添加到PATH环境变量中,直接回车(选择no):</p><figure class="highlight sql"><table><tr><td class="code"><pre><span class="line">installation finished.</span><br><span class="line"><span class="keyword">Do</span> you wish the installer <span class="keyword">to</span> prepend the Anaconda3 <span class="keyword">install</span> location</span><br><span class="line"><span class="keyword">to</span> <span class="keyword">PATH</span> <span class="keyword">in</span> your /root/.bashrc ? [yes|<span class="keyword">no</span>]</span><br><span class="line">[<span class="keyword">no</span>] >>> </span><br><span class="line"></span><br><span class="line">You may wish <span class="keyword">to</span> edit your .bashrc <span class="keyword">to</span> prepend the Anaconda3 <span class="keyword">install</span> location <span class="keyword">to</span> <span class="keyword">PATH</span>:</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">PATH</span>=/opt/modules/anaconda3/<span class="keyword">bin</span>:$<span class="keyword">PATH</span></span><br><span class="line"></span><br><span class="line">Thank you <span class="keyword">for</span> installing Anaconda3!</span><br></pre></td></tr></table></figure><p>手动将export PATH=/opt/modules/anaconda3/bin:$PATH添加到/etc/profile中, 最后source /etc/profile使环境变量生效:</p><figure class="highlight gradle"><table><tr><td class="code"><pre><span class="line"># <span class="keyword">source</span> <span class="regexp">/etc/</span>profile</span><br></pre></td></tr></table></figure><h1 id="Tensorflow安装"><a href="#Tensorflow安装" class="headerlink" title="Tensorflow安装"></a>Tensorflow安装</h1><p>建立Tensorflow运行环境<br>Tensorflow目前Python3版本最高支持到Python3.5,所以选择Python 3.5, 只需要执行conda create -n tensorflow python=3.5指令:</p><figure class="highlight vala"><table><tr><td class="code"><pre><span class="line"><span class="meta">## Python 2.7</span></span><br><span class="line"><span class="meta"># conda create -n tensorflow python=2.7 </span></span><br><span class="line"></span><br><span class="line"><span class="meta">## Python 3.5 </span></span><br><span class="line"><span class="meta"># conda create -n tensorflow python=3.5 </span></span><br><span class="line"></span><br><span class="line"><span class="meta">## Python 3.6 </span></span><br><span class="line"><span class="meta"># conda create -n tensorflow python=3.6</span></span><br></pre></td></tr></table></figure><p>在Anaconda3中创建Tensorflow虚拟环境:</p><figure class="highlight clean"><table><tr><td class="code"><pre><span class="line">Fetching package metadata ...........</span><br><span class="line">Solving package specifications: .</span><br><span class="line"></span><br><span class="line">Package plan for installation <span class="keyword">in</span> environment /opt/modules/anaconda3/envs/tensorflow:</span><br><span class="line"></span><br><span class="line">The following NEW packages will be INSTALLED:</span><br><span class="line"></span><br><span class="line"> ca-certificates: <span class="number">2017.08</span><span class="number">.26</span>-h1d4fec5_0 </span><br><span class="line"> certifi: <span class="number">2017.11</span><span class="number">.5</span>-py35h9749603_0</span><br><span class="line"> libedit: <span class="number">3.1</span>-heed3624_0 </span><br><span class="line"> libffi: <span class="number">3.2</span><span class="number">.1</span>-hd88cf55_4 </span><br><span class="line"> libgcc-ng: <span class="number">7.2</span><span class="number">.0</span>-h7cc24e2_2 </span><br><span class="line"> libstdcxx-ng: <span class="number">7.2</span><span class="number">.0</span>-h7a57d05_2 </span><br><span class="line"> ncurses: <span class="number">6.0</span>-h9df7e31_2 </span><br><span class="line"> openssl: <span class="number">1.0</span><span class="number">.2</span>m-h26d622b_1 </span><br><span class="line"> pip: <span class="number">9.0</span><span class="number">.1</span>-py35h7e7da9d_4 </span><br><span class="line"> python: <span class="number">3.5</span><span class="number">.4</span>-h417fded_24 </span><br><span class="line"> readline: <span class="number">7.0</span>-ha6073c6_4 </span><br><span class="line"> setuptools: <span class="number">36.5</span><span class="number">.0</span>-py35ha8c1747_0 </span><br><span class="line"> sqlite: <span class="number">3.20</span><span class="number">.1</span>-hb898158_2 </span><br><span class="line"> tk: <span class="number">8.6</span><span class="number">.7</span>-hc745277_3 </span><br><span class="line"> wheel: <span class="number">0.30</span><span class="number">.0</span>-py35hd3883cf_1 </span><br><span class="line"> xz: <span class="number">5.2</span><span class="number">.3</span>-h55aa19d_2 </span><br><span class="line"> zlib: <span class="number">1.2</span><span class="number">.11</span>-ha838bed_2 </span><br><span class="line"></span><br><span class="line">Proceed ([y]/n)? </span><br><span class="line"></span><br><span class="line"></span><br><span class="line">libffi<span class="number">-3.2</span><span class="number">.1</span>-h <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">137.60</span> kB/s</span><br><span class="line">ncurses<span class="number">-6.0</span>-h9 <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">01</span> <span class="number">622.10</span> kB/s</span><br><span class="line">openssl<span class="number">-1.0</span><span class="number">.2</span>m <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">03</span> <span class="number">1.06</span> MB/s</span><br><span class="line">tk<span class="number">-8.6</span><span class="number">.7</span>-hc745 <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">02</span> <span class="number">1.13</span> MB/s</span><br><span class="line">xz<span class="number">-5.2</span><span class="number">.3</span>-h55aa <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">1.28</span> MB/s</span><br><span class="line">zlib<span class="number">-1.2</span><span class="number">.11</span>-ha <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">1.59</span> MB/s</span><br><span class="line">readline<span class="number">-7.0</span>-h <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">1.27</span> MB/s</span><br><span class="line">sqlite<span class="number">-3.20</span><span class="number">.1</span>- <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">01</span> <span class="number">1.41</span> MB/s</span><br><span class="line">python<span class="number">-3.5</span><span class="number">.4</span>-h <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">07</span> <span class="number">3.87</span> MB/s</span><br><span class="line">certifi<span class="number">-2017.1</span> <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">6.01</span> MB/s</span><br><span class="line">setuptools<span class="number">-36.</span> <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">6.55</span> MB/s</span><br><span class="line">wheel<span class="number">-0.30</span><span class="number">.0</span>-p <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">6.82</span> MB/s</span><br><span class="line">pip<span class="number">-9.0</span><span class="number">.1</span>-py35 <span class="number">100</span>% |##################################################################| Time: <span class="number">0</span>:<span class="number">00</span>:<span class="number">00</span> <span class="number">6.78</span> MB/s</span><br><span class="line">#</span><br><span class="line"># To activate this environment, use:</span><br><span class="line"># > source activate tensorflow</span><br><span class="line">#</span><br><span class="line"># To deactivate an active environment, use:</span><br><span class="line"># > source deactivate</span><br><span class="line">#</span><br></pre></td></tr></table></figure><p>为了简便也可以直接指定版本python=3.5, 且克隆anaconda所有的Python包:</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line">conda create -n tensorflow <span class="attribute">python</span>=3.5 anaconda</span><br></pre></td></tr></table></figure><h1 id="conda环境管理"><a href="#conda环境管理" class="headerlink" title="conda环境管理"></a>conda环境管理</h1><h2 id="列出所有的环境"><a href="#列出所有的环境" class="headerlink" title="列出所有的环境"></a>列出所有的环境</h2><figure class="highlight nginx"><table><tr><td class="code"><pre><span class="line"><span class="attribute">conda</span> <span class="literal">info</span> --envs</span><br></pre></td></tr></table></figure><h2 id="创建一个指定Python版本且包含anaconda所有Python包的新环境"><a href="#创建一个指定Python版本且包含anaconda所有Python包的新环境" class="headerlink" title="创建一个指定Python版本且包含anaconda所有Python包的新环境"></a>创建一个指定Python版本且包含anaconda所有Python包的新环境</h2><figure class="highlight vala"><table><tr><td class="code"><pre><span class="line"><span class="meta"># conda create -n py36 python=3.6 anaconda</span></span><br></pre></td></tr></table></figure><h2 id="克隆一个环境"><a href="#克隆一个环境" class="headerlink" title="克隆一个环境"></a>克隆一个环境</h2><p>创建一个和root环境一样的副本:</p><figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">conda create -n py36 --<span class="keyword">clone</span> <span class="title">root</span></span><br></pre></td></tr></table></figure><h2 id="删除一个环境"><a href="#删除一个环境" class="headerlink" title="删除一个环境"></a>删除一个环境</h2><figure class="highlight lua"><table><tr><td class="code"><pre><span class="line"># conda <span class="built_in">remove</span> -n py36 <span class="comment">--all</span></span><br></pre></td></tr></table></figure><p>在conda环境下安装tensorflow(pip安装方式)<br>激活conda环境(tensorflow)</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"> <span class="built_in">source</span> activate tensorflow</span></span><br></pre></td></tr></table></figure><h2 id="根据tensorflow的版本设置环境变量(以CPU版本为例)"><a href="#根据tensorflow的版本设置环境变量(以CPU版本为例)" class="headerlink" title="根据tensorflow的版本设置环境变量(以CPU版本为例)"></a>根据tensorflow的版本设置环境变量(以CPU版本为例)</h2><p>Tensorflow的源码地址: <a href="https://github.com/tensorflow/tensorflow,如下三种环境Python2.7" target="_blank" rel="noopener">https://github.com/tensorflow/tensorflow,如下三种环境Python2.7</a>, Python3.4, Python3.5,选择一种(Python3.5)运行:</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line"><span class="comment">## Linux 64-bit, CPU only, Python 2.7 </span></span><br><span class="line">(tensorflow)$ export TF_BINARY_URL=https:<span class="regexp">//</span>ci.tensorflow.org<span class="regexp">/view/</span>tf-nightly<span class="regexp">/job/</span>tf-nightly-linux<span class="regexp">/TF_BUILD_IS_OPT=OPT,TF_BUILD_IS_PIP=PIP,TF_BUILD_PYTHON_VERSION=PYTHON2,label=cpu-slave/</span>lastSuccessfulBuild<span class="regexp">/artifact/</span>pip_test<span class="regexp">/whl/</span>tf_nightly-<span class="number">1</span>.head-cp27-none-linux_x86_64.whl </span><br><span class="line"><span class="comment">## Linux 64-bit, CPU only, Python 3.4 </span></span><br><span class="line">(tensorflow)$ export TF_BINARY_URL=https:<span class="regexp">//</span>ci.tensorflow.org<span class="regexp">/view/</span>tf-nightly<span class="regexp">/job/</span>tf-nightly-linux<span class="regexp">/TF_BUILD_IS_OPT=OPT,TF_BUILD_IS_PIP=PIP,TF_BUILD_PYTHON_VERSION=PYTHON3,label=cpu-slave/</span>lastSuccessfulBuild<span class="regexp">/artifact/</span>pip_test<span class="regexp">/whl/</span>tf_nightly-<span class="number">1</span>.head-cp34-cp34m-linux_x86_64.whl</span><br><span class="line"><span class="comment">## Linux 64-bit, CPU only, Python 3.5 </span></span><br><span class="line">(tensorflow)$ export TF_BINARY_URL=https:<span class="regexp">//</span>ci.tensorflow.org<span class="regexp">/view/</span>tf-nightly<span class="regexp">/job/</span>tf-nightly-linux<span class="regexp">/TF_BUILD_IS_OPT=OPT,TF_BUILD_IS_PIP=PIP,TF_BUILD_PYTHON_VERSION=PYTHON3.5,label=cpu-slave/</span>lastSuccessfulBuild<span class="regexp">/artifact/</span>pip_test<span class="regexp">/whl/</span>tf_nightly-<span class="number">1</span>.head-cp35-cp35m-linux_x86_64.whl</span><br></pre></td></tr></table></figure><h2 id="使用pip命令安装tensorflow"><a href="#使用pip命令安装tensorflow" class="headerlink" title="使用pip命令安装tensorflow"></a>使用pip命令安装tensorflow</h2><p>选择一种安装环境(Python 3):</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Python 2 </span></span></span><br><span class="line"><span class="meta">(tensorflow)#</span><span class="bash"> pip install --ignore-installed --upgrade <span class="variable">$TF_BINARY_URL</span> </span></span><br><span class="line"></span><br><span class="line"><span class="meta">#</span><span class="bash"><span class="comment"># Python 3 </span></span></span><br><span class="line"><span class="meta">(tensorflow)#</span><span class="bash"> pip install --ignore-installed --upgrade <span class="variable">$TF_BINARY_URL</span></span></span><br></pre></td></tr></table></figure><h2 id="使用conda命令安装tensorflow"><a href="#使用conda命令安装tensorflow" class="headerlink" title="使用conda命令安装tensorflow"></a>使用conda命令安装tensorflow</h2><p>Using conda参照如下网址:<br>A community maintained conda package is available from conda-forge.<br><a href="https://github.com/conda-forge/tensorflow-feedstock" target="_blank" rel="noopener">https://github.com/conda-forge/tensorflow-feedstock</a></p><p>Only the CPU version of TensorFlow is available at the moment and can be installed in the conda environment for Python 2 or Python 3.</p><figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> <span class="built_in">source</span> activate tensorflow </span></span><br><span class="line"><span class="meta">(tensorflow)#</span><span class="bash"></span></span><br></pre></td></tr></table></figure><p>Linux/Mac OS X, Python 2.7/3.4/3.5, CPU only:</p><figure class="highlight lisp"><table><tr><td class="code"><pre><span class="line">(<span class="name">tensorflow</span>)# conda install -c conda-forge tensorflow</span><br></pre></td></tr></table></figure><p>参考资料<br>【1】<a href="https://blog.csdn.net/liuguangrong/article/details/78737419" target="_blank" rel="noopener">https://blog.csdn.net/liuguangrong/article/details/78737419</a></p>]]></content>
<categories>
<category>服务器</category>
</categories>
<tags>
<tag>Centos</tag>
</tags>
</entry>
<entry>
<title>Centos更新源yum问题解决方法</title>
<url>/2018/12/05/Centos%E9%87%8D%E6%96%B0%E5%AE%89%E8%A3%85yum%E7%9A%84base-repo%E6%BA%90/</url>
<content><![CDATA[<p>好久不更新自己的服务器或者误删除了更新源,偶然返现自己的Centos的系统yum源出现问题了,如何才能修复?</p><h3 id="方式一:使用国内的阿里云镜像"><a href="#方式一:使用国内的阿里云镜像" class="headerlink" title="方式一:使用国内的阿里云镜像"></a>方式一:使用国内的阿里云镜像</h3><p>(1)把/etc/yum.repos.d/下面所有的源给删除掉了</p><p>(2)选择自己系统合适的镜像下载</p><figure class="highlight dts"><table><tr><td class="code"><pre><span class="line"><span class="meta"># CentOS 5</span></span><br><span class="line">wget -O <span class="meta-keyword">/etc/</span>yum.repos.d/CentOS-Base.repo http:<span class="comment">//mirrors.aliyun.com/repo/Centos-5.repo</span></span><br><span class="line"><span class="meta"># CentOS 6</span></span><br><span class="line">wget -O <span class="meta-keyword">/etc/</span>yum.repos.d/CentOS-Base.repo http:<span class="comment">//mirrors.aliyun.com/repo/Centos-6.repo</span></span><br><span class="line"><span class="meta"># CentOS 7</span></span><br><span class="line">wget -O <span class="meta-keyword">/etc/</span>yum.repos.d/CentOS-Base.repo http:<span class="comment">//mirrors.aliyun.com/repo/Centos-7.repo</span></span><br></pre></td></tr></table></figure><p>注意如果没有安装wget,可以从下面的网址中先手动下载,然后上传到linux上安装:<a href="http://www.rpmfind.net/linux/rpm2html/search.php?query=wget" target="_blank" rel="noopener">http://www.rpmfind.net/linux/rpm2html/search.php?query=wget</a></p><p>(3)执行命令,重新生成cache</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">yum</span> <span class="string">clean all</span></span><br><span class="line"><span class="attr">yum</span> <span class="string">makecache</span></span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="方式二:使用官网的镜像"><a href="#方式二:使用官网的镜像" class="headerlink" title="方式二:使用官网的镜像"></a>方式二:使用官网的镜像</h3><p>(1)把/etc/yum.repos.d/下面所有的源给删除掉了</p><p>(2)执行下面的命令,重新生成镜像</p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line"><span class="regexp">//</span>执行这个命令之后</span><br><span class="line">rpm -Uvh --force http:<span class="regexp">//mi</span>rror.centos.org<span class="regexp">/centos-7/</span><span class="number">7</span><span class="regexp">/os/</span>x86_64<span class="regexp">/Packages/</span>centos-release-<span class="number">7</span>-<span class="number">4.1708</span>.el7.centos.x86_64.rpm</span><br></pre></td></tr></table></figure><p>(3)查看/etc/yum.repos.d/下面目录,就会发现镜像已经安装完毕:</p><figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">1664</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-Base.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">1309</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-CR.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">649</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-Debuginfo.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">314</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-fasttrack.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">630</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-Media.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">1331</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-Sources.repo</span><br><span class="line">-rw-r--r--. <span class="number">1</span> root root <span class="number">3830</span> <span class="number">8</span>月 <span class="number">30</span> <span class="number">23</span>:<span class="number">53</span> CentOS-Vault.repo</span><br></pre></td></tr></table></figure><p>(4)生成缓存</p><figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="attr">yum</span> <span class="string">clean all</span></span><br><span class="line"><span class="attr">yum</span> <span class="string">makecache</span></span><br></pre></td></tr></table></figure><p>必要时可以执行yum update更新当前系统的安装包为新的</p><p>手动下载命令,例子:</p><figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">#安装<span class="keyword">vim</span>组件</span><br><span class="line">yum install -<span class="keyword">y</span> <span class="keyword">vim</span></span><br><span class="line"> </span><br><span class="line">#卸载<span class="keyword">vim</span>组件</span><br><span class="line">yum <span class="built_in">remove</span> <span class="keyword">vim</span></span><br></pre></td></tr></table></figure><p>推荐使用方式一。</p>]]></content>
<categories>
<category>服务器</category>
</categories>
<tags>
<tag>Centos</tag>
</tags>
</entry>
<entry>
<title>MAC在同一程序切换窗口</title>
<url>/2018/12/04/MAC%E5%9C%A8%E5%90%8C%E4%B8%80%E7%A8%8B%E5%BA%8F%E5%88%87%E6%8D%A2%E7%AA%97%E5%8F%A3/</url>
<content><![CDATA[<blockquote><p>在MAC下切换窗口是command+tab键,这个是多程序之间切换。那么在同一程序下,比如多个独立浏览器之间或者word之间切换呢?</p></blockquote><h3 id="同一程序切换窗口:"><a href="#同一程序切换窗口:" class="headerlink" title="同一程序切换窗口:"></a>同一程序切换窗口:</h3><p><code>command + ~</code>组合按键,在同时按住数字区域第一个键。</p><ul><li>多个浏览器窗口之间切换</li><li>多个Word之间切换</li></ul><h3 id="不同程序之间切换:"><a href="#不同程序之间切换:" class="headerlink" title="不同程序之间切换:"></a>不同程序之间切换:</h3><p><code>command + tab</code>键进行切换</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Mac</tag>
</tags>
</entry>
<entry>
<title>基于人工智能的树莓派人脸识别系统(Intelligent Facial Recognition System)</title>
<url>/2018/12/01/%E5%9F%BA%E4%BA%8E%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD%E7%9A%84%E6%A0%91%E8%8E%93%E6%B4%BE%E4%BA%BA%E8%84%B8%E8%AF%86%E5%88%AB%E7%B3%BB%E7%BB%9F(Intelligent%20Facial%20Recognition%20System)/</url>
<content><![CDATA[<p>This is a project of the face recognizer with Movidius on RaspberryPi 3B+ platform. The project also uses Django and Django REST framework which providing the web platform. The project would like to build a safety and intelligent face recognition system in AI era.</p><p>If you appreciate the content 📖, support projects visibility, give 👍| ⭐| 👏</p><p>GitHub: <a href="https://github.com/nature1995/Face_Recognition_System" target="_blank" rel="noopener">https://github.com/nature1995/Face_Recognition_System</a></p><h1 id="Compatibility"><a href="#Compatibility" class="headerlink" title="Compatibility"></a>Compatibility</h1><p>The code is tested using Tensorflow r1.7 and Movidius NCSDK2 under Debin 2018-06-27(Kernel version:4.14) with django 2.1.1 and Python 3.5.</p><h1 id="Real-Product-Images"><a href="#Real-Product-Images" class="headerlink" title="Real Product Images"></a>Real Product Images</h1><p><img data-src="https://github.com/nature1995/Face_Recognition_System/raw/master/image/2.jpg" alt="image"><br><a id="more"></a><br><img data-src="https://github.com/nature1995/Face_Recognition_System/raw/master/image/1.jpg" alt="image"><br><img data-src="https://github.com/nature1995/Face_Recognition_System/raw/master/image/3.jpg" alt="image"></p><h1 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h1><ul><li>Logitech HD Webcam C270</li><li>Micro SD Card 32G</li><li>Raspberry Pi 3 B+</li><li>Intel Movidius Neural Compute Stick</li></ul><p>The code requires <a href="https://www.python.org/download/releases/3.5/" target="_blank" rel="noopener">Python 3.5</a>, <a href="https://www.tensorflow.org/install/" target="_blank" rel="noopener">Tensorflow 1.7</a>, as well as the following python libraries:</p><ul><li>Pillow</li><li>django</li><li>django-allauth 0.37.1</li><li>django-widget-tweaks 1.4.3</li><li>pip 18.0</li><li>qrcode 6.0</li><li>setuptools 40.4.3</li></ul><p>Those modules can be installed using: <code>pip3 install xxx</code>.</p><h2 id="Neural-Compute-Application-Zoo"><a href="#Neural-Compute-Application-Zoo" class="headerlink" title="Neural Compute Application Zoo"></a>Neural Compute Application Zoo</h2><p>This repository is a place for any interested developers to share their projects (code and Neural Network content) that make use of the Intel® Movidius™ Neural Compute Stick (Intel® Movidius™ NCS) and associated <a href="http://www.github.com/movidius/ncsdk" target="_blank" rel="noopener">Intel® Movidius™ Neural Compute Software Development Kit</a>.</p><p>You can use the following url(<a href="https://github.com/movidius/ncappzoo" target="_blank" rel="noopener">NC App Zoo</a>) or git command to use the ncsdk2 branch of the NC App Zoo repo:</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> -b ncsdk2 https://github.com/movidius/ncappzoo.git</span><br></pre></td></tr></table></figure><h2 id="Install-Django-and-Django-REST-framework"><a href="#Install-Django-and-Django-REST-framework" class="headerlink" title="Install Django and Django REST framework"></a>Install Django and Django REST framework</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">pip3 -V</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U setuptools</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U django</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U djangorestframework</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U django-filter</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U markdown</span><br><span class="line"></span><br><span class="line">sudo pip3 install -U requests</span><br></pre></td></tr></table></figure><h2 id="Install-Adafruit-Python-DHT-library"><a href="#Install-Adafruit-Python-DHT-library" class="headerlink" title="Install Adafruit_Python_DHT library"></a>Install Adafruit_Python_DHT library</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/adafruit/Adafruit_Python_DHT.git</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> Adafruit_Python_DHT</span><br><span class="line"></span><br><span class="line">sudo python3 setup.py install</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span></span><br></pre></td></tr></table></figure><h2 id="Install-Adafruit-Python-BMP-library"><a href="#Install-Adafruit-Python-BMP-library" class="headerlink" title="Install Adafruit_Python_BMP library"></a>Install Adafruit_Python_BMP library</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/adafruit/Adafruit_Python_BMP.git</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span> Adafruit_Python_BMP</span><br><span class="line"></span><br><span class="line">sudo python3 setup.py install</span><br><span class="line"></span><br><span class="line"><span class="built_in">cd</span></span><br></pre></td></tr></table></figure><h2 id="Install-psutil-process-and-system-utilities"><a href="#Install-psutil-process-and-system-utilities" class="headerlink" title="Install psutil (process and system utilities)"></a>Install psutil (process and system utilities)</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">sudo pip3 install psutil</span><br></pre></td></tr></table></figure><h1 id="rpi-mjpg-streamer"><a href="#rpi-mjpg-streamer" class="headerlink" title="rpi-mjpg-streamer"></a>rpi-mjpg-streamer</h1><p>Instructions and helper scripts for running mjpg-streamer on Raspberry Pi.</p><h2 id="A-Setup-mjpg-streamer"><a href="#A-Setup-mjpg-streamer" class="headerlink" title="A. Setup mjpg-streamer"></a>A. Setup mjpg-streamer</h2><h3 id="Enable-Raspberry-Pi-Camera-module-from-raspi-config"><a href="#Enable-Raspberry-Pi-Camera-module-from-raspi-config" class="headerlink" title="Enable Raspberry Pi Camera module from raspi-config"></a>Enable Raspberry Pi Camera module from raspi-config</h3><figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line">$ sudo raspi-<span class="built_in">config</span></span><br></pre></td></tr></table></figure><h3 id="Install-necessary-packages-for-mjpg-streamer"><a href="#Install-necessary-packages-for-mjpg-streamer" class="headerlink" title="Install necessary packages for mjpg-streamer"></a>Install necessary packages for mjpg-streamer</h3><figure class="highlight q"><table><tr><td class="code"><pre><span class="line">$ sudo apt-<span class="built_in">get</span> <span class="keyword">update</span></span><br><span class="line">$ sudo apt-<span class="built_in">get</span> install build-essential libjpeg8-<span class="built_in">dev</span> imagemagick libv4l-<span class="built_in">dev</span> git cmake uvcdynctrl</span><br></pre></td></tr></table></figure><h3 id="Build-mjpg-streamer"><a href="#Build-mjpg-streamer" class="headerlink" title="Build mjpg-streamer"></a>Build mjpg-streamer</h3><figure class="highlight groovy"><table><tr><td class="code"><pre><span class="line">$ sudo ln -s <span class="regexp">/usr/</span>include<span class="regexp">/linux/</span>videodev2.h <span class="regexp">/usr/</span>include<span class="regexp">/linux/</span>videodev.h</span><br><span class="line">$ git clone <span class="string">https:</span><span class="comment">//github.com/jacksonliam/mjpg-streamer</span></span><br><span class="line">$ cd mjpg-streamer/mjpg-streamer-experimental</span><br><span class="line">$ cmake -<span class="string">DCMAKE_INSTALL_PREFIX:</span>PATH=.. .</span><br><span class="line">$ make install</span><br></pre></td></tr></table></figure><h3 id="Setup-video4linux-for-Raspberry-Pi-Camera-module"><a href="#Setup-video4linux-for-Raspberry-Pi-Camera-module" class="headerlink" title="Setup video4linux for Raspberry Pi Camera module"></a>Setup video4linux for Raspberry Pi Camera module</h3><figure class="highlight pgsql"><table><tr><td class="code"><pre><span class="line">$ sudo modprobe bcm2835-v4l2</span><br><span class="line">$ sudo vi /etc/modules</span><br><span class="line"></span><br><span class="line"># <span class="keyword">add</span> <span class="keyword">following</span> <span class="type">line</span>:</span><br><span class="line">bcm2835-v4l2</span><br><span class="line"></span><br><span class="line">$ sudo vi /boot/config.txt</span><br><span class="line"></span><br><span class="line"># <span class="keyword">add</span> <span class="keyword">following</span> <span class="type">line</span> <span class="keyword">if</span> you want <span class="keyword">to</span> <span class="keyword">disable</span> RPi camera<span class="string">'s LED:</span></span><br><span class="line"><span class="string">disable_camera_led=1</span></span><br></pre></td></tr></table></figure><h3 id="Add-yourself-to-the-video-group"><a href="#Add-yourself-to-the-video-group" class="headerlink" title="Add yourself to the video group"></a>Add yourself to the video group</h3><figure class="highlight stylus"><table><tr><td class="code"><pre><span class="line">$ sudo usermod -<span class="selector-tag">a</span> -G <span class="selector-tag">video</span> <span class="variable">$USER</span></span><br></pre></td></tr></table></figure><h2 id="B-Run-mjpg-streamer"><a href="#B-Run-mjpg-streamer" class="headerlink" title="B. Run mjpg-streamer"></a>B. Run mjpg-streamer</h2><h3 id="1-Clone-this-repository"><a href="#1-Clone-this-repository" class="headerlink" title="1. Clone this repository"></a>1. Clone this repository</h3><figure class="highlight crmsh"><table><tr><td class="code"><pre><span class="line">$ git <span class="keyword">clone</span> <span class="title">https</span>://github.com/meinside/rpi-mjpg-streamer.git</span><br></pre></td></tr></table></figure><h3 id="2-a-Run-mjpg-streamer-from-the-shell-directly"><a href="#2-a-Run-mjpg-streamer-from-the-shell-directly" class="headerlink" title="2-a. Run mjpg-streamer from the shell directly"></a>2-a. Run mjpg-streamer from the shell directly</h3><figure class="highlight stata"><table><tr><td class="code"><pre><span class="line"># <span class="keyword">copy</span> & <span class="keyword">edit</span> <span class="keyword">run</span>-mjpg-streamer.<span class="keyword">sh</span> to your environment or needs</span><br><span class="line">$ cp rpi-mjpg-streamer/<span class="keyword">run</span>-mjpg-streamer.<span class="keyword">sh</span>.<span class="keyword">sample</span> somewhere/<span class="keyword">run</span>-mjpg-streamer.<span class="keyword">sh</span></span><br><span class="line">$ vi somewhere/<span class="keyword">run</span>-mjpg-streamer.<span class="keyword">sh</span></span><br><span class="line"></span><br><span class="line"># then <span class="keyword">run</span></span><br><span class="line">$ somewhere/<span class="keyword">run</span>-mjpg-streamer.<span class="keyword">sh</span></span><br></pre></td></tr></table></figure><h3 id="2-b-Or-run-mjpg-streamer-as-a-service"><a href="#2-b-Or-run-mjpg-streamer-as-a-service" class="headerlink" title="2-b. Or run mjpg-streamer as a service"></a>2-b. Or run mjpg-streamer as a service</h3><h4 id="systemd"><a href="#systemd" class="headerlink" title="systemd"></a>systemd</h4><figure class="highlight crystal"><table><tr><td class="code"><pre><span class="line"><span class="comment"># copy & edit systemd/mjpg-streamer.service file,</span></span><br><span class="line">$ sudo cp rpi-mjpg-streamer/systemd/mjpg-streamer.service.sample /<span class="class"><span class="keyword">lib</span>/<span class="title">systemd</span>/<span class="title">system</span>/<span class="title">mjpg</span>-<span class="title">streamer</span>.<span class="title">service</span></span></span><br><span class="line">$ sudo vi /<span class="class"><span class="keyword">lib</span>/<span class="title">systemd</span>/<span class="title">system</span>/<span class="title">mjpg</span>-<span class="title">streamer</span>.<span class="title">service</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># then register as a service</span></span><br><span class="line">$ sudo systemctl enable mjpg-streamer.service</span><br><span class="line"></span><br><span class="line"><span class="comment"># or remove it</span></span><br><span class="line">$ sudo systemctl disable mjpg-streamer.service</span><br><span class="line"></span><br><span class="line"><span class="comment"># and start/stop it</span></span><br><span class="line">$ sudo systemctl start mjpg-streamer.service</span><br><span class="line">$ sudo systemctl stop mjpg-streamer.service</span><br></pre></td></tr></table></figure><h2 id="C-Connect"><a href="#C-Connect" class="headerlink" title="C. Connect"></a>C. Connect</h2><p>Connect through the web browser:</p><p><img data-src="https://github.com/nature1995/Face_Recognition_System/raw/master/image/IFRS01.png" alt="image"></p><p>Most modern browsers(including mobile browsers like Safari and Chrome) will show the live stream immediately.</p><h1 id="Notice"><a href="#Notice" class="headerlink" title="Notice"></a>Notice</h1><h2 id="Virtualenv"><a href="#Virtualenv" class="headerlink" title="Virtualenv"></a>Virtualenv</h2><p>pip3 install virtualenv</p><p>Run Virtualenv</p><p>source venv/bin/activate</p><h2 id="sqlite3-数据库文件db-sqlite3-权限-666"><a href="#sqlite3-数据库文件db-sqlite3-权限-666" class="headerlink" title="sqlite3 数据库文件db.sqlite3 权限 666"></a>sqlite3 数据库文件db.sqlite3 权限 666</h2><p>chmod 666 db.sqlite3</p><h2 id="django-所在文件夹-权限-775"><a href="#django-所在文件夹-权限-775" class="headerlink" title="django 所在文件夹 权限 775"></a>django 所在文件夹 权限 775</h2><p>chmod 777 xxx</p><h1 id="Citation"><a href="#Citation" class="headerlink" title="Citation"></a>Citation</h1><p>Just can be used for non-business projects.</p>]]></content>
<categories>
<category>源码</category>
</categories>
<tags>
<tag>Raspberry Pi</tag>
<tag>Hardware</tag>
</tags>
</entry>
<entry>
<title>Django博客开发:通过django-allauth实现第三方账号登录教程</title>
<url>/2018/12/01/Django%E5%8D%9A%E5%AE%A2%E5%BC%80%E5%8F%91%EF%BC%9A%E9%80%9A%E8%BF%87django-allauth%E5%AE%9E%E7%8E%B0%E7%AC%AC%E4%B8%89%E6%96%B9%E8%B4%A6%E5%8F%B7%E7%99%BB%E5%BD%95%E6%95%99%E7%A8%8B/</url>
<content><![CDATA[<p>因为需要加入博客的用户登录、注册系统,为了让网站支持第三方登录,所以只用使用<strong>django-allauth</strong>,根据其官方文档这里:<strong><a href="https://link.jianshu.com?t=https://django-allauth.readthedocs.io/en/latest/index.html" target="_blank" rel="noopener">https://django-allauth.readthedocs.io/en/latest/index.html</a></strong>),同时支持本地和第三方账号(Github,百度、微博、微信等等。。),集成了邮箱验证、密码修改、本地账号关联(多个)第三方账号等各种功能,真是厉害了。</p><h2 id="开发环境:python-3-6、django-2-1-3"><a href="#开发环境:python-3-6、django-2-1-3" class="headerlink" title="开发环境:python 3.6、django 2.1.3"></a>开发环境:python 3.6、django 2.1.3</h2><h2 id="准备工作:"><a href="#准备工作:" class="headerlink" title="准备工作:"></a>准备工作:</h2><p><strong>安装</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pip install django-allauth</span><br></pre></td></tr></table></figure><a id="more"></a><p><strong>project/settings.py设置</strong></p><figure class="highlight clean"><table><tr><td class="code"><pre><span class="line">INSTALLED_APPS = [# 必须安装的app</span><br><span class="line"> <span class="string">'django.contrib.auth'</span>, </span><br><span class="line"> <span class="string">'django.contrib.sites'</span>, <span class="string">'allauth'</span>,</span><br><span class="line"> <span class="string">'allauth.account'</span>, </span><br><span class="line"> <span class="string">'allauth.socialaccount'</span>,</span><br><span class="line"> # 下面是第三方账号相关的,比如我选了weibo和github </span><br><span class="line"> <span class="string">'allauth.socialaccount.providers.weibo'</span>, </span><br><span class="line"> <span class="string">'allauth.socialaccount.providers.github'</span>,</span><br><span class="line">]</span><br><span class="line"> # django-allauth相关设置</span><br><span class="line">AUTHENTICATION_BACKENDS = (</span><br><span class="line"> # django admin所使用的用户登录与django-allauth无关 </span><br><span class="line"> <span class="string">'django.contrib.auth.backends.ModelBackend'</span>,</span><br><span class="line"> # `allauth` specific authentication methods, such <span class="keyword">as</span> login by e-mail </span><br><span class="line"> <span class="string">'allauth.account.auth_backends.AuthenticationBackend'</span>,</span><br><span class="line">)</span><br><span class="line"># 前面我们app里添加了django.contrib.sites,需要设置</span><br><span class="line">SITE_IDSITE_ID = <span class="number">1</span></span><br><span class="line">ACCOUNT_AUTHENTICATION_METHOD = <span class="string">'username_email'</span></span><br><span class="line">ACCOUNT_EMAIL_REQUIRED = <span class="literal">True</span></span><br></pre></td></tr></table></figure><p><code>ACCOUNT_AUTHENTICATION_METHOD = 'username_email'</code>的作用是当用户登录时,既可以使用用户名也可以使用email, 其他可选的值是 <strong>“username”</strong>、<strong>“email”</strong> ,<code>ACCOUNT_EMAIL_REQUIRED = True</code>要求用户注册时必须填写email,默认False,email是选填的。</p><p><strong>project/urls.py 里include allauth的urls</strong></p><figure class="highlight awk"><table><tr><td class="code"><pre><span class="line">urlpatterns = [ url(<span class="string">r'^accounts/'</span>, include(<span class="string">'allauth.urls'</span>)),]</span><br></pre></td></tr></table></figure><p>设置好后,我们就可以用’accounts/login’ 访问登录页面, ‘accounts/logout’ 注销, 其他的可以去 <strong>allauth/account/urls.py</strong>查看。</p><p>以上基本设置后,需要同步数据库 <code>python manage.py migrate runserver</code>, 访问登录页面,allauth提供的模板非常简陋, 是这样的</p><p><img data-src="https://i.loli.net/2018/12/02/5c02e791028bc.png" alt="201144936.png"></p><p>我们可以使用自己制作的模板,在<strong>project/templates/account</strong>下创建login.html即可override allauth自带的模板,allauth自带的模板放在<strong>allauth/templates/account</strong>下, 我们只需要在项目templates/account下创建相同名字的html即可覆盖。</p><h2 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h2><p><strong>下面我们去注册页面test一下,先注册一个本地账号</strong></p><p>填写完注册信息后,却出现这样一个错误</p><p><img data-src="https://i.loli.net/2018/12/02/5c02e7b02ee3c.png" alt="107.png"></p><p>出现此错误的原因,官方文档也有说明,这是因为注册账号后,系统会自动发一封邮件来激活账号,但是我们还有发送邮件相关的设置。</p><p>django默认使用SMTP发送邮件,在project/settings.py如下设置:</p><figure class="highlight django"><table><tr><td class="code"><pre><span class="line"><span class="xml"># SMTP服务器,我使用的是sendclound的服务</span></span><br><span class="line"><span class="xml">EMAIL_HOST = 'smtp.sendcloud.net'</span></span><br><span class="line"><span class="xml">EMAIL_HOST_USER = '用户名'</span></span><br><span class="line"><span class="xml">EMAIL_HOST_PASSWORD = '对应的API key或者密码</span></span><br><span class="line"><span class="xml">'EMAIL_PORT = 25</span></span><br><span class="line"><span class="xml"># 是否使用了SSL 或者TLS</span></span><br><span class="line"><span class="xml">EMAIL_USE_SSL = True</span></span><br><span class="line"><span class="xml">EMAIL_USE_TLS = True</span></span><br><span class="line"><span class="xml"># 默认发件人,不设置的话django默认使用的webmaster@localhost</span></span><br><span class="line"><span class="xml">DEFAULT_FROM_EMAIL = 'Aaron-Zhao &lt;noreply@aaron-zhao.com&gt;</span></span><br></pre></td></tr></table></figure><p>完成以上设置后,注册账号是我们就可以收到邮件了,关于邮件的其他详细设置,请参照<strong><a href="https://link.jianshu.com?t=https://docs.djangoproject.com/en/1.10/topics/email/" target="_blank" rel="noopener">django的官方文档</a></strong></p><p>allauth默认的设置,当用户登录时会重定向到 /accounts/profile,这个页面allauth没有做,因为报错page not found,这个页面需要我们自己完成(请戳:<strong><a href="https://link.jianshu.com?t=http://www.aaron-zhao.com/post/5/" target="_blank" rel="noopener">django博客开发:添加用户资料页面和头像上传功能</a></strong>)。与此相关的参数是<strong>LOGIN_REDIRECT_URL</strong></p><p>在project/settings.py设置 *<em>LOGIN_REDIRECT_URL = ‘/‘ *</em>,这样用户登录后的页面就会跳转到首页,而不是用户profile页面。</p><p><strong>下面我们去测试一下第三方账号登录,这里以github为例</strong>,</p><p><strong><a href="https://link.jianshu.com?t=https://github.com/settings/applications/new" target="_blank" rel="noopener">Github OAuth注册页面:https://github.com/settings/applications/new</a></strong></p><p>要注意将回调地址<strong>callback URL</strong>设置为 <strong><a href="https://link.jianshu.com?t=http://127.0.0.1:8000/accounts/github/login/callback/" target="_blank" rel="noopener">http://127.0.0.1:8000/accounts/github/login/callback/</a>,</strong>完成注册后 我们会获得一个Cliend ID 和 Client Secret,后面我们会用到</p><p>先进入admin后台,会发现增加了social accounts和站点</p><p><img data-src="https://i.loli.net/2018/12/02/5c02e7c801608.png" alt="11145251.png"></p><p>点开站点管理,将example.com改为我们博客的域名,在开发环境下,我们用127.0.0.1:8000</p><p><img data-src="https://i.loli.net/2018/12/02/5c02e7dc1823d.png" alt="201145301.png"></p><p>点击SOCIAL ACCOUNTS下的Social application,增加一个application,如下图所示</p><p>provider选Github,这里的provider就是我们在INSTALLED_APP里增加的socialaccount.provider,client id 和secret key 我们在github注册应用里获得了,将sites加入到右边 选中的sites</p><p><img data-src="https://i.loli.net/2018/12/02/5c02e7ee96095.png" alt="145409.png"></p><p>完成以上设置后,注销,然后回到登录页面,选中github登录,授权应用即可,成功登录后,邮箱里也会收到一份激活邮件,这是因为django-allauth会自动为我们添加一个本地账号,根据你的social account用户名和email。</p><h2 id="其他相关设置"><a href="#其他相关设置" class="headerlink" title="其他相关设置"></a>其他相关设置</h2><p>project/settings.py</p><p><code>ACCOUNT_EMAIL_VERIFICATION = 'optional' / 'mandatory' / 'none'</code> 默认值是’optional’,当其等于’mandatory’时,在email未验证之前,本地注册的用户无法登陆。’optional’ 和 ‘none’ 则不要求必须验证邮箱,两者区别是’optional’ 依然会发送验证邮件,而’none’ 则不会</p><p><code>SOCIALACCOUNT_EMAIL_VERIFICATION = 'optional' / 'mandatory' / 'none'</code>与ACCOUNT_EMAIL_VERIFICATION 同理,作用于第三方账号的注册,默认值等于前者,</p><p><code>LOGIN_URL = '/accounts/login'</code> 当用户验证邮件里的验证链接后,如果当前没有登录的用户,则会指向LOGIN_URL</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Django</tag>
</tags>
</entry>
<entry>
<title>Sublime-text3-3176版本激活</title>
<url>/2018/12/01/Sublime-text3-3176%E7%89%88%E6%9C%AC%E6%BF%80%E6%B4%BB/</url>
<content><![CDATA[<p><img data-src="https://i.loli.net/2018/12/02/5c02df25e5ec3.png" alt="1201142054.png"></p><h3 id="步骤一:"><a href="#步骤一:" class="headerlink" title="步骤一:"></a>步骤一:</h3><p>更改hosts:sudo vim /private/etc/hosts</p><figure class="highlight css"><table><tr><td class="code"><pre><span class="line">127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span> <span class="selector-tag">www</span><span class="selector-class">.sublimetext</span><span class="selector-class">.com</span></span><br><span class="line">127<span class="selector-class">.0</span><span class="selector-class">.0</span><span class="selector-class">.1</span> <span class="selector-tag">license</span><span class="selector-class">.sublimehq</span><span class="selector-class">.com</span></span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="步骤二:"><a href="#步骤二:" class="headerlink" title="步骤二:"></a>步骤二:</h3><p>在Sublime里面Help–>License里面输入下面激活码:</p><figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">----- BEGIN LICENSE -----</span><br><span class="line">sgbteam</span><br><span class="line">Single User License</span><br><span class="line">EA7E<span class="number">-1153259</span></span><br><span class="line"><span class="number">8891</span>CBB9 F1513E4F <span class="number">1</span>A3405C1 A865D53F</span><br><span class="line"><span class="number">115</span>F202E <span class="number">7</span>B91AB2D <span class="number">0</span>D2A40ED <span class="number">352</span>B269B</span><br><span class="line"><span class="number">76E84</span>F0B CD69BFC7 <span class="number">59</span>F2DFEF E267328F</span><br><span class="line"><span class="number">215652</span>A3 E88F9D8F <span class="number">4</span>C38E3BA <span class="number">5</span>B2DAAE4</span><br><span class="line"><span class="number">969624E7</span> DC9CD4D5 <span class="number">717</span>FB40C <span class="number">1</span>B9738CF</span><br><span class="line"><span class="number">20</span>B3C4F1 E917B5B3 <span class="number">87</span>C38D9C ACCE7DD8</span><br><span class="line"><span class="number">5</span>F7EF854 <span class="number">86</span>B9743C FADC04AA FB0DA5C0</span><br><span class="line">F913BE58 <span class="number">42</span>FEA319 F954EFDD AE881E0B</span><br><span class="line">------ END LICENSE ------</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Software</tag>
</tags>
</entry>
<entry>
<title>蓝桥杯嵌入式与开发组——历年比赛试题与源码</title>
<url>/2018/12/01/%E8%93%9D%E6%A1%A5%E6%9D%AF%E5%B5%8C%E5%85%A5%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E7%BB%84%E2%80%94%E2%80%94%E5%8E%86%E5%B9%B4%E6%AF%94%E8%B5%9B%E8%AF%95%E9%A2%98%E4%B8%8E%E6%BA%90%E7%A0%81/</url>
<content><![CDATA[<h1 id="蓝桥杯历年比赛试题与源码(嵌入式与开发组)"><a href="#蓝桥杯历年比赛试题与源码(嵌入式与开发组)" class="headerlink" title="蓝桥杯历年比赛试题与源码(嵌入式与开发组)"></a>蓝桥杯历年比赛试题与源码(嵌入式与开发组)</h1><p>If you appreciate the content 📖, support projects visibility, give 👍| ⭐| 👏</p><p>Github: <a href="https://github.com/nature1995/lanqiao-competition-code" target="_blank" rel="noopener">https://github.com/nature1995/lanqiao-competition-code</a></p><h3 id="第九届决赛模拟题"><a href="#第九届决赛模拟题" class="headerlink" title="第九届决赛模拟题"></a>第九届决赛模拟题</h3><h3 id="第九届省赛模拟题"><a href="#第九届省赛模拟题" class="headerlink" title="第九届省赛模拟题"></a>第九届省赛模拟题</h3><h3 id="第八届决赛-频率控制器的功能设计与实现"><a href="#第八届决赛-频率控制器的功能设计与实现" class="headerlink" title="第八届决赛-频率控制器的功能设计与实现"></a>第八届决赛-频率控制器的功能设计与实现</h3><h3 id="第八届省赛-模拟升降控制器控制器"><a href="#第八届省赛-模拟升降控制器控制器" class="headerlink" title="第八届省赛-模拟升降控制器控制器"></a>第八届省赛-模拟升降控制器控制器</h3><h3 id="第七届决赛-温湿度监控设备"><a href="#第七届决赛-温湿度监控设备" class="headerlink" title="第七届决赛-温湿度监控设备"></a>第七届决赛-温湿度监控设备</h3><h3 id="第七届省赛-模拟液位检测告警系统-第二版"><a href="#第七届省赛-模拟液位检测告警系统-第二版" class="headerlink" title="第七届省赛-模拟液位检测告警系统 第二版"></a>第七届省赛-模拟液位检测告警系统 第二版</h3><h3 id="第六届决赛-电压测量与互补-PWM-信号输出"><a href="#第六届决赛-电压测量与互补-PWM-信号输出" class="headerlink" title="第六届决赛-电压测量与互补 PWM 信号输出"></a>第六届决赛-电压测量与互补 PWM 信号输出</h3><h3 id="第六届省赛-电压测量监控设备"><a href="#第六届省赛-电压测量监控设备" class="headerlink" title="第六届省赛-电压测量监控设备"></a>第六届省赛-电压测量监控设备</h3><h3 id="第五届省赛-双通道方波频率检测与倍频输出"><a href="#第五届省赛-双通道方波频率检测与倍频输出" class="headerlink" title="第五届省赛-双通道方波频率检测与倍频输出"></a>第五届省赛-双通道方波频率检测与倍频输出</h3><h3 id="第四届省赛-双路输出控制器"><a href="#第四届省赛-双路输出控制器" class="headerlink" title="第四届省赛-双路输出控制器"></a>第四届省赛-双路输出控制器</h3>]]></content>
<categories>
<category>源码</category>
</categories>
<tags>
<tag>Hardware</tag>
<tag>STM32</tag>
</tags>
</entry>
<entry>
<title>Gitment评论功能接入Jekyll & Hexo教程</title>
<url>/2018/11/29/Gitment%E8%AF%84%E8%AE%BA%E5%8A%9F%E8%83%BD%E6%8E%A5%E5%85%A5Jekyll_Hexo%E6%95%99%E7%A8%8B/</url>
<content><![CDATA[<h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>之前接入的评论功能,都因为各种原因被放弃了,今天从新计划接入评论功能,所以就在了今天这篇文章。</p><h3 id="评论模块"><a href="#评论模块" class="headerlink" title="评论模块"></a>评论模块</h3><ul><li><strong>多说</strong> - 最多用户使用的评论,但遗憾2017年6月将暂定服务;不建议新用户使用,但为旧用户保留,也感谢多说一路的陪伴;</li><li><strong>网易云跟帖</strong> - 网易提供的评论组件,功能比较简单,性能优秀;管理后台在查询上还不算特别智能,但足够普通用户使用;</li><li><strong>畅言</strong> - 搜狐提供的评论组件,功能丰富,体验优异;但必须进行域名备案。只要域名备过案就可以通过审核。</li><li><strong>Disqus</strong> - 国外使用较多的评论组件。万里长城永不倒,一枝红杏出墙来,你懂的。</li></ul><p>以上评论模块应该大家都知道,多说和网易云跟帖没有了,畅言要备案,对于对于挂靠在GitHub的博客非常的不友好,放弃!Disqus,不希望自己的博客,可以不分国界!也放弃!</p><p>所以,将是我们要介绍的Gitment!</p><a id="more"></a><h3 id="Gitment评论模块"><a href="#Gitment评论模块" class="headerlink" title="Gitment评论模块"></a>Gitment评论模块</h3><blockquote><p>Gitment is a comment system based on GitHub Issues, which can be used in the frontend without any server-side implementation.</p><p>Gitment 基于 GitHub Issues 的评论系统。支持在前端直接引入,不需要任何后端代码。可以在页面进行登录、查看、评论、点赞等操作,同时有完整的 Markdown / GFM 和代码高亮支持。尤为适合各种基于 GitHub Pages 的静态博客或项目页面。</p></blockquote><p>想了解具体示例,可以点击查看官方Demo Page:<a href="https://link.jianshu.com?t=https%3A%2F%2Fimsun.github.io%2Fgitment%2F" target="_blank" rel="noopener">Gitment Demo</a></p><p>具体的接入我就不在这里说了,本文末有参考链接大家可以去了解学习,主要是说一下其中的坑点。</p><h3 id="Gitment坑点小结"><a href="#Gitment坑点小结" class="headerlink" title="Gitment坑点小结"></a>Gitment坑点小结</h3><h5 id="1、owner-‘Your-GitHub-ID’"><a href="#1、owner-‘Your-GitHub-ID’" class="headerlink" title="1、owner: ‘Your GitHub ID’"></a>1、owner: ‘Your GitHub ID’</h5><p><code>owner: '你的 GitHub ID',</code></p><p>可以是你的GitHub用户名,也可以是github id,建议直接用GitHub用户名就可以。</p><h5 id="2、repo-‘The-repo-to-store-comments"><a href="#2、repo-‘The-repo-to-store-comments" class="headerlink" title="2、repo: ‘The repo to store comments"></a>2、repo: ‘The repo to store comments</h5><p><code>repo: '存储评论的 github repo'</code></p><p>这个是你要存储评论内容的仓库名,可以与博客下的仓库,也可以新建一个仓库专门存储评论内容的。</p><h5 id="3、Error-Not-Found问题"><a href="#3、Error-Not-Found问题" class="headerlink" title="3、Error: Not Found问题"></a>3、Error: Not Found问题</h5><p>owner或者repo配置错误了,注意名字和仓库名字的大小写。</p><h5 id="4、Error-Comments-Not-Initialized"><a href="#4、Error-Comments-Not-Initialized" class="headerlink" title="4、Error: Comments Not Initialized"></a>4、Error: Comments Not Initialized</h5><ul><li>在注册OAuth Application这个步骤中,给Authorization callback URL指定的地址错了</li><li>还没有在该页面的Gitment评论区登陆GitHub账号</li></ul><p>如果还是不行,可以参考另一个情况 <a href="https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fimsun%2Fgitment%2Fissues%2F95" target="_blank" rel="noopener">Error: Comments Not Initialized · Issue #95 · imsun/gitment</a></p><h5 id="5、Error:validation-failed"><a href="#5、Error:validation-failed" class="headerlink" title="5、Error:validation failed"></a>5、Error:validation failed</h5><p>issue的标签label有长度限制!labels的最大长度限制是50个字符。</p><p><code>id: '页面 ID', // 可选。默认为 location.href</code></p><p>这个id的作用,就是针对一个文章有唯一的标识来判断这篇本章。</p><p>在issues里面,可以发现是根据网页标题来新建issues的,然后每个issues有两个labels(标签),一个是gitment,另一个就是id。</p><p>所以明白了原理后,就是因为id太长,导致初始化失败,现在就是要让id保证在50个字符内。</p><p>对应配置的id为:</p><figure class="highlight gml"><table><tr><td class="code"><pre><span class="line"><span class="symbol">id</span>: <span class="string">'<%= page.title %>'</span></span><br></pre></td></tr></table></figure><p>如果用网页标题也不能保证在50个字符!<br>最后,我用文章的时间,这样长度是保证在50个字符内,完美解决!(避免了文章每次更新标题或路径时,会重新创建一个issue评论的问题。)</p><figure class="highlight applescript"><table><tr><td class="code"><pre><span class="line"><span class="built_in">id</span>: '<%= page.<span class="built_in">date</span> %>'</span><br></pre></td></tr></table></figure><h5 id="6、gitment的汉化"><a href="#6、gitment的汉化" class="headerlink" title="6、gitment的汉化"></a>6、gitment的汉化</h5><p>只需到模板里将原来定义CSS和JS的那两行改成:</p><figure class="highlight routeros"><table><tr><td class="code"><pre><span class="line"><link <span class="attribute">rel</span>=<span class="string">"stylesheet"</span> <span class="attribute">href</span>=<span class="string">"https://billts.site/extra_css/gitment.css"</span>></span><br><span class="line"><script <span class="attribute">src</span>=<span class="string">"https://billts.site/js/gitment.js"</span>></script></span><br></pre></td></tr></table></figure><p>即可。来源:<a href="https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fimsun%2Fgitment%2Fissues%2F104" target="_blank" rel="noopener">https://github.com/imsun/gitment/issues/104</a></p><h5 id="7、所有文章一键初始化评论"><a href="#7、所有文章一键初始化评论" class="headerlink" title="7、所有文章一键初始化评论"></a>7、所有文章一键初始化评论</h5><p>配置正确的文章,第一次显示,提示还没有初始化:</p><p><img data-src="https://i.loli.net/2018/11/30/5c007d29f22e0.png" alt="99517-c690c994c6e43496.png"></p><p>文章作者需要登陆GitHub授权后,会显示初始化按钮(注意,不要多点按钮,否则issues出现多条一样的):</p><p><img data-src="https://i.loli.net/2018/11/30/5c007d29f23fb.png" alt="99517-bddc67793047456b.png"></p><p>点击初始化按钮后,如果正常就会出现:</p><p><img data-src="https://i.loli.net/2018/11/30/5c007d29a5614.png" alt="99517-21a83b5ea0ee7f78.png"></p><p>关于自动初始化所有文章的功能,到本文编写时,还没有一个完善的解决方法,就是用脚本来执行自动化,有需要的可以详细了解:<a href="https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fimsun%2Fgitment%2Fissues%2F5" target="_blank" rel="noopener">https://github.com/imsun/gitment/issues/5</a></p><h5 id="8、请保持克制,切勿滥用。"><a href="#8、请保持克制,切勿滥用。" class="headerlink" title="8、请保持克制,切勿滥用。"></a>8、请保持克制,切勿滥用。</h5><p>作者提醒使用本项目的用户,请保持克制,切勿滥用。<a href="https://link.jianshu.com?t=https%3A%2F%2Fimsun.net%2Fposts%2Fgitment-introduction%2F" target="_blank" rel="noopener">详见 Gitment:使用 GitHub Issues 搭建评论系统</a></p><h5 id="9、订阅issue"><a href="#9、订阅issue" class="headerlink" title="9、订阅issue"></a>9、订阅issue</h5><p>issue订阅,有新评论时就可以通过邮件提醒,这个功能是把双刃剑,因为有些垃圾订阅邮件骚扰,大家看着用吧。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>Gitment目前来说,已经解决了一个博客简单的评论功能,点赞 / 有完整的 Markdown / GFM 和代码高亮支持!</p><p>通过Gitment至少可以证明一点,生产力的发展有无限可能,环境的限制,会有的各种奇异的想法,或者奇异的东西出现,Gitment就是一个完美的证明,充分解决了一个简单博客的评论功能。另一方面,与国内的生活要求希望大家能够遵守规则,让GitHub和Gitment在我们生活中快乐成长!</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Gitment</tag>
</tags>
</entry>
<entry>
<title>Jupyter Lab Config on Raspberry Pi</title>
<url>/2018/11/29/jupyter-lab-config-raspi/</url>
<content><![CDATA[<h2 id="To-activate-this-environment-use"><a href="#To-activate-this-environment-use" class="headerlink" title="To activate this environment, use"></a>To activate this environment, use</h2><pre><code>$ conda activate base
$ conda activate tensorflow</code></pre><h4 id="树莓派:-source-activate-tensorflow"><a href="#树莓派:-source-activate-tensorflow" class="headerlink" title="树莓派: source activate tensorflow"></a>树莓派: source activate tensorflow</h4><h2 id="To-deactivate-an-active-environment-use"><a href="#To-deactivate-an-active-environment-use" class="headerlink" title="To deactivate an active environment, use"></a>To deactivate an active environment, use</h2><pre><code>$ conda deactivate</code></pre><h4 id="树莓派:-source-deactivate-tensorflow"><a href="#树莓派:-source-deactivate-tensorflow" class="headerlink" title="树莓派: source deactivate tensorflow"></a>树莓派: source deactivate tensorflow</h4><p>conda install jupyter notebook<br>conda install jupyterlab</p><p>jupyter notebook –ip=0.0.0.0 –no-browser –allow-root<br>jupyter lab –ip=0.0.0.0 –no-browser –allow-root</p><p>不输出日志 jupyter lab –ip=0.0.0.0 –no-browser –allow-root >/dev/null 2>&1 &<br>jupyter lab >/dev/null 2>&1 &</p>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Raspberry Pi</tag>
<tag>Jupyter</tag>
</tags>
</entry>
<entry>
<title>Keras:安装graphviz和pydot后出现RuntimeError:无法导入pydot</title>
<url>/2018/11/29/Keras%E5%AE%89%E8%A3%85graphviz%E5%92%8Cpydot%E5%90%8E%E5%87%BA%E7%8E%B0RuntimeError%E6%97%A0%E6%B3%95%E5%AF%BC%E5%85%A5pydot/</url>
<content><![CDATA[<h3 id="首先Conda安装graphviz:"><a href="#首先Conda安装graphviz:" class="headerlink" title="首先Conda安装graphviz:"></a>首先Conda安装graphviz:</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">pip install graphviz </span><br><span class="line">pip install pydot</span><br></pre></td></tr></table></figure><h3 id="其次在运行的-py文件前面加入如下内容"><a href="#其次在运行的-py文件前面加入如下内容" class="headerlink" title="其次在运行的.py文件前面加入如下内容:"></a>其次在运行的.py文件前面加入如下内容:</h3><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os os.environ[<span class="string">"PATH"</span>] += os.pathsep + <span class="string">'C:\\Users\\10057491\\AppData\\Local\\Continuum\\anaconda3\\envs\\tensorflow\\Library\\bin\\graphviz'</span></span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category>教程</category>
</categories>
<tags>
<tag>Anaconda</tag>
<tag>Keras</tag>
</tags>
</entry>
<entry>
<title>Centos 配置mailx使用外部smtp发送邮件</title>
<url>/2018/11/29/Centos-%E9%85%8D%E7%BD%AEmailx%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8smtp%E5%8F%91%E9%80%81%E9%82%AE%E4%BB%B6/</url>
<content><![CDATA[<h2 id="Centos-配置mailx使用外部smtp发送邮件"><a href="#Centos-配置mailx使用外部smtp发送邮件" class="headerlink" title="Centos 配置mailx使用外部smtp发送邮件"></a>Centos 配置mailx使用外部smtp发送邮件</h2><h3 id="安装mailx"><a href="#安装mailx" class="headerlink" title="安装mailx"></a>安装mailx</h3><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">yum install mailx</span><br></pre></td></tr></table></figure><h3 id="配置mailx"><a href="#配置mailx" class="headerlink" title="配置mailx"></a>配置mailx</h3><p>推荐163邮箱,当然,QQ邮箱也是可以的,PS:记得要进邮箱打开SMTP</p><figure class="highlight sh"><table><tr><td class="code"><pre><span class="line">vi /etc/mail.rc //如果不存在,则编辑/etc/nail.rc</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 在文件的末尾加入下面代码,相应帐号密码填写自己的帐号密码</span></span><br><span class="line">set <span class="keyword">from</span>=<span class="string">"xxx@163.com"</span></span><br><span class="line">set smtp=smtp<span class="number">.163</span>.com</span><br><span class="line">set smtp-auth-user=xxx</span><br><span class="line">set smtp-auth-password=邮箱密码</span><br><span class="line">set smtp-auth=login</span><br></pre></td></tr></table></figure><a id="more"></a><h3 id="使用mailx发送邮件"><a href="#使用mailx发送邮件" class="headerlink" title="使用mailx发送邮件"></a>使用mailx发送邮件</h3><p>发件人名称可不添加,第二步已配置过<br>假设邮件内容存储于mesg文件中,那么可以用如下2个方法:</p><figure class="highlight vim"><table><tr><td class="code"><pre><span class="line">mailx -s <span class="string">"发件人名称 邮件标题"</span> xxx@<span class="number">163</span>.<span class="keyword">com</span> < mesg</span><br><span class="line"><span class="keyword">cat</span> mesg | mailx -s <span class="string">"发件人名称 邮件标题"</span> xxx@<span class="number">163</span>.<span class="keyword">com</span></span><br></pre></td></tr></table></figure><p>多个收件人之间用逗号分隔:</p><figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">cat mesg | mailx -s <span class="string">"发件人名称 邮件标题"</span> <span class="symbol">xxx@</span><span class="number">163.</span>com,<span class="symbol">xxx2@</span><span class="number">163.</span>com,<span class="symbol">xxx3@</span><span class="number">163.</span>com</span><br></pre></td></tr></table></figure><p>也可以直接从命令行输入邮件内容:</p><figure class="highlight autoit"><table><tr><td class="code"><pre><span class="line">mailx -s <span class="string">"发件人名称 邮件标题"</span> xxx<span class="symbol">@163</span>.com <span class="meta">##输入完后回车按Ctrl+D提交发送</span></span><br><span class="line">echo hello word | mailx -v -s <span class="string">" title"</span> xxx3<span class="symbol">@163</span>.com</span><br></pre></td></tr></table></figure><h4 id="NOTE"><a href="#NOTE" class="headerlink" title="NOTE:"></a>NOTE:</h4><p>记得要进邮箱打开SMTP<br>配置好后,记住查看是否打开代理(例如翻墙VPN),代理可能会导致Telnet不通25端口</p>]]></content>
<categories>
<category>服务器</category>
</categories>
<tags>
<tag>Centos</tag>
</tags>
</entry>
<entry>
<title>VNC和远程桌面的区别</title>
<url>/2018/11/29/VNC%E5%92%8C%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2%E7%9A%84%E5%8C%BA%E5%88%AB/</url>
<content><![CDATA[<h3 id="VNC和远程桌面的区别"><a href="#VNC和远程桌面的区别" class="headerlink" title="VNC和远程桌面的区别"></a>VNC和远程桌面的区别</h3><p>我们可以通过VNC(Virtual Network Computing)和远程桌面来连接到远程的计算机上,但是两者是有一定的区别的。</p><h3 id="VNC"><a href="#VNC" class="headerlink" title="VNC"></a>VNC</h3><p>VNC使用的是RFB协议来做屏幕分享和远程操作的软件,由著名的AT&T欧洲研究实验室开发。使用时需要客户端软件和服务器软件配合使用。</p><h3 id="远程桌面"><a href="#远程桌面" class="headerlink" title="远程桌面"></a>远程桌面</h3><p>远程桌面使用RDP(Remote Desktop Protocol)远程桌面协议,大部分Windows都默认支持此协议,可以远程接入操作桌面。</p><h3 id="RFB和RDP区别"><a href="#RFB和RDP区别" class="headerlink" title="RFB和RDP区别"></a>RFB和RDP区别</h3><p>RFB是在服务器端将窗口在显存中画好之后将图像传给客户端,客户端只充当一个图像解码显示的角色; RDP则将画图的工作交给了客户端,服务器需要了解客户端显示能力的具体情况,以便作出相应调整。</p><p>RFB主要传图像,RDP主要传指令。就一般应用而言,RFB数据量太大,RDP对客户端要求较高,因此RFB适用于瘦客户端,RDP适用于低速网络。</p>]]></content>
<categories>
<category>Knowledge</category>
</categories>
<tags>
<tag>Virtualization</tag>
</tags>
</entry>
<entry>
<title>为什么北美东部冬季经常有暴风雪,而中国冷而少雪?</title>
<url>/2018/11/15/%E4%B8%BA%E4%BB%80%E4%B9%88%E5%8C%97%E7%BE%8E%E4%B8%9C%E9%83%A8%E5%86%AC%E5%AD%A3%E7%BB%8F%E5%B8%B8%E6%9C%89%E6%9A%B4%E9%A3%8E%E9%9B%AA%EF%BC%8C%E8%80%8C%E4%B8%AD%E5%9B%BD%E5%86%B7%E8%80%8C%E5%B0%91%E9%9B%AA%EF%BC%9F/</url>
<content><![CDATA[<h3 id="学术预警!长文预警!多图预警!"><a href="#学术预警!长文预警!多图预警!" class="headerlink" title="学术预警!长文预警!多图预警!"></a>学术预警!长文预警!多图预警!</h3><p>北美冬季的气候处于西风带的控制之下,西风带形成了一条很长的高空急流(Jet stream)。这条急流夏天的时候在加拿大北部地区,对美国的气候不造成明显影响,而冬天的时候,急流从美国中间穿过,形成了整个冬季的冷暖气团对峙局面。高空急流形成的原因,高中地理都学过,我就不解释了。</p><p><img data-src="https://i.loli.net/2018/12/25/5c218825d4bd0.jpg" alt="2018.11.16-01.jpg"></p><p>美国冬季典型的天气分布是这样的,以今年更为明显。今年冬天整个西部温暖如春,比平均温度高15<del>25华氏度,东部冰天雪地,比平均温度低15</del>25华氏度。<br><a id="more"></a></p><p><img data-src="https://i.loli.net/2018/12/25/5c218826297ca.jpg" alt="2018.11.16-02.jpg"></p><p>这条高空急流从太平洋吹过来,在美国西海岸遇到了高山,被推到了高纬度,在华盛顿州的迎风坡造成了大量降水,又一路绕过落基山脉,来到了大平原。大平原地区没有任何山脉的阻挡,急流可以一路南下直到德克萨斯州,最南的时候可以到达佛罗里达州北部。这条急流所在的区域一般约等于0度等温线,所以冬天的时候来冷空气,佛罗里达州北部可能都会产生霜冻灾害。为什么无法再往南推进了,因为墨西哥湾的暖气团太强大了,所以佛罗里达州的南部城市比如迈阿密,从来没有经历过霜冻灾害。</p><p><img data-src="https://i.loli.net/2018/12/25/5c2188251a425.jpg" alt="2018.11.16-03.jpg"></p><p>拿答主写答案的当前时间为例,北边再冷的时候,佛罗里达州南部都是暖和的,佛罗里达三面环海,完全受到墨西哥湾暖流的控制。(图中深蓝色是0度等温线)与此同时,一场暴风雪正在酝酿中</p><p><img data-src="https://i.loli.net/2018/12/25/5c2188262368e.jpg" alt="2018.11.16-04.jpg"></p><p>现在的降水不强大,因为没遇到什么水汽。两天后(2015/02/25)当这股降水被吹到北卡罗来纳州,遇到北大西洋暖流带来的的暖气团时,冷暖空气交汇,将会给当地造成暴雪。</p><p><img data-src="https://i.loli.net/2018/12/25/5c218825d8a12.jpg" alt="2018.11.16-05.jpg"></p><p>5~8英寸约等于12到20厘米深的积雪。这对于中国而言,绝对是大暴雪了,美国人民表示见得多了,什么样的大雪没见过?今年一月底东北部的那场大雪,比这个高到不知道哪里去了。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884f93828.jpg" alt="2018.11.16-06.jpg"></p><p>好了话说回来。这条高空急流是会经常随意摆动的,当加拿大的极地冷空气势力强的时候,就被推得更南下,当墨西哥湾暖气团势力强的时候,就会被往北推。由于美国东部地形比较平坦,所以高空急流的位移幅度非常大。南则兵临佛罗里达,北则退到缅因州。<br><img data-src="https://i.loli.net/2018/12/25/5c21884f91939.jpg" alt="2018.11.16-07.jpg"></p><p>所以凡是高空急流扫过的地方,都会形成冷锋或暖锋,带来丰富的降水。今年冬天太冷了,所以佛罗里达州雨也下的多了,正常年份是,佛罗里达类似于国内的海南岛,因为冷空气够不着,所以没法形成降水,冬季很干燥。但是美国东海岸越往北,冬季的降水比例越大。以我居住的波士顿为例,一年四季的降水分配情况基本上是平均的,三四月份和十一十二月份降水较多,因为那个时段冷暖交替,被高空急流扫过的频率比较高。今年下雪下疯了,人行道上堆起来的雪太高了,我得跳起来才能看见马路对面……</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884fc6ea8.jpg" alt="2018.11.16-08.jpg"></p><p>对比一下和波士顿同纬度的中国城市沈阳。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884fc8c50.jpg" alt="2018.11.16-09.jpg"></p><p>在气温上来对比,沈阳冬天比波士顿冷,夏天比波士顿热,气温年较差比较大,因为中国东部的气候比较大陆性,美国东部的气候受到哈得孙湾和五大湖等水体的影响比较海洋性。有趣的是,美国东部的气候在冬天比中国东部要极端的多。看看一月极端最高温度,波士顿是22度,沈阳是8度,波士顿的日极端比日平均高温要高20度,而沈阳只有13度。波士顿的极端低温也比平均低温要低20度,沈阳小一点,17度。生活在中国北方的人都知道,只要河一结冰,开春之前都是不会化的,所以可以放心的冰上走,而美国东北部的河流一般不会整个封冻,冷几天就冻上了,暖几天就化了,除非遇到今年这种特别冷的年份,不敢在冰上走。中国东部的冬天是稳定的,比如沈阳的一月,每天都是最低零下十几度,最高零下几度,但波士顿的一月,有时候最低零下十几度,有时候最低甚至有零上五六度。之所以产生这么大的变化幅度,因为高空急流的位置不同。当高空急流位于南方时,美国东北部就会受到加拿大的极地高压的影响,而高空急流暂时减弱,墨西哥湾暖流占优势时,美国东北部就会受到大西洋的低气压影响。如果这时候突然来了冷空气,就会形成暴风雪。相比,因为中国近海没有暖流,所以中国的东北部几乎一直在西伯利亚-蒙古高压的控制之下,气温稳定而冷,不会出现极端的变化。没有冷暖气团的相遇,就不会形成多少降水,这就是为什么中国北方的冬天干而冷。在中国南方,因为纬度比较低,太阳辐射强,晴天容易升温,遇到冷空气的时候一般就会形成冷锋下雨或者下雪,冬季比较湿冷。但是再怎么湿冷,由于控制中国南方的暖气团势力远远不如墨西哥湾的暖气团强,所以不会形成明显的降水,下雨也只是下小雨,不会出现像夏天一样的暴雨。在中国的东部,冬天影响天气的几乎只有一个变量,那就是有没有冷空气来。如果没有冷空气来,尤其是在黄河以南,天气会随着晴天越来越暖和,直到来一个冷空气大降温,几天的暖和又回到解放前,气温的变化一般是这样的。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884f51e3c.jpg" alt="2018.11.16-10.jpg"></p><p>冷空气来大降温,几天出太阳越来越暖和,直到又来一个冷空气。秋天的时候冷空气的降温更快,太阳晒暖和的速度赶不上,所以越来越冷;春天的时候太阳晒得更快,来一个冷空气倒春寒一下。而美国东部冬天的天气有两个变量影响,不仅要考虑从背面袭来的冷空气,还要考虑从南面袭来的暖空气。这两方打斗不停,所经之处下雨下雪,类似梅雨的形成原因。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884f46cc5.jpg" alt="2018.11.16-11.jpg"></p><p>冷空气控制下冷几天,暖空气控制下暖和几天,反复无常,毫无规律可循。所有直角的地方,基本上都得下雨下雪。暖空气来了就暖锋下雨,冷空气来了就冷锋下雪。在美国东部,尤其是东北部生活的时候你会发现一个现象,就是阴天往往比晴天暖和。因为晴天的时候基本代表高空急流在你南边,高气压控制,冷;阴天的时候基本代表高空急流在你北边,低气压控制,暖。还有一个现象就是,早上比傍晚暖和,或者第二天早上比前一天傍晚暖和。发生这种事的时候,那就是高空急流在移动,扫过你的所在地了。美国东部的冬天气温可以说是完全不受太阳辐射影响,连晒几天太阳并不会明显增温,连下几天雨也不一定会降温。气温完全取决于你的所在地当前是受到什么气团的控制,而气团被高空急流所分割开,所以美国的天气预报别的先不说,先在地图上画一条线,告诉你明天高空急流在哪里,你自己看吧,你在它北边你就冷,你在它南边你就暖和,你被它压着你就很可能下雨下雪。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884f48ce7.jpg" alt="2018.11.16-12.jpg"></p><p>如果是这种情况,那么就是西海岸冷,东海岸暖和,高空急流从南方绕过去,反正一般不会从美国中部山最高的地方穿过去。雷达蓝色的是下雪,绿色的是下雨,这就是一场横扫中部的暴风雪。今年为什么东北部下雪那么多,因为今年的高空急流长期保持这种形状。</p><p><img data-src="https://i.loli.net/2018/12/25/5c21884f95b9d.jpg" alt="2018.11.16-13.jpg"></p><p>从西海岸北部穿过,给加拿大西海岸带来充沛降水,然后从中部杀下来,再从沿海走上去。由于盛行西风,当冷空气穿过的时候遇到五大湖的水汽,再遇到北大西洋暖流,在新英格兰地区带来百年一遇的连续的暴风雪。不仅下雪,因为对流太强烈,还会打雷,冬天北方打雷这在中国是无法想象的!最后还得吐槽一句啊,美国的天气预报信息太全面了,The Weather Channel可以显示每时每刻当前全国的气温降水风速分布图等等,中国天气网就没这功能,要看雷达图也只能看一小块局部的,更别说全国当前气温分布图了。中央气象台还得提高姿势水平啊,不要动不动一下雪了就弄个大新闻,跑得比香港记者还快。</p>]]></content>
<categories>
<category>Diary</category>
</categories>
<tags>
<tag>随笔</tag>
</tags>
</entry>
<entry>
<title>寒假计划NASA之旅</title>
<url>/2018/11/10/%E5%AF%92%E5%81%87%E8%AE%A1%E5%88%92NASA%E4%B9%8B%E6%97%85/</url>
<content><![CDATA[<h4 id="休斯顿(Houston)"><a href="#休斯顿(Houston)" class="headerlink" title="休斯顿(Houston)"></a>休斯顿(Houston)</h4><p>德州最大城市,美国第四大城市。提及休斯顿,人们最耳熟能详的莫过于姚明曾效力的NBA火箭队了。然而,在这里,并非只有篮球爱好者才能寻迹自己的梦想,包容、多元的休斯顿也为艺术家们提供了丰富多彩的舞台。</p><p><img data-src="https://i.loli.net/2018/12/25/5c2187db218d9.jpeg" alt="houston1.jpeg"></p><a id="more"></a><p>休斯顿整个城市都充满了浓厚的艺术氛围,无论是露天的音乐派对,还是即兴的街头表演,抑或是剧院和艺术中心演出的芭蕾舞和歌剧……在这里,你随时可以欣赏到高质量的艺术表演。小编本次就带大家开启休斯顿艺术之行。</p><p><img data-src="https://i.loli.net/2018/12/25/5c2187db6a5f5.jpeg" alt="houston2.jpeg"></p>]]></content>
<categories>
<category>Diary</category>
</categories>
<tags>
<tag>游记</tag>
</tags>
</entry>
<entry>
<title>Markdown语法</title>
<url>/2018/11/10/Markdown%E8%AF%AD%E6%B3%95/</url>
<content><![CDATA[<h1 id="Markdown语法"><a href="#Markdown语法" class="headerlink" title="Markdown语法"></a>Markdown语法</h1><h1 id="一级标题"><a href="#一级标题" class="headerlink" title="一级标题"></a>一级标题</h1><h2 id="二级标题"><a href="#二级标题" class="headerlink" title="二级标题"></a>二级标题</h2><h5 id="五级标题"><a href="#五级标题" class="headerlink" title="五级标题"></a>五级标题</h5><ul><li>列表第一项</li><li>列表第二项<a id="more"></a></li></ul><ol><li>有序列表第一项</li><li>有序列表第二项<br><a href="链接地址">标题</a></li></ol><p><img data-src="https://i.loli.net/2018/12/25/5c2187a35e52f.png" alt="markdown_google.png"></p><p><em>斜体</em><br><strong>粗体</strong></p><blockquote><p>引用段落<br><code>代码块</code></p></blockquote>]]></content>
</entry>
<entry>
<title>开始Hexo之旅</title>
<url>/2018/11/09/%E5%BC%80%E5%A7%8BHexo%E4%B9%8B%E6%97%85/</url>
<content><![CDATA[<p>今天开始部署属于自己的Hexo博客。</p>]]></content>
<tags>
<tag>随笔</tag>
</tags>
</entry>
</search>