前言
上周蒲公英在毫无征兆的情况下,把官网的域名给改了。本以为各种接口改成最新的域名就能接着用,但实际上jenkins打包自动上传到蒲公英成功后,iOS扫码安装失效了。众所周知苹果iOS包的网页扫码安装是由itms-services服务实现,但是前提是域名需要支持https,我们本地已经有一台jenkis打包机了,那我本地打完包后,把ipa包放到本地http服务,不经过域名,是否也能实现内网下扫码安装呢?答案接着往下看
什么是itms-services
itms-service是apple为iOS用户提供网页、扫码安装方式所使用的协议。这种方式不需要经过APP Store分发也可以安装iOS App。(前提是你的iPhone设备ID已经加入进去打包的开发者账号、或者使用企业账号(现在基本绝种))。
items-services协议本质就是访问一个https的远程plist描述文件,这个plist记载了app的信息和ipa包下载地址。 plist文件格式如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>http://192.168.1.2/xxx.ipa</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.zxh.xpzc</string>
<key>bundle-version</key>
<string>1.0.0</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>需要下载这个标题</string>
</dict>
</dict>
</array>
</dict>
</plist>
传统方案一般是打包机打完包后,生成上面的plist文件,把ipa上传cdn服务器上,然后plist文件也上传到cdn即可。
用户只要扫码或者浏览器打开:
itms-services://?action=download-manifest&url=https://XXXXXXXX/app.plist
就可以扫码安装。由于itms-services需要使用https链接,很多人默认以为一定要有一个域名加上配了https才行。实际上如果我本地开启一个https服务,直接https://192.168.3.x/install.plist是否可以呢?
搭建本地https服务
搭建本地https服务非常简单,我们只需要brew安装http-server
brew install http-server
在Mac下使用http-server开启HTTP服务后,要配置HTTPS,需要执行以下步骤:
- 生成SSL证书和私钥:可以使用openssl命令生成自签名的SSL证书和私钥。打开终端,执行以下命令:
openssl req -nodes -new -x509 -keyout server.key -out server.crt
这将生成一个名为server.key
的私钥文件和一个名为server.crt
的证书文件。
- 启动HTTP服务并配置HTTPS:在终端中,切换到你要启动HTTP服务的目录下,然后执行以下命令:
http-server -S -C server.crt -K server.key
这将启动一个使用HTTPS的HTTP服务。-S
参数表示启用HTTPS,-C
参数指定证书文件,-K
参数指定私钥文件。
- 在浏览器中访问:打开浏览器,输入
https://localhost:8080
(假设HTTP服务监听在8080端口),即可访问使用HTTPS的本地HTTP服务。
请注意,由于自签名证书不受信任的CA颁发,浏览器可能会显示警告信息。如果需要使用受信任的证书,可以申请并购买来自公共CA的SSL证书,并将其配置到HTTP服务器中。
上面提到由于本地https是不受信任的,这导致如果ipa文件放到https服务下是不能直接下载的,所以我们这里取巧,同时开启https和http服务,https为了触发itms-servics协议,ipa文件则放到http服务下,这样就可以完整的实现本地iOS安装包扫码安装。同时启动http和https两个服务命令如下:
http-server -p 8081 & http-server -p 8082 -S -C server.crt -K server.key
本地自建服务兼容jenkins
之前编写的jenkins自动打包是打完包后分发是基于蒲公英的,如果要支持本地扫码安装那脚本需要兼容,脚本如下,这里只说明如何实现生成itms-services的plist文件,以及生成二维码,如何实现自动打包不做说明。
打完包生成了iPA后,我们把ipa挪到到http服务写入plist如下:
def write_local_itms_services(rootPath,ipa_directory,ipa_path):
pwd='python3 ./create_config.py --root_path {} --app_directory {} --app_url {} --build_version {}'.format(rootPath,ipa_directory,ipa_path,build_version)
os.system(pwd)
ipaPath=targerIPA_parth + '/' + ipa_filename + '/' + 'App.ipa'
httpIpaPath='http://192.168.41.3:8081' + '/' + ipa_filename + '/' + 'App.ipa'
write_local_itms_services(targerIPA_parth,ipa_filename,httpIpaPath)
生成plist脚本:
#!usr/bin/python3
# -*- coding:utf-8 -*-
import os
import sys
import plistlib
from datetime import datetime, timedelta, timezone
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--root_path', default='')
parser.add_argument('--app_directory', default='')
parser.add_argument('--app_url', default='')
parser.add_argument('--build_version', default=0)
parser.add_argument('--app_name', default='')
args = parser.parse_args()
JFAppUrl = args.app_url
JFAppDirectory = args.app_directory
JFBuildVersion = args.build_version
JFPackageRootPath = args.root_path
JFAppName = args.app_name
print(args)
def readInfoPlist(inputFilePath):
if not os.path.isfile(inputFilePath):
return None
with open(inputFilePath, 'rb') as fp:
infoPList = plistlib.load(fp)
print(infoPList)
return infoPList
def writePlist(outFilePath,fileName,packageName,title):
pl = {'items': [{
'assets': [
{'kind': 'software-package', 'url': JFAppUrl},
{'kind': 'display-image', 'url': 'https://xxx.jpg'},
{'kind': 'full-size-image', 'url': 'https://xxx.jpg'}],
'metadata':
{'bundle-identifier': packageName, 'bundle-version': JFBuildVersion, 'kind': 'software', 'title': title}
}]}
with open(outFilePath, 'wb') as fp:
plistlib.dump(pl, fp)
plistFileOut = JFPackageRootPath + "/"+ JFAppDirectory + "/" + "iOS_" + JFBuildVersion + ".plist"
infoPList = None
ipaFileName = 'App'
packageName = 'com.xxxx.xxxx'
appVersion = JFBuildVersion
title = JFAppName + '_' + JFBuildVersion
#写plist文件,用来网页安装
print(ipaFileName,packageName,appVersion,title)
writePlist(plistFileOut,ipaFileName,packageName,title)
写入plist后,我们生成二维码扫码安装,然后把二维码发到飞书(也是脚本实现)即可。
生成二维码脚本:
def write_qr_code(rootPath,ipaDirName):
saveRqCodeFileName="rqcode_" + build_number + ".png"
saveFilePath = rootPath + '/' + ipaDirName + '/' + saveRqCodeFileName
pwd = 'python3 ./createRqCode.py --build_number {} --qrcode_save_file {} --version {} --dir_name {}'.format(build_number,saveFilePath,build_version,ipaDirName)
print(pwd)
os.system(pwd)
return saveRqCodeFileName
write_local_itms_services(targerIPA_parth,ipa_filename,httpIpaPath)
imgName=write_qr_code(targerIPA_parth,ipa_filename)
//生成二维码图片保存路径,推送到飞书
imgPath=ipaPath=targerIPA_parth + '/' + ipa_filename + '/' + imgName
生成带itms-services协议二维码图片脚本:
from PIL import Image, ImageFont, ImageDraw
import os, sys
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--build_number', default=0)
parser.add_argument('--app_name', default='')
parser.add_argument('--version', default='')
parser.add_argument('--dir_name', default='')
parser.add_argument('--qrcode_save_file', default='')
args = parser.parse_args()
JFBuildNumber = args.build_number
JFAppName = args.app_name
JFVersion = args.version
JFDirName = args.dir_name
JFQRCodeSaveFile = args.qrcode_save_file
plistFileName= "iOS_" + JFVersion + ".plist"
InstallUrl='itms-services://?action=download-manifest&url=https://192.168.41.3:8082/' + JFDirName + '/' + plistFileName
pkEnvStr = "测试"
textContent = JFBuildNumber
packageName = JFAppName + pkEnvStr
imagSize = 512
imagSaveFile = "/tmp/number.png"
JFColor = (0,255,0,255)
path=os.getcwd()+'/script/'
font = ImageFont.truetype(path + "FZZZHONGJW.ttf", size=int(imagSize / 6))
img = Image.new("RGBA",(imagSize,imagSize),color=0)
# 写文字
draw = ImageDraw.Draw(img)
textSize = draw.textbbox((0,0),text=packageName, font=font)
draw.text(xy=((imagSize- textSize[2]) / 2, (imagSize - textSize[3]) / 4), text=packageName, font=font,fill=JFColor)
textSize = draw.textbbox((0,0),text=JFBuildNumber, font=font)
draw.text(xy=((imagSize- textSize[2]) / 2, (imagSize - textSize[3]) / 1.5), text=JFBuildNumber, font=font,fill=JFColor)
img.save(imagSaveFile)
# 输出
# img.show()
print(JFQRCodeSaveFile)
from MyQR import myqr
version, level, qr_name = myqr.run(
words=InstallUrl,
version=1,
level='H',
picture=imagSaveFile,
colorized=True,
contrast=1.0,
brightness=1.0,
save_name=JFQRCodeSaveFile,
save_dir=os.getcwd()
)
结论
脚本根据不同环境情况可能要略微调整,仅供参考,就这样成功生成二维码发送到飞书后,扫码即可实现本地https服务安装测试iOS包啦(前提是手机连接的wifi和打包机开启的https服务在同一个内网下),再也不用蒲公英分发啦,打包速度提升100s+(不需要上传到蒲公英服务器,本地上传时间为0)。