本地部署iOS應用OTA安裝 Go + Goland詳細實現步驟

用一臺Mac Mini部署了Jenkins之後,打完包要下載到自己電腦用iTunes安裝。

因此決定用Go在Mini 上也配置一個OTA在線安裝環境

 

1. 下載安裝Go 

https://golang.org/dl/

默認安裝路徑在/usr/local/go,添加環境

vi ~/.bash_profile

添加內容  export PATH=$PATH:/usr/local/go/bin

source  ~/.bash_profile

 

2. 安裝Goland

使用量並不大,使用Goland直接run

建一個工程文件夾GoProject,下面都在這個文件夾下 

新建ota.go

菜單->run->edit configurations 添加go build

 

接下來參考https://blog.csdn.net/sydnash/article/details/54691878

 

3.自建https證書

新建文件夾myssl

cd myssl
sudo openssl genrsa -out server.key 1024
sudo openssl req -new -key server.key -out server.csr //填寫信息時Common Name爲本機ip地址,服務器有域名使用域名
sudo openssl genrsa -out ca.key 1024
sudo openssl req -new -x509 -days 365 -key ca.key -out ca.crt

在myssl中新建文件夾demoCA

在demoCA新建文件夾newcerts,新建文件index.txt和serial,serial添加文本內容:01

生成ca文件

sudo openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key 

如果出現錯誤

Using configuration from /private/etc/ssl/openssl.cnf

variable lookup failed for ca::default_ca

首先確保安裝 openssl。安裝: brew install openssl
拷貝文件
sudo cp /usr/local/etc/openssl/openssl.cnf /private/etc/ssl/openssl.cnf

 

生成證書後,在GoProject中新建文件夾ssl,將ca.crt拷貝一份過來

 

4. OTA現在必須使用https

固定鏈接格式讀取plist安裝

鏈接:itms-services://?action=download-manifest&url=https://xxx/xxx.plist

Jenkins打包後沒有提供ota的plist文件 需要靠自己創建

在GoProject中新建文件夾OtaInstall,用於保存創建的plist文件。同時存放57*57和512*512兩張應用icon圖片

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>https://xxx/xxx.ipa</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>display-image</string>
                    <key>url</key>
                    <string>https://xxx/57*57.png</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <string>full-size-image</string>
                    <key>url</key>
                    <string>https://xxx/512*512.png</string>
                </dict>
            </array>
            <key>metadata</key>
            <dict>
                <key>bundle-identifier</key>
                <string>應用buildle-id</string>
                <key>bundle-version</key>
                <string>版本號</string>
                <key>kind</key>
                <string>software</string>
                <key>title</key>
                <string>應用名</string>
            </dict>
        </dict>
    </array>
</dict>
</plist>

 

5. Go實現

 

package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
)

var installTemplate = `
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, minimum-scale=1, user-scalable=no">
  <meta content="telephone=no" name="format-detection"/>
  <title>應用名-在線安裝</title>
</head>
<style>
  html {
    width: 100%;
    height: 100%
  }
  body {
    background-color: #fafafa;
    font-family: "Microsoft YaHei";
    color: #0a0909;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
	margin-right:auto;
	margin-left:auto;
	text-align:center;
  }
  div, p, header, footer, h1, h2, h3, h4, h5, h6, span, i, b, em, ul, li, dl, dt, dd, body, input, select, form, button {
    margin: 0;
    padding: 0
  }
  ul, li {
    list-style: none
  }
  img {
    border: 0 none
  }
  input, img {
    vertical-align: middle
  }
  * {
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    outline: 0;
    box-sizing: border-box;
  }
  a {
	text-align:center;
	margin-right:auto;
	margin-left:auto;
  }
  h1, h2, h3, h4, h5, h6 {
    font-weight: normal;
  }
  .title {
    font-size: 18px;
    margin-bottom: 20px;
  }
  .install {
    width: 300px;
    height: 40px;
    border: 1px solid #ccc;
    background: transparent;
    border-radius: 6px;
    font-size: 14px;
    margin-bottom: 10px;
    display: block;
	margin-right:auto;
	margin-left:auto;
  }
</style>
<body>
</br></br></br>
  <p class="title">iOS應用OTA安裝</p>
	</br></br>
  <a title="iPhone" href="http://ip:1717/static/ca.crt">
    <button class="install">證書信任</button>
  </a>
</br></br>
   <a href="https://ip:1718/ipalist">
    <button class="install">打包列表</button>
  </a>
</body>
</html>
`

var ipalistHtml  =  `<!DOCTYPE html>
	<html>
	<head lang="en">
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, minimum-scale=1, user-scalable=no">
	<meta content="telephone=no" name="format-detection"/>
	<title>應用名-打包列表</title>
	</head>
	<style>
		html {
			width: 100%;
			height: 100%
		}
	body {
		width: 100%;
		height: 100%;
		background-color: #fafafa;
		font-family: "Microsoft YaHei";
		color: #0a0909;
		-webkit-touch-callout: none;
		-webkit-user-select: none;
	}
	</style>
	<body>
	</br></br></br>
`

