iOS开发-关于iOS11适配的一些坑

安全区域(Safe Area)
在iOS11以前, 自定义UIViewController时, 从来没有考虑过topLayoutGuide和bottomLayoutGuide这两个属性.但是在iOS11出来之后,这两个属性被废弃,取而代之的就是safeArea的概念,safeArea是描述你的视图部分不被任何内容遮挡的方法。 它提供两种方式:safeAreaInsets或safeAreaLayoutGuide来提供给你safeArea的参照值,即 insets 或者 layout guide。 safeArea区域如图所示:

iPhone X图
在除了iPhone X以外的型号手机上, UINavigationController 就是 topLayoutGuide区域. UITabBarController就是bottomLayoutGuide.但是这两个属性在iOS11已经被取代了. 取而代之的就是safeArea区域(iPhone X图区域4部分)

当我们自定义一个UIViewController, 内部嵌套了UINavigationController 或者UITabBarController的时候. 运行程序会出现以下错误:

图1-1
原因是少了:

具体是为什么,没有细研究.不过解决办法如下:

图1-2
因为我是创建的空项目所以只有LaunchScreen.storyboard..如果你的项目里还有Main.storyboard,那么 你需要把第四个勾勾也去掉.

iPhone X 竖屏UINavigationController和UITabBarController尺寸问题

iPhone X竖屏
区域一: 高度30

区域二:高度14

区域三:高度44

区域五:高度49

区域六:高度14

区域七:高度20

iOS横屏UITabBarController
更新iOS11以后, UITabBarController上的图标文字由原来的垂直放置, 变成水平放置

iPhone X 横屏UINavigationController和UITabBarController尺寸问题

iPhone X横屏
iPhone X 横竖屏在尺寸上有很多细微的差别.

UINavigationController
我们打印UINavigationController navigationBar的时候发现他的高度为44.也就是上图绿色区域.但是在实际显示的时候确实红色区域,高度为32.整个高度向上12.也就是蓝色区域.

UITabBarController
我们打印UITabBarController tabBar的时候发现他的高度为32,就是橘色区域,红色区域为功能区,高度为21.我们是何止tabBar颜色的时,包括橘色区和红色区.

控制大标题的显示
待更新

屏幕未充满?底部tabbar留白
解决方案: 准备一张尺寸:1125 * 2436的启动图片, 添加到Assret中

iOS11导航栏自定义按钮偏移问题
解决方案: 通过改变按钮的 contentEdgeInsets和imageEdgeInsets的值成功改变了按钮的偏移问题,单独设置contentEdgeInsets也可达到一定的效果。

UITableview UICollectionView MJRefresh下拉刷新错乱
解决方案:iOS11弃用了automaticallyAdjustsScrollViewInsets属性,新增contentInsetAdjustmentBehavior来替代它

IOS 微信端 视频自动播放问题解决方法

问题描述:由于浏览器限制,视频不能够自动播放,需要用户手势触发才可以。

解决方法:通过微信浏览器提供的WeixinJSBridgeReady做桥接后就可以绕过这一限制。

上面链接中的操作比较繁琐,在此给出简化版本(亲测可用):

