Android 几种消息推送方案总结

首先看一张国内Top500 Android应用中它们用到的第三方推送以及所占数量:

现在总结下Android平台下几种推送方案的基本情况以及优缺点:

一、使用GCM(Google Cloude Messaging)

Android自带的推送GCM可以帮助开发人员给他们的Android应用程序发送数据。它是一个轻量级的消息,告诉Android应用程序有新的数据要从服务器获取,或者它可能是一个消息,其中包含了4KB的payload data(像即时通讯这类应用程序可以直接使用该payload消息)。GCM服务处理排队的消息,并把消息传递到目标设备上运行的Android应用程序。

优点:Google提供的服务、原生、简单,无需实现和部署服务端。

缺点:1.要求Android 2.2以上,对于不少2.2以前的系统没法推送;

2.国内服务不稳定。而且不少国内的终端厂商纷纷把Google的服务去掉,替换上自己的。

3.需要用户绑定Google账号,但不少国内用户没有Google账号。

二、使用XMPP协议(Openfire+Spark+Smark)

XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性,有很强的可扩展性。包括上面讲的GCM服务器底层也是采用XMPP协议封装的。

优点:协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中,且已有开源的Java版的开发实例androidpn。

缺点:协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本高。

而androidpn(Android Push Notification)就是基于 XMPP 开源组件的一套整合方案,服务端基于Openfire、客户端基于Smack。到AndroidPN项目主页( http://sourceforge.net/projects/androidpn/ ) 下载2个文件: androidpn-server-0.5.0-bin.zip 和 androidpn-client-0.5.0.zip 分别是服务器和客户端的代码。详细的实现方式网上有不少文章。

1.androidpn服务端重启后客户端不会重连,这个非常悲剧

2.由于服务器不保存消息,造成了如果客户端当前离线就收不到消息

3.androidpn发送完消息就不管了,所以没有消息回执报表之类,造成没法做应用后续的数据分析用户体验的改善,这对于企业级的应用是个致命伤。

XMPP协议比较费电费流量,这个对当前智能机的消耗太大,在窄带网络和不稳定的(手机)网络都不是最优的选择。但总体来说,XMPP协议还是比较成熟的。

三、使用MQTT协议(想了解更多可以看http://mqtt.org/)

轻量级的、基于代理的“发布/订阅”模式的消息传输协议。

优点:协议简洁、小巧、可扩展性强、省流量、省电,目前已经应用到企业领域(参考: http://mqtt.org/software),且已有C++版的服务端组件rsmb。

缺点:不够成熟、实现较复杂、服务端组件rsmb不开源,部署硬件成本较高。

四、HTTP轮循方式

定时向HTTP服务端接口(Web Service API)获取最新消息。

优点:实现简单、可控性强,部署硬件成本低。

缺点:实时性差。

五、采用第三方服务

就是前面介绍的第三方推送,客户端只需要导入第三方提供的lib库,有第三方管理长连接,负责消息的接收/发送。同时对消息都有比较详细的报表数据,可以用于做数据分析、挖掘,改善用户体验。

Python搭建APNS苹果推送通知推送服务的相关模块使用指南

这里总结了一份Python搭建苹果推送通知推送服务的相关模块使用指南,包括PyAPNs、基于twisted框架的pyapns以及apns-client三个模块的介绍,需要的朋友可以参考下.

APNS 是苹果为IOS设备提供的推送服务,全称是(Apple Push Notification service)。 如果你有接触移动互联网相关的开发的话,应该对它很熟悉。

接下来我会给大家简单介绍一下Python下的一些APNS相关的模块以及其特点。

模块介绍:

PyAPNs

项目地址: https://github.com/djacobs/PyAPNs
PyAPNs是我最早使用的APNS模块,它应该是我要介绍的所有模块里面最简单的,最新的源码 只有384行,实现了APNS的基本功能,包括发送推送、使用Frame群发推送、feedback 接口等。

它的所有验证都是在客户端做的,比如每一个Payload不超过256字节。
简单来说,就是尽量复用你的链接,不要频繁的建立和断开,不然会被当做DoS攻击处理。所以 我们使用它来发送推送时应该这么干:

1
2
3
4
... ...
# 复用这个gateway_server
apns.gateway_server.send_notification(token_hex, payload)

复用这个gateway_server也就是连接,但是到APNS Server的链接是很不稳定的,很多情况下 都会被断开,比如网络原因、发送了非法的token等。所以我们还需要一个重连的机制。

但PyAPNs模块没有为你处理这些,所以你需要自己去处理那些出错的情况,这也是使用 这个模块最不方便的地方。

所以我的建议是,除非你自己需要去写一个APNS的Provider,那你可以以这个模块作为起点。 否则,如果你想在你的项目里面快速用上推送服务的话,建议还是选择别的模块。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time
from apns import APNs, Frame, Payload
 
apns = APNs(use_sandbox=True, cert_file='cert.pem', key_file='key.pem')
 
# Send a notification
token_hex = 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b87'
payload = Payload(alert="Hello World!", sound="default", badge=1)
apns.gateway_server.send_notification(token_hex, payload)
 
# Send multiple notifications in a single transmission
frame = Frame()
identifier = 1
expiry = time.time()+3600
priority = 10
frame.add_item('b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b87', payload, identifier, expiry, priority)
apns.gateway_server.send_notification_multiple(frame)
 
# Get feedback messages
for (token_hex, fail_time) in apns.feedback_server.items():
# do stuff with token_hex and fail_time
对于更复杂的alerts,比如自定义按钮,可以使用PayloadAlert类
alert = PayloadAlert("Hello world!", action_loc_key="Click me")
payload = Payload(alert=alert, sound="default")

pyapns(twisted)

项目地址: https://github.com/samuraisam/pyapns
他们使用的就是这个项目作为他们的推送服务的provider,所以我之后把推送从PyAPNs迁移到了这个项目, 使用下来其实还是挺不错的,这个项目的主要特点是:

它其实是一个基于twisted的server,所有发送推送的请求都通过它来和苹果的服务器交互。
对Django和Pylons有原生支持。
支持多个APP。
因为和苹果的推送服务器是由这个provider维持的长连接,所以你每次发送推送的时候都直接 这个provier进行叫交互,这样的的好处是每一次的接口调用返回都很快,真正推送到苹果服务器的过程 则是由这个provider异步来完成。

但是这个模块很长时间都没有维护了,其实Apple那边的协议在这段时间里已经进行了一些更新。 但这个模块没有跟上。

我使用这个模块碰到的最大的问题就是 群发推送的效果得不到保证。

虽然这个模块的demo里面有对批量发送推送进行支持,但是我的使用经验是,这个模块的群发 推送效果比较差,而且缺少从苹果Server拿到错误反馈的逻辑。

因为Twisted的代码风格实在不怎么喜欢,所以我群发碰到问题后开始寻找别的解决方案。

apns-client

项目地址: https://bitbucket.org/sardarnl/apns-client/
总结一下就是:

维持持久链接。SSL协议的握手环节是很慢的。当每一个连接被建立之后,它应该一直保持最少几分钟来等待 下一次的推送。
支持改进过的的协议格式。Apple的程序员们设计了一个臭名昭著的推送协议。他们更新了一个版本,这个版本可以让你知道 每一次群发推送里面到底是哪一个单独的消息出了问题。
清晰的Python API
没有把验证这块写进代码里,而是直接返回APNS的错误信息
使用这个模块来发送推送也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from apnsclient import *
# 可以使用Session对象来维持连接池
session = Session()
con = session.get_connection("push_sandbox", cert_file="sandbox.pem")
# 发送推送和得到反馈
messge = Message(["my", "device", "tokens"], alert="My message", badge=10)
# Send the message.
srv = APNs(con)
res = srv.send(message)
# Check failures. Check codes in APNs reference docs.
for token, reason in res.failed.items():
  code, errmsg = reason
  print "Device faled: {0}, reason: {1}".format(token, errmsg)
# Check failures not related to devices.
for code, errmsg in res.errors:
  print "Error: ", errmsg

对于我来说,这个模块最大的优点就是为你处理了连接有可能被异常断开重连的情况。而且代码不像 pyapns这样晦涩,更直观,可读性更高。所以你如果要在它的基础上做一些修改也没有任何问题。

经过我的使用经验,使用apns-client来处理百万级别这种量级的推送没有任何问题,到达率也很好。

所以如果你没有特殊的需求的话,apns-client应该是你最好的选择。

关于RGB & 灰度 & CMYK & HSB 模式的一些小知识

1.关于RGB

电视屏幕上的颜色是由红色、绿色、蓝色三种色光按照不同比例混合而成的颜色。

用英文表示 R:(red) G:(green) B:(blue)

RGB模式是显示器的物理色彩模式,无论在软件中使用何种色彩模式,只要在显示器上显示的,图像最终以RGB方式出现。


2.灰度模式与RGB模式

在关闭色彩管理功能下成立:编辑-颜色设置-设置(改为自定) 快捷键:ctl+shift+k

以上图片显示在灰度模式下K值为100,在RGB模式下值为0,所以这两种模式是黑白颠倒的。

18%的灰=?RGB       在RGB模式中三原色光各有256个级别

256×(1-18%)=209.92      所以18%的灰=R:210   G:210  B:210


3.CMYK色彩模式

只要在屏幕上显示的就是RGB模式表现,只要在印刷品上看到的图像就是CMYK模式。CMYK是3种印刷油墨的首字母:青色cyan、洋红色magenta、黄色yellow。而K为black的最后一个字母,之所以不取首字母是因为避免与blue混淆。

从理论上来讲,只需要CMY三种油墨就可以得到黑色。但因为制造工艺还不能造出高纯度的油墨,CMY相加实际是暗红色,所以需要黑墨。


4.HSB色彩模式

色相(H)  饱和度(S)  明度(B)

Docker容器的跨主机连接

使用网桥实现跨主机容器连接

1.网桥方式需要安装网桥管理工具:

apt-get install bridge-utils

2.修改主机的/etc/network/interfaces文件

  1. auto lo
  2. iface lo inet loopback
  3. auto br0
  4. iface br0 inet static
  5. address 10.211.55.3 #这里具体需要填写你自己主机的IP地址
  6. netmask 255.255.255.0
  7. gateway 10.211.55.1 #需要你填写具体的网管
  8. bridge_ports eth0

3.修改/etc/default/docker文件,添加如下内容

DOCKER_OPTS="-b=br0 --fixed-cidr='10.211.55.64/26'"

解释一下:

-b:指定使用自定义网桥,-b=br0

–fixed-cidr:限制ip地址的分配范围

IP地址划分为:

Host1:10.211.55.64/26

地址范围:10.211.55.65~10.211.55.126

Host2:10.211.55.128/26

地址范围:10.211.55.129~10.211.55.190

怎么算的?

http://help.bitscn.com/ip/

设置完毕以后使用ifconfig,ps -fe | grep docker来查看一下.在其他的主机上也做同样的地址配置就可以是选不同物理主机之间的docker容器互联了.

楼主鼓捣了半天,没配置好还是算了吧,因为这种方式实现跨主机容器连接的方式缺点很多一般不使用.

所以咱们直接学第二种方式来实现跨主机的容器连接.

使用Open vSwitch(ovs)实现跨主机的容器连接.

先看官方的定义:它是一个高质量的,多层的虚拟交换机,使用开源Apache2.0许可协议,主要实现代码是可移植的C代码.目的是让大规模网络自动化可以通过编程扩展,同时仍然支持标准的管理接口和协议.

再来看民间的:一个虚拟交换机.交换机有啥作用,OVS就啥作用.

先看一下实现这种方式的连接所具备的条件:

1.双网卡,Host-Only & NAT

2.安装Open vSwitch:apt-get install openvswitch-switch

操作步骤:

1.在虚拟机中建立ovs网桥

2.添加gre连接

3.配置docker容器虚拟网桥

4.为虚拟网桥添加ovs接口

5.添加不同Docker容器网段路由

具体执行的一些命令如下:

  1. sudo ovs-vsctl show
  2. sudo ovs-vsctl add-br obr0
  3. sudo ovs-vsctl add-port obr0 gre0
  4. sudo ovs-vsctl set interface gre0 type=gre options:remote_ip=192.168.59.104
  5. sudo ovs-vsctl show
  6. sudo brctl addbr br0
  7. sudo ifconfig br0 192.168.1.1 netmask 255.255.255.0
  8. sudo brctl addif br0 obr0
  9. sudo brctl show
  10. route
  11. sudo ip route add 192.168.2.0/24 via 192.168.59.104 dev eth0


使用weave实现跨主机容器连接

建立一个虚拟的网络,用于将运行在不同主机的Docker容器连接起来.

要实现这种方式所需要的条件如下:

双网卡,Host-Only & NAT

host1:10.0.2.6

host2:10.0.2.8

host1上应用容器1:192.168.0.2/24 host1上应用容器2:192.168.1.2/24

host2上应用容器1:192.168.0.3/24

两台机器上均安装Docker以及weave,并均启动好weave路由容器

在两台机器上均启动一个应用容器.可以直接使用weave run命令,也可以先使用docker run启动好容器,然后使用weave attach命令给容器绑定IP地址.

weave run 192.168.0.2/24

或者

docker run -itd ubuntu bash
weave attach 192.168.0.2/24 &ID

这个时候发现两个容器之间不通的,需要使用weave connect命令在两台weave的路由器之间建立连接.

weave connect 10.0.2.8

会发现,此时位于两台不同主机上的容器之间可以相互ping通了。但是处于不同子网的两个容器是不能互联的,这样我们就可以使用不同子网进行容器间的网络隔离了。

我们会发现,如果不使用Docker的原生网络,在容器内部是不能访问宿主机以及外部网络的。此时我们可以使用weave expose 192.168.0.1/24来给weave网桥添加IP,以实现容器与宿主机网络连通。但是,此时在容器内部依然不能访问外部网络。

我们可以同时使用Docker的原生网络和weave网络来实现容器互联及容器访问外网和端口映射。使用外部网络及端口映射的时候就使用docker0网桥,需要容器互联的时候就使用weave网桥。每个容器分配两个网卡。

操作步骤:

1.安装weave

# sudo wget -O /usr/local/bin/weave https://raw.githubusercontent.com/zettio/weave/master/weave
# sudo chmod a+x /usr/local/bin/weave

2.启动weave

weave launch

非docker的jenkins的master如何使用docker的jenkins的slave

前提

1、存在jenkins的master,这个master不是docker的,是通过yum install jenkins安装的

2、使用docker创建n个jenkins,方法是docker pull jenkins拉取官方的jenkins版本

这里可以使用网易镜像中心的jenkins:https://c.163.com/hub#/m/repository/?repoId=3093

或者docker上的jenkins:https://store.docker.com/images/jenkins

操作

1、首先使用官方版镜像创建jenkins,根据官方指导(上面的两个链接里有)使用如下命令:

docker run --name myjenkins1 -p 8081:8080 -p 50000:50000 -v /var/lib/jenkins:/var/jenkins_home jenkins
或者:docker run --name myjenkins2 -p 8082:8080 -p 50000:50000 -v /home/admin/jenkins:/var/jenkins_home  hub.c.163.com/library/jenkins

说明:因为我们是制作slave的,所有没有必要映射5000端口,5000端口主要作为master的jenkins用来连接slave的。
当我们在一个机器上创建多个docker容器作为jenkins的slave时,需要映射宿主机不同的位置.同事要映射宿主机不同的端口,避免端口冲突

2、在master上配置从节点

说明:远程工作目录根据上步映射的宿主机目录填写(/home/admin/jenkins/var/lib/jenkins);

启动方法选择Launch agent via execution of command on the master, Launch command内容为sshpass -p password  ssh user@ip /usr/install/jdk1.8.0_60/jre/bin/java -jar /var/lib/jenkins/slave.jar;master机器上先安装sshpass(yum install sshpass),”/usr/install/jdk1.8.0_60/jre/bin/java”为master机器的java执行全路径,不要写java,同时如果/varl/lib/jenkins下面没有slave.jar的化,copy进去

基于Docker+Jenkins+Gitlab搭建持续集成环境

随着DevOps理念和敏捷理念的发展,我们希望通过自动化技术,加快项目的迭代。尤其是当使用微服务方案后,面临在大量的项目构建和部署工作,借助于jenkins的持续集成,可以快速把应用打包成docker镜像,实现自动部署。

持续集成.png

如图演示了以下的场景:

  • 开发者向自己的gitlab网站提交了代码
  • jenkins通过定时任务检测到了代码有变成,执行自动化构建过程
  • jenkins在自动化构建脚本中调用docker命令将构建好的镜像push 私有镜像注册中心
  • 同时,jenkins也可以直接执行remote shell启动构建好的容器
  • 构建失败或者成功,可以及时将结果推送给相关人员,比如测试人员,安排测试
  • 服务端可以手动通过docker命令,从镜像仓库中心拉取镜像,进行手动部署

我搭建的环境都是在本地,gitlab、jenkins、docker私有仓库都部署在本地,以下是操作步骤:

搭建docker私有仓库

使用docker拉取registry镜像,然后启动容器

docker run -d -p 5000:5000 -v ~/docker-registry:/tmp/registry registry

这样就可以在本地运行一个私有镜像注册中心,通过镜像名称前缀127.0.0.1:5000可以将镜像推送到这个地址

搭建gitlab

拉取gitlab镜像,并启动

docker pull gitlab/gitlab-ce
sudo docker run --detach \
--hostname gitlab.bill.com \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume ~/gitlab/config:/etc/gitlab \
--volume ~/gitlab/logs:/var/log/gitlab \
--volume ~/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce:latest

因为部署在本地,又指定了gitlab.bill.com作为域名,所以在/etc/hosts配置下,这样可以通过域名访问gitlab。

127.0.0.1 gitlab.bill.com

初次登录可以修改密码,或者重新注册一个用户,我注册了一新用户,并在gitlab中创建了一个demo项目

demo.png

创建了一个空项目后,然后根据github上的提示,将本地的git项目推送到gitlab

Gitlab_http.png

上传完后Gitlab项目如下:

Demo sourcecode.png

搭建jenkins

安装jenkins
使用docker 下载jenkins镜像 jenkinsci/jenkins,并启动

docker run -d \
-p 8080:8080 \
-p 50000:50000 \
--name jenkins \
--link gitlab:gitlab.bill.com \
-u root \
-v ~/jenkins:/var/jenkins_home  \
-v /usr/share/maven:/usr/local/maven \
-v /usr/lib/jvm/jdk1.8:/usr/local/jdk 
jenkinsci/jenkins:latest

8080端口是jenkins的端口,5000端口是master和slave通信端口(没错,jenkins可以部署集群,在本次中没有配置)
并将宿主机maven和jdk映射到对应的容器目录上,同时通过配置–link连接gitlab,因为要从gitlab下代码。

初次启动的时候,可以通过docker logs -f jenkins查看控制台的密码,通过这个密码登录系统,执行创建用户等操作

安装插件
启动项目后,下载所需的插件(尤其要下载Git Plugin和Maven Intergration Plugin插件),如果缺少Maven Integration Plugin插件,在创建job时,不会有新建一个maven项目选项

jenkins plugin.png

注意:在Global Tool Configuration中配置Maven和Jdk,按道理可以配置通过配置JAVA_HOME 和MAVEN_HOME,分别指向/usr/local/jdk,/usr/local/maven,但是通过测试JAVA_HOME生效了,但MAVEN_HOME没成效,提示说没有找到maven,所以又通过自动化安装重新配置了maven

自动安装maven.png

配置项目
新建一个项目,输入项目名称demo,并选择Maven project,然后在配置中做如下配置:

General Info.png
Source Code Management.png
Build Triggers.png

每隔5检查是否有新代码发布,如果有则自动执行构建

Pre Steps.png
Build Settings.png

点开项目,点击左侧菜单build now则立马开始一次构建,点开当前构建信息,左侧Console Output中,可查看构建的明细

Console Output.png

集成docker部署

在上述的命令中,基本完成集成,但是还没有使用docker,构建的jar包也无法推送到测试环境或者生产环境,我们可以通过如下思路解决:

在项目生成jar包之后,调用项目中Dockerfile文件,使用docker构建镜像,将镜像推送到私有镜像中心的同时,使用该镜像启动一个容器(启动前删除同名容器)

代码如下所示:

# 定义变量
API_NAME="demo"
API_VERSION="0.0.1"
API_PORT=58080
IMAGE_NAME="127.0.0.1:5000/billjiang/$API_NAME:$BUILD_NUMBER"
CONTAINER_NAME=$API_NAME-$API_VERSION

# 进入target 目录复制Dockerfile 文件
cd $WORKSPACE/target
cp classes/Dockerfile .

#构建docker 镜像
docker build -t $IMAGE_NAME .

#推送docker镜像
docker push $IMAGE_NAME

#删除同名docker容器
cid=$(docker ps | grep "$CONTAINER_NAME" | awk '{print $1}')
if [ "$cid" != "" ]; then
   docker rm -f $cid
fi

#启动docker 容器
docker run -d -p $API_PORT:8080 --name $CONTAINER_NAME $IMAGE_NAME

#删除 Dockerfile 文件
rm -f Dockerfile

以上未声明的变量$WORKSPACE为jenkins的变量

Post Steps中通过配置以上Execute shell命令,并不能成功执行,发现在Console Output中出现docker not found 的错误,是因为jenkins 的容器没有安装docker。所以解决这个问题要么jenkins不使用docker容器安装,要自己安装一个带有docker的jenkins容器,当然还有docker in docker的类似解决方案,本人觉得独立安装jenkins也许算的上一种比较好的方案。

 

MAC删除开机自启动程序

一些程序在安装后会设置开机启动,如果不希望这些程序开机启动,可以通过以下办法禁止掉

打开「系统偏好」选择「用户与群组」,选择当前用户,并在右侧选择登录项标签,开机启动的程序会在这里列出来,想禁止哪一个,选中,并点击下方的➖按钮即可。

一般通过以上方法即可禁止许多应用,但也有一些应用并未出现用户的登陆项列表里,此时就需要别的办法禁止其开机启动。

部分应用在安装时会生成几个plist文件,用以设置开机启动。这些文件大多存在于一下几个位置

/Library/LaunchDaemons/
/Library/LaunchAgents/
~/Library/LaunchAgents/

其中~开头的是用户目录,/开头的是系统目录。在这些目录下存在着以程序名命名的plist文件,或者包含程序名。
找到某个程序对应的plist文件后,可通过下面的命令来取消其开机启动

launchctl unload com.razer.rzupdater.plist # 禁止razer程序的开机启动

Ansible之roles介绍

Ansible之roles介绍

本节内容:

  • 什么场景下会用roles?
  • roles示例

一、什么场景下会用roles?

假如我们现在有3个被管理主机,第一个要配置成httpd,第二个要配置成php服务器,第三个要配置成MySQL服务器。我们如何来定义playbook?

第一个play用到第一个主机上,用来构建httpd,第二个play用到第二个主机上,用来构建php,第三个play用到第三个主机上,用来构建MySQL。这些个play定义在playbook中比较麻烦,将来也不利于模块化调用,不利于多次调。比如说后来又加进来一个主机,这个第4个主机既是httpd服务器,又是php服务器,我们只能写第4个play,上面写上安装httpd和php。这样playbook中的代码就重复了。

为了避免代码重复,roles能够实现代码重复被调用。定义一个角色叫websrvs,第二个角色叫phpappsrvs,第三个角色叫dbsrvs。那么调用时如下来调用:

hosts: host1
role:
- websrvs

hosts: host2
role:
- phpappsrvs
        
hosts: host3
role:
- dbsrvs
        
hosts: host4
role:
- websrvs
- phpappsrvs
这样代码就可以重复利用了,每个角色可以被独立重复调用。下面举例说明使用方式。

二、roles示例

假设有3台主机,172.16.7.151主机上安装MySQL,172.16.7.152上安装httpd,172.16.7.153上安装MySQL和httpd。我们建立两个角色websrvs和dbsrvs,然后应用到这几个主机上。

1. 创建roles的必需目录 

[root@node1 opt]# mkdir -pv ansible_playbooks/roles/{websrvs,dbsrvs}/{tasks,files,templates,meta,handlers,vars}

每个role下面有个目录叫meta,在里面可以新建文件main.yml,在文件中可以设置该role和其它role之前的关联关系。

2. 配置角色

(1)配置角色websrvs

[root@node1 opt]# cd ansible_playbooks/roles/
[root@node1 roles]# cd websrvs/
[root@node1 websrvs]# ls
files  handlers  meta  tasks  templates  vars

a. 将httpd配置文件上传到files目录下,我这里假设httpd.conf每台主机都是一样的,实际上应该用模板,先用一样的配置文件举例

[root@node1 websrvs]# cp /etc/httpd/conf/httpd.conf files/

直接复制的静态文件都放在files目录下。打算用模板文件的都放在templates目录下。

b.编写任务列表tasks

[root@node1 websrvs]# vim tasks/main.yml
- name: install httpd package
  yum: name=httpd
- name: install configuration file
  copy: src=httpd.conf dest=/etc/httpd/conf
  tags:
  - conf
  notify:
  - restart httpd
- name: start httpd
  service: name=httpd state=started

c.由于上面的tasks中定义了notify,所以要定义handlers

[root@node1 websrvs]# vim handlers/main.yml
- name: restart httpd
  service: name=httpd state=restarted

如果需要定义变量,则在vars目录下创建main.yml文件,在文件中写入变量,以key:value的形式定义,比如:

http_port: 8080

(2)配置角色dbsrvs

[root@node1 roles]# cd dbsrvs/
[root@node1 dbsrvs]# ls
files  handlers  meta  tasks  templates  vars

a.将MySQL配置文件上传到files目录下。

b.编写任务列表tasks

[root@node1 dbsrvs]# vim tasks/main.yml
- name: install mysql-server package
  yum: name=mysql-server state=latest
- name: install configuration file
  copy: src=my.cnf dest/etc/my.cnf
  tags:
  - conf
  notify:
  - restart mysqld
- name:
  service: name=mysqld enabled=true state=started
c.定义handlers
[root@node1 dbsrvs]# vim handlers/main.yml
- name: restart mysqld
  service: name=mysqld state=restarted

(3)定义playbook

【注意】:要在roles目录同级创建playbook。

[root@node1 ansible_playbooks]# vim web.yml
- hosts: 172.16.7.152
  roles:
  - websrvs 

[root@node1 ansible_playbooks]# vim db.yml
- hosts: 172.16.7.151
  roles:
  - dbsrvs 

[root@node1 ansible_playbooks]# vim site.yml
- hosts: 172.16.7.153
  roles:
  - websrvs
  - dbsrvs
运行:
[root@node1 ansible_playbooks]# ansible-playbook web.yml
[root@node1 ansible_playbooks]# ansible-playbook db.yml
[root@node1 ansible_playbooks]# ansible-playbook site.yml

当然也可以把这些内容写入同一个playbook中。playbook的名字可以自定义。

cordova–入门 webview_url修改,打开web时跳出app

cordova–入门 webview_url修改,打开web时跳出app

若url为http打头的,需要做如下修改:

修改web的网址:

cordova初始化:
安装cordova cli
1、下载安装node.js(js的运行环境),安装时勾选npm(js包的管理工具)。
2、下载安装git(配置好环境变量)。
3、使用npm安装cordova。

Swift – 封装一个正则表达式工具类(附:正则替换、正则匹配样例)

之前我写过一篇文章介绍如何使用正则表达式来验证用户名、邮箱、URL 等格式是否正确(点击查看)。除了验证数据外,我们还可以使用正则表达式进行文字替换、或者提取工作。下面通过样例进行演示。

一、封装一个正则工具类(Regex.swift)

由于 NSRegularExpression 使用起来十分繁琐,为方便使用,我们首先对它进行封装。增加一些常用的正则处理方法。
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
import Foundation
/// 基于NSRegularExpression api 的正则处理工具类
public struct Regex {
    private let regularExpression: NSRegularExpression
    
    //使用正则表达式进行初始化
    public init(_ pattern: String, options: Options = []) throws {
        regularExpression = try NSRegularExpression(
            pattern: pattern,
            options: options.toNSRegularExpressionOptions
        )
    }
    
    //正则匹配验证(true表示匹配成功)
    public func matches(_ string: String) -> Bool {
        return firstMatch(in: string) != nil
    }
    
    //获取第一个匹配结果
    public func firstMatch(in string: String) -> Match? {
        let firstMatch = regularExpression
            .firstMatch(in: string, options: [],
                        range: NSRange(location: 0, length: string.utf16.count))
            .map { Match(result: $0, in: string) }
        return firstMatch
    }
    
    //获取所有的匹配结果
    public func matches(in string: String) -> [Match] {
        let matches = regularExpression
            .matches(in: string, options: [],
                     range: NSRange(location: 0, length: string.utf16.count))
            .map { Match(result: $0, in: string) }
        return matches
    }
    
    //正则替换
    public func replacingMatches(in input: String, with template: String,
                                 count: Int? = nil) -> String {
        var output = input
        let matches = self.matches(in: input)
        let rangedMatches = Array(matches[0..<min(matches.count, count ?? .max)])
        for match in rangedMatches.reversed() {
            let replacement = match.string(applyingTemplate: template)
            output.replaceSubrange(match.range, with: replacement)
        }
        
        return output
    }
}
//正则匹配可选项
extension Regex {
    /// Options 定义了正则表达式匹配时的行为
    public struct Options: OptionSet {
        
        //忽略字母
        public static let ignoreCase = Options(rawValue: 1)
        
        //忽略元字符
        public static let ignoreMetacharacters = Options(rawValue: 1 << 1)
        
        //默认情况下,“^”匹配字符串的开始和结束的“$”匹配字符串,无视任何换行。
        //使用这个配置,“^”将匹配的每一行的开始,和“$”将匹配的每一行的结束。
        public static let anchorsMatchLines = Options(rawValue: 1 << 2)
        
        ///默认情况下,"."匹配除换行符(\n)之外的所有字符。使用这个配置,选项将允许“.”匹配换行符
        public static let dotMatchesLineSeparators = Options(rawValue: 1 << 3)
        
        //OptionSet的 raw value
        public let rawValue: Int
        
        //将Regex.Options 转换成对应的 NSRegularExpression.Options
        var toNSRegularExpressionOptions: NSRegularExpression.Options {
            var options = NSRegularExpression.Options()
            if contains(.ignoreCase) { options.insert(.caseInsensitive) }
            if contains(.ignoreMetacharacters) {
                options.insert(.ignoreMetacharacters) }
            if contains(.anchorsMatchLines) { options.insert(.anchorsMatchLines) }
            if contains(.dotMatchesLineSeparators) {
                options.insert(.dotMatchesLineSeparators) }
            return options
        }
        
        //OptionSet 初始化
        public init(rawValue: Int) {
            self.rawValue = rawValue
        }
    }
}
//正则匹配结果
extension Regex {
    // Match 封装有单个匹配结果
    public class Match: CustomStringConvertible {
        //匹配的字符串
        public lazy var string: String = {
            return String(describing: self.baseString[self.range])
        }()
        
        //匹配的字符范围
        public lazy var range: Range<String.Index> = {
            return Range(self.result.range, in: self.baseString)!
        }()
        
        //正则表达式中每个捕获组匹配的字符串
        public lazy var captures: [String?] = {
            let captureRanges = stride(from: 0, to: result.numberOfRanges, by: 1)
                .map(result.range)
                .dropFirst()
                .map { [unowned self] in
                    Range($0, in: self.baseString)
            }
            
            return captureRanges.map { [unowned self] captureRange in
                if let captureRange = captureRange {
                    return String(describing: self.baseString[captureRange])
                }
                
                return nil
            }
        }()
        
        private let result: NSTextCheckingResult
        
        private let baseString: String
        
        //初始化
        internal init(result: NSTextCheckingResult, in string: String) {
            precondition(
                result.regularExpression != nil,
                "NSTextCheckingResult必需使用正则表达式"
            )
            
            self.result = result
            self.baseString = string
        }
        
        //返回一个新字符串,根据“模板”替换匹配的字符串。
        public func string(applyingTemplate template: String) -> String {
            let replacement = result.regularExpression!.replacementString(
                for: result,
                in: baseString,
                offset: 0,
                template: template
            )
            
            return replacement
        }
        
        //藐视信息
        public var description: String {
            return "Match<\"\(string)\">"
        }
    }
}

 

二、使用样例

1,验证字符串格式

下面样例验证一个邮箱地址的格式是否正确。
1
2
3
4
5
6
7
8
9
10
11
//初始化正则工具类
let pattern = "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
let regex = try! Regex(pattern)
//验证邮箱地址
let mailAddress = "admin@hangge.com"
if regex.matches(mailAddress) {
    print("邮箱地址格式正确")
}else{
    print("邮箱地址格式有误")
}

 

2,提取字符串

(1)获取第一个匹配结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//初始化正则工具类
let pattern = "([\\u4e00-\\u9fa5]+):([\\d]+)"
let regex = try! Regex(pattern)
//原始字符串
let str = "王大锤:123456,李子明:23457,李洛克:110"
//获取第一个匹配对象
if let first = regex.firstMatch(in: str) {
    print("--- 第一个匹配结果  ---")
    print(first)
    print("匹配字符串:", first.string)
    print("捕获组:", first.captures[0]!, first.captures[1]!)
    print("匹配范围:", first.range)
}

(2)获取所有的匹配结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//初始化正则工具类
let pattern = "([\\u4e00-\\u9fa5]+):([\\d]+)"
let regex = try! Regex(pattern)
//原始字符串
let str = "王大锤:123456,李子明:23457,李洛克:110"
//获取第一个匹配对象
for match in regex.matches(in: str) {
    print("\n--- 匹配结果  ---")
    print(match)
    print("匹配字符串:", match.string)
    print("捕获组:", match.captures[0]!, match.captures[1]!)
    print("匹配范围:", match.range)
}

3,字符串替换

(1)简单的替换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//初始化正则工具类
let pattern = "([\\u4e00-\\u9fa5]+):([\\d]+)"
let regex = try! Regex(pattern)
//原始字符串
let str = "王大锤:123456,李子明:23457,李洛克:110"
//只替换第1个匹配项
let out1 = regex.replacingMatches(in: str, with: "***", count: 1)
//替换所有匹配项
let out2 = regex.replacingMatches(in: str, with: "***")
  
//输出结果
print("原始的字符串:", str)
print("替换第1个匹配项:", out1)
print("替换所有匹配项:", out2)

(2)捕获组替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//初始化正则工具类
let pattern = "([\\u4e00-\\u9fa5]+):([\\d]+)"
let regex = try! Regex(pattern)
//原始字符串
let str = "王大锤:123456,李子明:23457,李洛克:110"
//只替换第1个匹配项
let out1 = regex.replacingMatches(in: str, with: "$1的电话是$2", count: 1)
//替换所有匹配项
let out2 = regex.replacingMatches(in: str, with: "$1的电话是$2")
  
//输出结果
print("原始的字符串:", str)
print("替换第1个匹配项:", out1)
print("替换所有匹配项:", out2)