func main() {
    ///安裝頁和列表頁,兩個靜態頁面
	http.Handle("/install/", http.HandlerFunc(install))
	http.Handle("/ipalist/", http.HandlerFunc(ipalist))
    ///靜態文件服務
    ///將ca證書文件夾開放提供下載 設置安裝後 需要在設備 設置-關於本機-證書信任,信任證書
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./ssl"))))
    ///開放打包存放路徑
	http.Handle("/ota/", http.StripPrefix("/ota/", http.FileServer(http.Dir("/xxx/Package"))))
    ///開放存放ota plist文件夾
	http.Handle("/otainstall/", http.StripPrefix("/otainstall/", http.FileServer(http.Dir("./OtaInstall"))))
    ///監聽1717端口 提供http服務 用於證書安裝
	go func() {
		http.ListenAndServe(":1717", nil)
	}()
    ///使用之前自建的證書,監聽1718端口 提供https服務 用於應用安裝 
	err := http.ListenAndServeTLS(":1718", "./myssl/server.crt", "./myssl/server.key", nil)
	if err != nil {
		log.Fatal("ListenAndServe:", err)
	}
}

///install 頁面 證書信任 包列表
func install(w http.ResponseWriter, req *http.Request) {
	fmt.Fprint(w, installTemplate)
}

///包列表 從Jenkins打包保存路徑讀取所有打包文件夾
func ipalist(w http.ResponseWriter, req *http.Request)  {

	dir_list, e := ioutil.ReadDir("/xxx/Package")
	if e != nil {
		fmt.Fprintf(w, "get ipa list error:%s", e.Error())
		return
	}
      ///文件列表倒序
	dir_list = reverse(dir_list)
	var html  string = ipalistHtml
	for _, f := range dir_list {
        ///不是文件夾過濾掉
		if f.IsDir() == false {
			continue
		}
		var name string = f.Name()
		subList, _ := ioutil.ReadDir("/xxx/Package/" + name)
        ///如果文件夾裏沒有子文件 則忽略(這裏只是粗略的過濾,過濾掉空文件夾)
		if len(subList) == 0 {
			continue
		}
        ///從.xcarchive文件裏讀取info.plist獲取version和build
		var version, build  = getIPAVersionWithFileName(name)
        ///如果沒有ota在線安裝的plist配置文件則創建一個
		createOTAPlistIfNeededWithFileName(name)
        ///作爲一個a標籤拼接到html
		var a = "<div style=\"text-align:center;\"><a" + " href=\"itms-services://?action=download-manifest&url=https://ip:1718/otainstall/"+ name + ".plist\" >"  + name + "	[" + version + "]" + "[build " + build + "]" +  "</a></div> </br>"
		html += a
	}
	html += "</body></html>"
    ///顯示html靜態頁面
	fmt.Fprintf(w, html)
}

///將文件列表倒序
func reverse(s [] os.FileInfo) []os.FileInfo {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}

///判斷是否已經有ota安裝plist文件,如果沒有創建則創建並寫入內容
func createOTAPlistIfNeededWithFileName(filename string)  {
	var path = "./OtaInstall/" + filename + ".plist"
	exist, err := pathExists(path)
	if err != nil {
		return
	}
	if exist {
		return
	}
	var version,_  = getIPAVersionWithFileName(filename)
    ///拼接plist內容
	var content = `
	<?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>https://ip:1718/ota/`+ filename + "/" + filename + `.ipa</string>
				</dict>
				<dict>
					<key>kind</key>
					<string>display-image</string>
					<key>url</key>
					<string>https://ip:1718/otainstall/icon57.png</string>
				</dict>
				<dict>
					<key>kind</key>
					<string>full-size-image</string>
					<key>url</key>
					<string>https://ip:1718/otainstall/icon512.png</string>
				</dict>
			</array>
			<key>metadata</key>
			<dict>
				<key>bundle-identifier</key>
				<string>com.xxx.xxx</string>
				<key>bundle-version</key>
				<string>` + version + `</string>
				<key>kind</key>
				<string>software</string>
				<key>title</key>
				<string>應用名</string>
			</dict>
		</dict>
	</array>
</dict>
</plist>`

	f, err := os.Create(path) //創建文件
	if err != nil{
		return
	}
    ///寫入文件
	io.WriteString(f,content)
}

///讀取包version和build 
///內容比較簡單,用比較笨的辦法做的
///一行一行讀取 找到<key>CFBundleShortVersionString</key>和<key>CFBundleVersion</key>
func getIPAVersionWithFileName(filename string)(v string, build string){
	var path = "/xxx/Package/" + filename + "/xxx.xcarchive/Info.plist"
	fi, err := os.Open(path)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return "1.0.0","1"
	}
	defer fi.Close()

	br := bufio.NewReader(fi)
	var version string = "1.0.0"
	var buildNum string = "1"
	for {
		a, _, c := br.ReadLine()
		if c == io.EOF {
			break
		}
		var text = string(a)

		if strings.Contains(text, "<key>CFBundleShortVersionString</key>") {
			a, _, _ := br.ReadLine()
			text = string(a)
			var sub string = strings.Split(text,"<string>")[1]
			version = strings.Split(sub, "</string>")[0]
		}
		if strings.Contains(text, "<key>CFBundleVersion</key>") {
			a, _, _ := br.ReadLine()
			text = string(a)
			var sub string = strings.Split(text,"<string>")[1]
			buildNum = strings.Split(sub, "</string>")[0]
		}
	}
	return version, buildNum
}

///判斷文件或者文件夾是否存在
func pathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

運行後,在同一局域網絡下就可以使用了

Jenkins打包後刷新列表頁,第一次因爲寫plist文件比較慢一點,然後就可以選擇安裝了

代碼耦合的,路徑寫在代碼裏,自行解耦合

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章