function doPlay(){
WeixinJSBridge.invoke(‘getNetworkType’, {}, function (e) {
var $video1 = $(“#video1”)
var $video2 = $(“#video2”)
$video1[0].play()
$video2[0].play()
})
}

if (window.WeixinJSBridge) {
doPlay()
} else {
document.addEventListener(“WeixinJSBridgeReady”, function(){
doPlay()
}, false);
}
需要注意的是,监听WeixinJSBridgeReady事件后,回调函数里需要调用一下invoke,在invoke中操作视频才可以生效。

另外,页面不需要引入jweixin-1.0.0.js,微信浏览器会自带api。

VUE前端界面在iOS中遇到的坑

一、滑动效果卡顿问题解决方案
在需要滑动的位置加上如下css代码:
-webkit-overflow-scrolling:touch;
以此,界面滑动卡顿问题得以解决,但是,这一行代码会带来一个副作用:
1、在滑动界面之中使用的position:fixed 无法固定下来,会随着界面进行一起滚动
解决方法:将使用的position:fixed(头部导航)写在滑动部位外部,在使用*对定位进行布局,以此解决问题
2、vue中使用v-if导致的界面初始化之后无法滑动
解决方法:将v-if改成v-show进行展示,解决界面进入之后*次不能滑动的问题

二、界面点反应慢、延时问题解决方案
经过排查,导致问题的原因是iOS嵌套界面之后,界面点击效果会自动产生一个300毫秒的延时,解决方案:
vue 引入 fastclick(npm install fastclick -S)
然后再进行引入 FastClick.attach(document.body);
问题解决
注:在引入fastclick之后,会有一个副作用:input输入框需要连续点击两次或者长按才能获取焦点,解决方法详见个人文章:
vue引入fastClick导致的输入框点击无响应问题

三、按钮点击之后会产生灰色图层解决方案
目前未发现前端有效的解决方案,本次是让iOS进行单独的处理

四、关于new Date()转换时间在iOS中不生效问题
前端界面使用new Date(‘2018-12-20 23:59:59’).getTime(),放到iOS中后发现,没有正常转换成时间戳,经过查找相关资料发现,
iOS不支持该种时间格式的转换,将代码写成:
new Date(‘2018-12-20T23:59:59’).getTime()
即可获取到正确的时间戳

五、关于部分拷贝方法在iOS中不生效问题
前端有时候需要用到拷贝部分内容到剪切板中,但是在使用时发现,部分网上提供的拷贝方法在Android中正常使用,但是在iOS中无法进行拷贝,经过试验发现一种通用的拷贝方法,如下:
/**

将内容拷贝到剪切板中
@param copyText 需要拷贝的内容
*/
function copyContent(message) { // text: 要复制的内容, callback: 回调
var input = document.createElement(“input”);
input.value = message;
document.body.appendChild(input);
input.select();
input.setSelectionRange(0, input.value.length);
document.execCommand(‘Copy’);
document.body.removeChild(input);
}
该方法亲测在Android以及iOS中都有效
六、关于input输入框在iOS中获取到焦点之后界面上移无法回落问题
在联合移动端开发过程中,会遇到input输入框获取到焦点之后,软键盘自动顶起界面,但是失去焦点之后无法回落的问题,解决方法如下,添加如下代码:
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0;
window.scrollTo(0, Math.max(scrollHeight – 1, 0));

七、关于移动端定时器运行过程中,界面点击/滑动会造成定时器卡顿的现象
原因:界面点击时,iOS会有300毫秒延时阻塞,同时界面滑动时,也会造成定时器方法的阻塞
解决方法:
1.使用fastClick结局300毫秒延时问题,参考文章 第二点
2.定时器运转期间,使用css或者js禁止界面滚动

ISO7816 调试心得

ISO7816是一种接触式的读卡协议,PSAM、SAM、手机的SIM卡都是按照这个协议定制的。协议分为2个部分,复位和数据的传输。本文档主要介绍自己的理解和在调试的过程,具体协议部分的内容需要翻看ISO7816的协议文档。本文重点是带着数据对协议进行分析。适合刚接触ISO7816的人。加深对协议的理解。

一、复位

上电复位或者手动给卡复位之后,卡会发出一段数据,这段数据是代表卡本身的一些信息,如发送的格式是什么样子的,卡支持什么样的协议,卡片的号码等等。复位的结构如下:

 

TS…………………………………………表示正向或者反向约定,强制性在

T0…………………………………………格式字符,强制性

TA(i) TB(i) TC(i) TD(i)…………… 接口字符,可选的

T1 T2…Tk…………………………………历史字符,可选的

Tck……………………………………… 检测字符,有条件的

 

*个数据是TS,我个人理解是大端和小端的问题,*个数据永远是“0X3B”或者是“0X3F”,其中“0X3B”代表正向约定,“0X3F”代表反向约定,在我见到的卡片里面,只见过“0X3B”,还没有见过“0X3F”。如果*个数据不是这两个值,那么代表接收的时序有问题,需要查看驱动或者硬件是否有问题。

 

第二个数据是T0,这个数据比较重要,他决定了下面的数据代表什么意思。也就是说,从第三个数据开始,每个数据代表什么意思是不确定的。T0的结构如下。

b8

msb

b7

b6

b5

b4

b3

b2

b1

lsb

Y(1)

K

图1 — T0编码

Y(1)……接口字节存在的标记

b5=1时TA(1)存在

b6=1时TB(1)存在

b7=1时TC(1)存在

b8=1时TD(1)存在

K……历史字节的数目,从0到15

由图1 可知,第三个数据代表什么意思,是由T0的高4位决定的。T0的低4位代表是历史字节的数目,这个后面在介绍。

 

第三个数据是有T0决定的,下面我就举一段例子,来说明。下面是我读取到的一张卡的数据。

1

0x3b

2

0x69

3

0x00

4

0x00

5

0x45

6

0x53

7

0x41

8

0x4d

9

0x10

10

0xd3

11

0x4c

12

0x8a

13

0xe6

上面这段数据是我抓取的一张T=0协议卡的复位数据。*个数据是“0x3B”,代表TS,是正向约定,就是小端的意思。第二个数据是0X69,代表T0,二进制“01101001”,则带表下面第三个数据是TB1,因为其b6位是1,那么顺下去,b7位也是1,则第四个数据就代表TC1,所以说TA(i) TB(i) TC(i) TD(i)是可选字符(i代表1,2,3,4、、、由于可能存在好多组TA、TB、TC、TD,所以用i来表示),也就是说他们不一定都存在,会根据T0和TD(i)来决定。至于TA(i) TB(i) TC(i) TD(i)是干什么用的,我们下面会介绍。这组数据由于T0的b8位由于没有TD1,所以就没有TA2、TB2、TC2、TD2、、、、、以及以后的数据,那么,剩下的数据代表什么意思的,剩下的从第5个开始到第13个,代表的是历史字符,也就是T1 T2…Tk,我们上面说了,历史字符的个数是由T0的低4位决定的,我们这组数据中,T0为0X69,低4位是9,则说明有9个历史数据,而剩下的数据正好是9个,由于*后一个字符Tck是有条件的,也就是说不一定存在,在T=0的卡中,一般是不存在Tck的。分析到这里,这组数据和我们的复位协议也都对上了。简单的总结一下,这组数据代表了这张卡是正向约定的卡,存在TB1和TC1,有9个历史字符。那么这张卡到底是T=0的卡还是T=1的卡,根据现在的情报还没办法分析出来。下面开始介绍TA(i) TB(i) TC(i) TD(i)。

 

TA(1)

— FI,位b8到b5 上的时钟率转换因子的引用。

— DI,位b4到b7上波特率校正因子的引用。

这位和etu有关系,总之是和两个数据的传输间隔时间相关。这位确定了卡支持的传输速率。

 

TB(1)  b8=0代码处

— II,位b7 b6上*大编程电流的引用。

— PI1,位b5到b1上编程电压的值。

注:接口设备可以忽略TB(1)的位b8。

这一位我也没用到过。所以这里也就不介绍了。

 

TC(1)代码(见8.5.3)

— N,计算八位额外保护时间的引用。

—  这一位我也没用到过。所以这里也就不介绍了。

 

下面是重点TD1

字节TD1由两部分组成。

— 位b8到b5构成Y2);每个等于1的位指明接口字节的存在。

— 位b4到b1构成8.2中定义的参数T的值。

 

b8

msb

b7

b6

b5

b4

b3

b2

b1

lsb

Y(2)

T

图2 — TD(1)编码

Y(2)……接口字节存在的标记

b5=1时TA(2)存在

b6=1时TB(2)存在

b7=1时TC(2)存在

b8=1时TD(2)存在

T……协议参考和/或接口字节限制符

TD1和T0差不多,高4为代表了TA(2)TB(2) TC(2) TD(2)是否存在,低4为代表了这张卡支出什么协议,参数T的定义如下:

——T=0        异步半双工字符传输协议 在第8章中说明。

——T=1        异步半双工块传输协议 在第9章中说明。

——T=2和T=3    保留用于将来的全双工操作。

——T=4        保留用于增强的异步半双工字符传输协议。

——T=5到T=13   保留待未来使用。

——T=14       未由ISO/IEC JTC1 SC17标准化的传输协议

——T=15       不属于传输协议,仅指明了全程接口字节的类型(见8.4.3.2)

TD2、TD3的高四位定义和TD1差不多,类推一下就好了,低4位目前我没用过,反正我判断是什么协议的卡只判断TD1的低4位,TD2、TD3的低4位我直接忽略了。如果TD1不存在,则代表了该卡支持的是T=0的卡。

TA2、TB2、TC2我也没有用到,所以没有仔细研究过,如果想要了解就需要看协议的相关文档。

TA3 也是比较重要的一个值,他确定了IFSC的大小,IFSC后面会介绍,这里只要记住有这个东西,并且比较重要就好了。

TB3的值和CWT和BWT,这两个值关系到了数据传输的时间,具体看协议,我也没有了解过。

TC3的值也比较重要,他决定T=1的检验位采用什么方式,第1位是0,则采用LRC校验,第1位是1,则采用CRC校验。如果没有TC3,则代表采用LRC校验。

接下来的数据是历史字符,具体代表什么意思需要看卡片。我也没研究过。

还有*后一个数据,Tck—校验字符

TCK具有一个检验复位应答期间所发送数据完整性的值。TCK的值应使从T0到包括TCK在内的所有字

由于T=0的卡没有校验,所以没有该值,在T=1的卡中,会正常返回该值。

 

下面我给出一组T=1卡的复位数据,进行分析。

序号

分析

1

0x3b

TS,正向约定

2

0xff

T0,有TA1、TB1、TC1、TD1,历史数据为15个

3

0x18

TA1控制了传输的速率,详细看下面的介绍

4

0x00

TB1 我也不知道具体什么意思

5

0xff

TC1 我也不知道具体什么意思

6

0x81

TD1,有TD2,卡支持T=1协议

7

0x31

TD2,有TA3和TB3,后四位好像不是代表支持的协议

8

0xfe

TA3,代表IFSC的值为254

9

0x45

TB3 我也不知道具体什么意思

10

0x65

T1

11

0x63

T2

12

0x0d

T3

13

0xoc

T4

14

0x76

T5

15

0x01

T6

16

0x56

T7

17

0x00

T8

18

0x0d

T9

19

0x92

T10

20

0x94

T11

21

0x03

T12

22

0x00

T13

23

0x07

T14

24

0x30

T15

25

0x0a

Tck,*后的校验位

 

二、数据传输

1、 传输速度

卡的传输速度除了有卡本身的决定外,还由CLK的时钟决定。在ISO7816协议中规定,CLK的时钟必须1Mhz-5Mhz,CLK的时钟是由读卡器决定的,而DATA的位持续时间是由CLK和卡本身决定的,所以说,卡的传输速度是由读卡器和卡共同决定。这里有一个叫做etu的东西,我把他理解成为每一位的持续时间。在复位期间,etu的计算如下:

etu =372 / f

其中F即为CLK的时钟,假如CLK的时钟为3.6M,则

etu =372 / 3600000 = 103us

那么,在CLK为3.6M的情况下,波特率为

1 / (372 / 3600000)= 9677

“STM32 ISO7816 智能卡”的DEMO中,其CLK的时钟输出就是3.6M,所以其波特率为9677。而我在调试过程中,将STM32的主频修改为8M,那么,CLK无论怎么分频都无法输出3.6M,所以我只能将CLK设置为2M,那么相应的波特率也改为了5376。

而复位的信息中包含了时钟率转换因子(FI)和波特率矫正因子(DI),那么etu的计算就变成了:

etu =FI / (DI * f)

FI和DI由TA决定,一般情况下,FI=372,DI=1,所以,一般情况下,复位完成后无需重新设置波特率,如果有遇到特殊的卡,在复位完成之后还需要对波特率进行重新设置。

2、 传输协议

卡支持不同的协议,传输方式也不同,目前*常见的就是T=0的卡和T=1的卡,他们的传输都是通过APDU格式进行传输,协议的内容我就不介绍了。

其中T=0叫字符传输,T=1叫块传输。说白了就是T=0的卡没有校验,你不知道还剩下多少个数据。也不知道传输过来的数据对不对。T=1的卡就不一样了,分为头域、信息域、尾域。其中,头域代表了传输的地址、命令以及信息域的长度,信息域代表的意思和头域有关,如果发送的是I块,那么信息域就是APUD数据,如果发送的S块,那么信息域代表了控制信息,R块的信息域为0,我们上面说的IFSC指的就是信息域的大小,刚刚那组数据中,IFSC的值为254,那么代表传输过程中,信息域*大的长度就是254。尾域的作用就是校验接收的数据是否正确。

T=0的卡百度也有很多现成的代码,直接搜“STM32 ISO7816 智能卡 代码”应该就能找得到。可以做一些参考。T=1的卡百度代码较少。可以在美信的官网上找一下智能卡相关的内容,应该可以找得到源码,或者在下面的连接下载也可以。

 

https://www.maximintegrated.com/cn/design/tools/appnotes/4200/an4200_sw.zip

 

这个代码支持T=0和T=1的卡,可以用作参考。

 

另外需要注意的就是,T=1的卡复位完成之后,必须先发一个S块,应该是规定好的。

 

 

 

下面给出一组T=0的卡传输过程中的数据分析。

序号

收发

分析

1

0x00

CLA 指令类别 根据卡的文档决定

2

0x84

INS 指令类别 0x84代表获取随机数

3

0x00

P1

4

0x00

P2

5

0x04

LE(没有LC,则没有DATA),希望得到4个数据

6

0x84

INS,卡回应INS,则代表相应了这个命令

7

0x86

DATA1

8

0x91

DATA2

9

0xd3

DATA3

10

0x48

DATA4

11

0x90

SW1 和SW2一起表示正常结束

12

0x00

 

 

这支持一个简单的测试卡是否可以正常通信的命令。该支持的意思是获取4个随机数。其实每个代表什么意思,还需要看卡的文档。

 

下面给出一组T=1的卡传输过程中的数据分析,举一个I块的例子。

序号

收发

分析

1

0x00

NAD 节点地址,一般都是0

2

0x00

PCB 协议控制字节 b0=1代表是I块,b7=0代表是第1组数据,b6=0代表是*后一个块,剩下的字节保留

3

0x05

LEN 长度,代表信息域的长度为5

4

0x00

信息域,其实内容和T=0一样的

CLA 指令类别 根据卡的文档决定

5

0x84

INS 指令类别 0x84代表获取随机数

6

0x00

P1

7

0x00

P2

8

0x04

LE(没有LC,则没有DATA),希望得到4个数据

9

0x85

EDC 校验位,前面所有数据的异或值

10

0x00

NAD 节点地址,和发送的一样

11

0x00

PCB 协议控制字节 b0=1代表是I块,b7=0代表是第1组数据,b6=0代表是*后一个块,剩下的字节保留

12

0x20

LEN 长度,代表信息域的长度为2

13

0x67

SW1 0x67代表长度错误

14

0x00

 

15

0x1e

EDC 校验位,前面所有数据的异或值

 

我特意选了一组带有错误值,发送的数据和T=0的卡一样,是一个希望得到4个随机数的指令,而返回的只有SW1和SW2,没有DATA的值,SW1为0X67代表数据的长度错误,后来才发现,我手里的这张T=1的卡只支持获取8个随机数的指令,所以,把LE改成8之后,接收的数据就正确了。

c语言代码 linux 关机_Linux关机和重启命令

说到关机和重启,很多人认为,重要的服务器(比如银行的服务器、电信的服务器)如果重启了,则会造成大范围的灾难。笔者在这里解释一下。

首先,就算是银行或电信的服务器,也不是不需要维护,而是依靠备份服务器代替。其次,每个人的经验都是和自己的技术成长环境息息相关的。比如笔者是游戏运维出身,而游戏又是数据为王,所以一切操作的目的就是保证数据的可靠和安全。这时,有计划的重启远比意外岩机造成的损失要小得多,所以定义重启是游戏运维的重要手段。

shutdown命令

在早期的 Linux 系统中,应该尽量使用 shutdown 命令来进行关机和重启。因为在那时的 Linux 中,只有 shutdown 命令在关机或重启之前会正确地中止进程及服务,所以我们一直认为 shutdown 才是*安全的关机与重启命令。

而在现在的系统中,一些其他的命令(如 reboot)也会正确地中止进程及服务,但我们仍建议使用 shutdown 命令来进行关机和重启。

shutdown 命令的基本信息如下。

命令名称:shutdown。

英文原意:bring the system down。

所在路径:/sbin/shutdown。

执行权限:超级用户。

功能描述:关机和重启

命令格式

[root@localhost ~]# shutdown [选项] 时间 [警告信息]

选项:

-c:取消已经执行的 shutdown 命令;

-h:关机;

-r:重启;

【例 1】重启与定时重启。

先来看看如何使用 shutdown 命令进行重启:

[root@localhost ~]# shutdown -r now

#重启, now是现在重启的意思

[root@localhost ~]# shutdown -r 05:30

#指定时间重启,但会占用前台终端

[root@localhost ~]# shutdown -r 05:30 &

#把定义重启命令放入后台,&是后台的意思

[root@localhost ~]# shutdown -c

//取消定时重启

[root@localhost ~]# shutdown -r +10

#10分钟之后重启

【例 2】关机和定时关机。

[root@localhost ~]# shutdown -h now

#现在关机

[root@localhost ~]# shutdown -h 05:30

#指定时间关机

reboot命令

在现在的系统中,reboot 命令也是安全的,而且不需要加入过多的选项。

[root@localhost ~]# reboot

#重启

halt和poweroff命令

这两个都是关机命令,直接执行即可。

[root@localhost ~】# halt

#关机

[root@localhost ~】# poweroff

#关机

init命令

init 是修改 Linux 运行级别的命令,也可以用于关机和重启。

[root@localhost~]# init 0

#关机,也就是调用系统的 0 级别

[root@localhost ~】# init 6

#重启,也就是调用系统的 6 级别

一行代码搞定 Python 日志!

一行代码搞定 Python 日志!

 

写了这么多年的 Python ,我一直都是使用 Python 自带的 logging 模块来记录日志,每次需要写一些配置将日志输出到不同的位置,设置不同日志输出格式,或者将日志进行分文件和压缩等。这个日志模块没什么问题,直到我无意中发现了一个神器,我才发觉原来记日志可以这么简单的!这个神器就是 loguru 。

安装
这个库的安装方式很简单,直接使用 pip 就可以,我使用 Python 3 版本,安装命令如下:

pip3 install loguru
小试牛刀
安装完毕之后,我们就可以使用了,*简单的使用方式:

from loguru import logger

logger.debug(‘this is a debug message’)

无需任何配置,即取即用。上例是打印一条 debug 级别的日志,输出结果如下:

2021-03-16 22:17:23.640 | DEBUG    | __main__:<module>:8 – this is a debug message

这条输出日志信息包含了日期、时间、日志级别、日志代码行数以及日志内容信息。可以说*基本的内容都囊括了,当然你还可以打印 warning、info、error、critical、success 等级别。输出的日志在 console 中还带有高亮颜色,并且每个级别的日志颜色不一样,简直不要太酷!

日志文件
写文件
在loguru中,输出日志文件只需要一个 add() 函数即可:

logger.add(‘hello.log’)

logger.debug(‘i am in log file’)

这时候,在 console 中会正常打印日志信息,在同级目录下会生成一个日志文件 hello.log ,我们打开日志文件,可以看到内容如下:

2021-03-16 21:20:31.460 | DEBUG    | __main__:<module>:12 – i am in log file

当然,我们还可以加一些参数,来指定文件中日志输出的格式、级别:

log = logger.add(‘world.log’, format=”{time} | {level} | {message}”, level=”INFO”)

logger.debug(‘i am debug message’)
logger.info(‘i am info message’)

对应的文件输出信息如下:

2021-03-16T22:47:53.226998+0800 | INFO | i am info message

我们设置了文件只记录 info 级别的信息,所以 debug 级别的日志信息并没有写入日志文件。

我们也可以给日志文件名称加信息:

logger.add(‘hello_{time}.log’)

上面的代码运行后,会生成一个带时间的日志文件。

停止写入文件
当我们不再需要将日志写入文件时,我们随时可以停止:

id = logger.add(‘world.log’, format=”{time} | {level} | {message}”, level=”INFO”)
logger.info(‘this is a info message’)
logger.remove(id)
logger.info(‘this is another info message’)

add() 方法会返回一个日志文件的 id ,当我们需要停止写入信息时,我们使用 remove() 方法,传入 id ,即可。上面代码运行后,日志文件记录的信息如下:

2021-03-16T22:47:53.227389+0800 | INFO | this is a info message

在调用 remove() 方法后,其后面的日志信息并没有写入日志文件中。

滚动记录日志文件
我们可以配置 rotation 参数,来指定日志文件的生成方式,跟通常的日志记录一样,我们可以设置按照文件大小、时间、日期等来指定生成策略。

# 超过200M就新生成一个文件
logger.add(“size.log”, rotation=”200 MB”)
# 每天中午12点生成一个新文件
logger.add(“time.log”, rotation=”12:00″)
# 一周生成一个新文件
logger.add(“size.log”, rotation=”1 week”)

指定日志文件的有效期
我们还可以通过 retention 参数来指定日志文件的保留时长:

logger.add(“file.log”, retention=”30 days”)

通过上面的配置,可以指定日志文件*多保留30天,30天之前的日志文件就会被清理掉。

配置压缩文件
为了节省空间,我们可能存在压缩日志文件的需求,这个 loguru 也可以实现:

logger.add(“file.log”, compression=”zip”)

通过上面的配置,我们指定了日志文件的压缩格式为 zip 。

异常捕获
loguru 不仅可以记录日志,还可以捕获异常信息,这个 可以帮助我们更好地追溯错误原因。

在 loguru 模块中,我们通常有两种异常捕获方式:通过 catch 装饰器捕获和通过 exception 方法捕获。

catch 装饰器捕获异常
我们来看一个例子:

@logger.catch
def a_function(x):
return 1 / x

a_function(0)

输出信息如下:

021-03-16 23:10:28.124 | ERROR    | __main__:<module>:32 – An error has been caught in function ‘<module>’, process ‘MainProcess’ (25939), thread ‘MainThread’ (140735895298944):
Traceback (most recent call last):
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 483, in <module>
pydevconsole.start_client(host, port)
│            │            │     └ 62146
│            │            └ ‘127.0.0.1’
│            └ <function start_client at 0x10fd596a8>
└ <module ‘pydevconsole’ from ‘/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/py…

……

> File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 32, in <module>
a_function(0)
└ <function a_function at 0x11021e620>
File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 30, in a_function
return 1 / x
└ 0
ZeroDivisionError: division by zero
上面的代码中,我特意造了一个 1 除以 0 的异常,我们可以看到日志输出信息非常详细,将每一步调用的错误信息都详细的列出来,并且还把参数的值也打印出来了,还有非常直观的指向性,简直是异常分析神器!

exception 方法捕获异常
我们直接看例子:

def b_function1(x):
try:
return 1 / x
except ZeroDivisionError:
logger.exception(“exception!!!”)

b_function1(0)

运行上面代码,输出信息如下:

2021-03-16 23:16:07.602 | ERROR    | __main__:b_function1:40 – exception!!!
Traceback (most recent call last):
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 483, in <module>
pydevconsole.start_client(host, port)
│            │            │     └ 62254
│            │            └ ‘127.0.0.1’
│            └ <function start_client at 0x118d216a8>
└ <module ‘pydevconsole’ from ‘/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/py…
File “/Users/cxhuan/Library/Application Support/JetBrains/IntelliJIdea2020.3/plugins/python/helpers/pydev/pydevconsole.py”, line 411, in start_client
process_exec_queue(interpreter)
│                  └ <_pydev_bundle.pydev_ipython_console.InterpreterInterface object at 0x118d36240>
└ <function process_exec_queue at
0x118d21400>

……

File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 42, in <module>
b_function1(0)
└ <function b_function1 at 0x11913b598>
> File “/Users/cxhuan/Documents/python_workspace/mypy/loguru/logurustudy.py”, line 38, in b_function1
return 1 / x
└ 0
ZeroDivisionError: division by zero
同样地,也是很详细和直观地打印了错误详情。

Python 高手都是这样处理数据的!

Python 高手都是这样处理数据的!

 

工欲善其事,必先利其器!我们想要更轻松更有效率地开发,必须学会一些“高级”技能。前不久看到一位 Python 高僧的代码,其中使用了一个短小精悍的模块,我认为还蛮有用的,今天分享给大家。

这个模块就叫 glom ,是 Python 处理数据的一个小模块,它具有如下特点:

嵌套结构并基于路径访问

使用轻量级的Pythonic规范进行声明性数据转换

可读、有意义的错误信息

内置数据探测和调试功能

看起来比较抽象,对不对?下面我们用实例来给大家演示一下。

安装
作为 Python 内置模块,相信你一定知道怎么安装:

pip3 install glom

几秒钟就搞定!

简单使用
我们来看看*简单的用法:

d = {“a”: {“b”: {“c”: 1}}}
print(glom(d, “a.b.c”)) # 1

在这里,我们有一个嵌套三层的 json 结构,我们想获取*里层的 c 对应的值,正常的写法应该是:

print(d[“a”][“b”][“c”])

如果到这里,我说 glom 比传统方式好一些,因为你不用一层层地写中括号和引号,你会不会嗤之以鼻?

好,我们再来看看下面的情况:

d = {“a”: {“b”: None}}
print(d[“a”][“b”][“c”])

遍历到一个 None 对象,你会收到下面的错误:

Traceback (most recent call last):
File “/Users/cxhuan/Documents/python_workspace/mypy/pmodules/pglom/glomstudy.py”, line 10, in <module>
print(d[“a”][“b”][“c”])
TypeError: ‘NoneType’ object is not subscriptable

我们来看看 glom 的处理方式:

from glom import glom

d = {“a”: {“b”: None}}
print(glom(d, “a.b.c”))

同样地,glom 不能把错误的输出成对的,你会得到以下错误:

Traceback (most recent call last):
File “/Users/cxhuan/Documents/python_workspace/mypy/pmodules/pglom/glomstudy.py”, line 11, in <module>
print(glom(d, “a.b.c”))
File “/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/glom/core.py”, line 2181, in glom
raise err
glom.core.PathAccessError: error raised while processing, details below.
Target-spec trace (most recent last):
– Target: {‘a’: {‘b’: None}}
– Spec: ‘a.b.c’
glom.core.PathAccessError: could not access ‘c’, part 2 of Path(‘a’, ‘b’, ‘c’), got error: AttributeError(“‘NoneType’ object has no attribute ‘c'”)

如果你仔细看报错内容,你就会发现这报错内容*其详细,一目了然,这对于找程序 bug 简直是神器!

复杂用法
刚才简单的例子,让大家对 glom 有了直观的认识,接下来我们看看 glom 的 glom 方法的定义:

glom(target, spec, **kwargs)

我们看看参数的含义:

target:目标数据,可以是dict、list或者其他任何对象

spec:是我们希望输出的内容

下面我们来使用这个方法。

先看一个例子。我们有一个 dict ,想要获取出 所有 name 的值,我们可以通过 glom 来实现:

data = {“student”: {“info”: [{“name”: “张三”}, {“name”: “李四”}]}}
info = glom(data, (“student.info”, [“name”]))
print(info) # [‘张三’, ‘李四’]

如果用传统方式的话,我们可能会需要遍历才能获取到,但是使用 glom ,我们只需要一行代码就可以了,输出是一个数组。

如果你不想输出数组,而是想要一个 dict 的话,那也是很简单的:

info = glom(data, {“info”: (“student.info”, [“name”])})
print(info) # {‘info’: [‘张三’, ‘李四’]

我们只需要将原来的数组赋值给一个字典来接收就好了。

搞定麻烦需求
假如我现在有两组数据,我要取出 name 的值:

data_1 = {“school”: {“student”: [{“name”: “张三”}, {“name”: “李四”}]}}
data_2 = {“school”: {“teacher”: [{“name”: “王老师”}, {“name”: “赵老师”}]}}

spec_1 = {“name”: (“school.student”, [“name”])}
spec_2 = {“name”: (“school.teacher”, [“name”])}
print(glom(data_1, spec_1)) # {‘name’: [‘张三’, ‘李四’]}
print(glom(data_2, spec_2)) # {‘name’: [‘王老师’, ‘赵老师’]}

我们通常是这么写,对吗?假如我们有好多组数据,每组都是类似的取法呢?这时候我们就会想办法避免一个个重复写 N 行参数了,我们可以使用 Coalesce 方法:

data_1 = {“school”: {“student”: [{“name”: “张三”}, {“name”: “李四”}]}}
data_2 = {“school”: {“teacher”: [{“name”: “王老师”}, {“name”: “赵老师”}]}}

spec = {“name”: (Coalesce(“school.student”, “school.teacher”), [“name”])}

print(glom(data_1, spec)) # {‘name’: [‘张三’, ‘李四’]}
print(glom(data_2, spec)) # {‘name’: [‘王老师’, ‘赵老师’]}

我们可以用 Coalesce 把多个需求聚合起来,然后针对同一个 spec 来取值就行了。

下面再来一个大杀器——取值计算。glom 还可以对取值进行简单计算,我们来看例子:

data = {“school”: {“student”: [{“name”: “张三”, “age”: 8}, {“name”: “李四”, “age”: 10}]}}
spec = {“sum_age”: (“school.student”, [“age”], sum)}
print(glom(data, spec)) # {‘sum_age’: 18}

总结
介绍了这么多,大家应该知道 glom 的厉害之处了吧,据说很多大佬都喜欢使用呢。其实它还有很多其他的实用功能有待大家去发掘,这里就不一一介绍了。如果你觉得今天分享的模块有用,点个“在看”支持一下吧!

机房停电服务器自动关机脚本(Windows和Linux)

作为一名机房运维人员,*烦躁的事,莫过于停电时,要关闭服务器。
因为机房的UPS的容量有限,不可能长时间供电,而且停电有可能发生在休息时间,夜里或你出去游玩的时候,你根本没办法远程登录进行关机。
就算可以登录,但服务器的数量众多,有windows的,有linux的,每一台都要输入用户名密码再关机,操作下来也是很繁琐。
在没有系统的管理软件执行自动关机的情况下,可以通过将以下脚本配置为计划任务,让其每隔一定时间去检测某个IP地址是否能ping通,再决定是否关机。
几行代码,即可减轻运维人员的压力,让你安心睡大觉,开开心心的出去玩。

windows服务器的停电自动关机检测脚本:

@echo off
for /f %%n in (‘ping 192.168.1.10 ^| find /c “TTL”‘) do set p=%%n
if %p% EQU 0 (
echo 自动关机条件已经触发,系统将在1分钟后自动关机
shutdown /s
)

Linux服务器的停电自动关机检测脚本:

#! /bin/bash
p=`ping -c 5 192.168.1.10 | grep -c “ttl”`
if [ $p = 0 ]; then
echo “自动关机条件已经触发,系统将在1分钟后自动关机”
shutdown -h +1
fi

这样写Python代码四两拨千斤!

实用炫酷!这样写Python代码四两拨千斤!

 

 

判断一个程序员水平的高低,不能光看他的发量,也不能光看他的代码量,还要看他代码蕴含的思想,代码的质量。代码蕴含的思想主要体现在各种设计模式的运用上,而代码的质量就既要实现需求,又要保证代码的简洁优雅。保证代码质量是需要长期积累,养成良好的编程习惯,不断思考优化的。

今天就给大家介绍一种实用的保证代码简洁的武功—— Python 推导四式。

什么是推导式
推导式 comprehensions(又称解析式),是 python 的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列。

看定义很抽象,下面我们来看看具体的例子,通过例子来学习这四种推导式。

列表推导式
我们先来看一个需求:

快速创建一个包含元素1-9的平方的列表

面对这个需求,我们通常的实现方式是这样的:

lis = []
for i in range(1, 10):
lis.append(i*i)
print(lis) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
用列表推导式怎么实现呢?你只需要一行代码:

lis = [x * x for x in range(1, 10)]
print(list) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
这就是推导式。我们可以看到列推导式的语法是这样的:

变量名 = [表达式 for 变量 in 列表]

再来一个复杂点的:

list = [x * y for x in range(1, 10) for y in range(1, 10)]
print(lis)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 4, 6, 8, 10, 12, 14, 16, 18, 3, 6, 9, 12, 15, 18, 21, 24, 27, 4, 8, 12, 16, 20, 24, 28, 32, 36, 5, 10, 15, 20, 25, 30, 35, 40, 45, 6, 12, 18, 24, 30, 36, 42, 48, 54, 7, 14, 21, 28, 35, 42, 49, 56, 63, 8, 16, 24, 32, 40, 48, 56, 64, 72, 9, 18, 27, 36, 45, 54, 63, 72, 81]
这个是多个变量的类型,因此我们可以扩展上面的语法:

变量名 = [表达式 for 变量 in 列表 for 变量 in  xxx]

当然,还有一种带条件的列推导式,它的语法是这样的:

变量名 = [表达式 for 变量 in 列表 if 条件]

我们来看一个需求:快速创建一个包含1-10之间所有偶数的列表。

lis = [i for i in range(1, 11) if i % 2 == 0]
print(lis) # [2, 4, 6, 8, 10]
字典推导式
字典推导式的形式和列表推导式类似,语法也是类似的,只不过字典推导式返回的结果是字典。

变量名 = {key: value表达式}

我们来看一个例子:

dic = {x: x/2 for x in range(1,11) if x % 2 == 0}
print(dic) # {2: 1.0, 4: 2.0, 6: 3.0, 8: 4.0, 10: 5.0}
如果这里我写成下面这种方式,返回的结果是什么呢?

dic = {‘half’: x/2 for x in range(1,11) if x % 2 == 0}
print(dic)
大家可以自己尝试运行一下,看跟你想的结果是不是一样的。

集合推导式
看了前面两个推导式,想必你已经知道集合推导式怎么写了。语法如下:

变量名 = {表达式 for 变量 in 列表 for 变量 in  xxx}

或者

变量名 = {表达式 for 变量 in 列表 if 条件}

来一个实际的例子:创建一个存储10个偶数的集合。

set1 = {x for x in range(10) if x % 2 == 0}
print(set1) # {0, 2, 4, 6, 8}
元组推导式
我们先来看一个例子:

tup=(x for x in range(1,10))
print(tup) # <generator object <genexpr> at 0x1101fade0>
看到这个结果打印是不是有点懵?

上面的代码返回的变量其实是一个生成器,并不是一个元组。其实没有真正的元组推导式,我们只能用一个类似的方法来生成元组,暂且叫做“伪元组推导式”吧。

上面的例子我们来改进一下:

tup=tuple(x for x in range(1,10))
print(tup) # (1, 2, 3, 4, 5, 6, 7, 8, 9)
在前面加上 tuple 来显式进行类型转换就行了。

总结
这里介绍了四种 python 的推导式,主要是用来简化 循环的代码,生成不同的数据结构用的。当然,从这些基础的表达式语法出发,还可以运用到复杂的推导式,大家在写代码时留心使用就会发现它的强大之处。这种一行代码既简洁明了,又可以在新手面前炫技,用起来不要太爽!

如何正确关机和重启服务器?

如果要关机,必须保证当前系统中没有其他用户在线。可以通过who命令查看。

正确的关机流程为:sync——>shutdown——>reboot——>halt

sync命令[sɪŋk]可以将数据由内存同步到硬盘中。

#shutdown -h 10  服务器将在10分钟后关机,且会显示在登陆用户的当前屏幕中。

在10分钟之内系统管理员可以输入 shutdown -c 来取消这次的关机指令

#shutdown -h now  立即关机

#shutdown -h 20:25  系统会在20:25关机

#shutdown -r now  立即重启

#shutdown -r +10  十分钟后重启

#reboot 重启