前言
無風不起浪,為什麼會做這個事情,就要由前幾天講起了。。
悲劇了
小公司沒有資源,因為很多內測都是用第三方的,這邊用的是蒲公英;
在某日早上,開發提測,打包,上傳pgy
,準備給業務方體驗的時候,結果點選檢視下載頁
按鈕,彈出這貨;
一開始以為是自己手誤,然後再上傳幾次,依然顯示這個介面,也沒有任何報錯資訊,懵逼啊,之前都用的好好的,什麼鬼?
折騰半天,無望,拿起手機,看到有簡訊,點開一開,顯示這個:
這裡面說到不再接受金融類應用在該平臺分發,我司產品雖然是資訊類產品,但內容的確是金融相關的,好像沒毛病;
操作起來有點麻煩
公司某專案的打包產品是一個zip,是當打包完成後把apk跟ipa壓縮成一個zip輸出,而使用者需要下載這個zip,解壓,電腦連手機/模擬器安裝,方可體驗;
整個鏈路過長,也比較麻煩,因此就想著兩點:
- 打包拆分,支援安卓、ios分開打包,不然有時候驗證一個平臺的問題需要打兩個包,打包時長成本問題;
- 打包產物顯示二維碼便捷下載
在正式開始之前,先說明下,testerhome其實有類似的文章,如下圖:
對應的文章都寫的挺好的,但是輪子嘛,還是要親力親為印象才深刻,而且會針對對應文章缺乏的內容進行相應補充,儘可能更加詳細把過程寫出來;
jenkins顯示圖片
這裡不會再講述jenkins是什麼,怎麼安裝之類的內容,如果有疑問,請點選此處或第二處檢視;
想要做成的效果是這樣的:
- 支援修改檔案描述
- 支援顯示二維碼
外掛安裝
jenkins不支援上面兩個操作的,因此需要安裝外掛來使用;
- Build Name Setter ,用於修改Build名稱
- description setter,用於在修改Build描述資訊,在描述資訊中增加顯示QRCode(二維碼)
直接在Jenkins的外掛管理頁面搜尋上述外掛,點選安裝即可
怎麼用
點選對應job的設定介面;
Build Name Setter
點選Build Environment
,找到set build name
,預設是#${BUILD_NUMBER}
,這裡可以自定義,如下修改成#${BUILD_NUMBER}jbtest
,執行任務後的結果是這樣的;
description setter
這個是在Post-build Actions
裡面,將<img src='qr_code_url'>
寫入到build描述資訊中即可;
但填寫完發現跟預想的不一致,這是因為Jenkins出於安全的考慮,所有描述資訊的Markup Formatter
預設都是採用Plain text
模式,在這種模式下是不會對build描述資訊中的HTML編碼進行解析的。
要改變也很容易,Manage Jenkins
-> Configure Global Security
,將Markup Formatter
的設定更改為Safe HTML
即可。
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3559712731,2278077975&fm=26&gp=0.jpg" style="background-color: rgb(177, 197, 163); width: 286px; height: 189px;">
複製程式碼
儲存後,執行任務,會就會顯示url對應的圖片了;
到這裡,jenkins上顯示圖片的問題,就這樣解決啦~
小小結
jenkins顯示圖片及修改任務描述,需要安裝兩個外掛,並且需要傳一個圖片的img標籤過來即可;
jenkins任務結果收集產物
這裡額外提及一個點,如果job裡面是有產物的,比如apk等檔案,預設構建後是不會顯示出來的,如下圖:
那怎樣讓其在右側顯示出來?還是開啟job的設定項,Post-build Actions
,選擇歸檔成品/Archives build artifacts,在Files to archive
裡面輸入內容就好啦;
定位檔案時,可以通過正規表示式進行匹配,也可以呼叫專案的環境變數;多個檔案通過逗號進行分隔;
${OUTPUT_FOLDER}/*.ipa,*.txt,QRCode.png
#注意,${OUTPUT_FOLDER}是
複製程式碼
新增後的配置頁面如下圖所示:
重新構建任務,就可以看到對應的產物啦;
分發平臺
首先說明,非廣告貼,非廣告貼,這節除了介紹分發平臺,也會介紹不使用分發平臺時怎麼搞,任君選擇;
上網搜了下,目前國內比較有名且還能用的分發平臺,就是蒲公英跟fir.im
蒲公英
點選上面的地址開啟官網,註冊登入,點選文件,會有API說明;
簡單看了下,支援的功能蠻多的,好像可以,而本章的重點是上傳APP,可以搜尋框輸入上傳APP,也可以點選連結直接跳轉;
仔細看了下response,有二維碼地址,good,就是你啦;
常規引數說明
引數 | 別稱 | 說明 |
---|---|---|
_api_key | API Key | API Key,用來識別API呼叫者的身份,如不特別說明,每個介面中都需要含有此引數。對於同一個蒲公英的註冊使用者來說,這個值在固定的; |
userKey | User Key | 使用者Key,用來標識當前使用者的身份,對於同一個蒲公英的註冊使用者來說,這個值在固定的; |
appKey | App Key | 表示一個App組的唯一Key。例如,名稱為'微信'的App上傳了三個版本,那麼這三個版本為一個App組,該參數列示這個組的Key。這個值顯示在應用詳情--應用概述--App Key。 |
buildKey | Build Key | Build Key是唯一標識應用的索引ID,可以通過獲取App所有版本取得 |
_api_key
跟userKey
在登入狀態下,點選網頁的按鈕即可獲取;
上傳App
引數太多了,懶的貼了,直接上程式碼吧;
Linux
這是官網給的例子,Linux
下直接使用curl
命令上傳即可;
curl -F 'file=@/c/Users/jb/Desktop/jb-android-3.4.1.30402-release-1812251912.apk' -F '_api_key=你的key' https://www.pgyer.com/apiv2/app/upload
複製程式碼
執行後等待上傳完即可:
從返回的結果來看,是有一個buildQRCodeURL,就是拿這個給到jenkins那邊的;Python 環境,py3
import requests
import sys
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
upload_url = "https://www.pgyer.com/apiv2/app/upload"
_api_key = "你的key"
apk_path = "你要上傳的檔案路徑"
def pgy_uploadFile():
# 獲取執行傳遞過來的引數
# _apk_path = sys.argv[1]
# 上傳apk
try:
file = {'file': open(apk_path, 'rb')}
param = {'_api_key': _api_key}
req=requests.post(url=upload_url,files=file,data=param,verify=False)
if (req.status_code == 200):
print(req.json().get("data")["buildQRCodeURL"])
else:
print("上傳失敗,狀態碼: "+req.status_code)
except Exception as e:
print("upload:" + e)
if __name__ == '__main__':
pgy_uploadFile()
複製程式碼
如果是需要傳參給指令碼,就直接用sys.argv
來獲取,指令碼本來沒做太多相容處理,將就用吧;
最後會輸出二維碼地址,拿這個地址傳給jenkins就好啦;
結合jenkins玩玩
上面提及到,jenkins顯示二維碼是利用img src來處理,但是這個蒲公英返回的二維碼地址是每次都不同的呢,那怎麼搞?按照常理來說,是把src的值寫成變數就好啦;
其實就是寫成一個變數就好了,但是也因為url本身每次都變化,因此不能直接貼url,而是把url下載下來,固定下來一個名稱,變數直接取這個路徑即可;
那上面的img標籤就會變成這樣啦:
<img src="${BUILD_URL}/artifact/QRCode.png" style="background-color: width: 286px; height: 189px;">
# 上面有多餘的樣式,因為圖片是隨便複製的,比較大,因此做了下限制,非必選;
複製程式碼
這裡可能有同學會問題,這個${BUILD_URL}
是怎麼來的,代表什麼意思;
${BUILD_URL}
是jenkins的內建變數,代表著顯示當前構建的URL地址,文章尾部會列出常用的jenkins變數;
比如上,假如二維碼的連結是這樣:
jenkineUrl/job/jobName/34/artifact/QRCode.png
複製程式碼
那麼,jenkineUrl/job/jobName/34
這一串就是${BUILD_URL}
既然需要圖片,那我們就下載圖片吧,反正都有url了;
def pgy_doanloadQRCode(QRCodeURL):
print("準備下載二維碼")
filename = os.getcwd()+"/QRCode.png"
with open(filename, 'wb') as f:
# 以二進位制寫入的模式在本地構建新檔案
header = {
'User-Agent': '"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",'
, 'Referer': QRCodeURL}
f.write(requests.get(QRCodeURL, headers=header).content)
print("%s下載完成" % filename)
複製程式碼
結果截圖
因pgy需要上傳apk或ipa,因為為了方便,直接hardcore了一個圖片url來演示結果;
fir.im
點選這裡跳轉到官網,看了下,實名認證使用者有 100 次/日的免費下載限額,未實名,僅有10次/日的免費下載限額;
一般來說,小公司,每天100次夠用啦,除非產品夠多,或者打包頻繁;
實名好麻煩,還要手持證件照,沒關係,反正有10次,夠用啦;
然後去看api文章,咦,response居然沒有二維碼欄位?那手動上傳一個應用試試看,結果。。
當時心裡的疑問就如下圖一樣,好吧,再見;小小結
網上找了下分發平臺,國內比較有名且還能用的只剩下蒲公英跟fir.im,然而fir.im需要實名才能玩,那就剩下蒲公英了,親自接入下蒲公英,接入比較簡單,而且支援的欄位也不少,目前來看,比較推薦,省去不少事;
造輪子
此時有同學可能會有疑問,我司產品比較機密,不想用第三方,自己可以造一個輪子嗎?
這個問題非常好,沒錯,可以的,簡單想想了,這個輪子需要啥?
- 一個介面,提供上傳檔案按鈕;
- 檔案支援點選下載,支援滑鼠移動到檔案時顯示對應的二維碼;
- 一臺伺服器;
想的介面很簡單,本來一開始是想著安裝個phpstudy就好啦,但是突然想起,前幾天看到大佬發了個截圖:
想著差不多,也順便弄一個倉庫唄,比較方便;
但jb不懂這個怎麼弄,就跑去問老大了;
聽老大說挺簡單的,但是啊,jb只聽過ng,沒真正玩過啊,哪裡簡單的了,哭;
於是乎就去簡單瞭解ng下了,漫漫人生路;
nginx簡單介紹
也許沒聽過nginx,但是沒關係,Apache肯定是聽過的,這兩者都屬於http server,因此,nginx同樣是一款開源的HTTP伺服器軟體;
主要拿來幹嘛
- 反向代理
- 負載均衡
- HTTP伺服器(包含動靜分離)
- 正向代理
反向代理
反向代理應該是Nginx做的最多的一件事了;
什麼是反向代理呢,以下是百度百科的說法:
反向代理(Reverse Proxy)方式是指以代理伺服器來接受internet上的連線請求,然後將請求轉發給內部網路上的伺服器,並將從伺服器上得到的結果返回給internet上請求連線的客戶端,此時代理伺服器對外就表現為一個反向代理伺服器。簡單來說就是真實的伺服器不能直接被外部網路訪問,所以需要一臺代理伺服器,而代理伺服器能被外部網路訪問的同時又跟真實伺服器在同一個網路環境,當然也可能是同一臺伺服器,埠不同而已。
複製程式碼
下面貼上一段簡單的實現反向代理的程式碼
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host:$server_port;
}
}
複製程式碼
儲存配置檔案後啟動Nginx,這樣當我們訪問localhost的時候,就相當於訪問localhost:8080了;
負載均衡
負載均衡其意思就是分攤到多個操作單元上進行執行,例如Web伺服器、FTP伺服器、企業關鍵應用伺服器和其它關鍵任務伺服器等,從而共同完成工作任務。
簡單而言就是當有2臺或以上伺服器時,根據規則隨機的將請求分發到指定的伺服器上處理,負載均衡配置一般都需要同時配置反向代理,通過反向代理跳轉到負載均衡。
HTTP伺服器
Nginx本身也是一個靜態資源的伺服器,當只有靜態資源的時候,就可以使用Nginx來做伺服器,同時現在也很流行動靜分離,就可以通過Nginx來實現,首先看看Nginx做靜態資源伺服器;
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;
location / {
root e:\wwwroot;
index index.html;
}
}
複製程式碼
這樣如果訪問http://localhost
就會預設訪問到E盤wwwroot目錄下面的index.html,如果一個網站只是靜態頁面的話,那麼就可以通過這種方式來實現部署。
動靜分離
動靜分離是讓動態網站裡的動態網頁根據一定規則把不變的資源和經常變的資源區分開來,動靜資源做好了拆分以後,我們就可以根據靜態資源的特點將其做快取操作,這就是網站靜態化處理的核心思路;
upstream test{
server localhost:8080;
server localhost:8081;
}
server {
listen 80;
server_name localhost;
location / {
root e:\wwwroot;
index index.html;
}
# 所有靜態請求都由nginx處理,存放目錄為html
location ~ \.(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
root e:\wwwroot;
}
# 所有動態請求都轉發給tomcat處理
location ~ \.(jsp|do)$ {
proxy_pass http://test;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root e:\wwwroot;
}
}
複製程式碼
這樣就可以吧HTML以及圖片和css以及js放到wwwroot目錄下,而tomcat只負責處理jsp和請求;
例如當我們字尾為gif的時候,Nginx預設會從wwwroot獲取到當前請求的動態圖檔案返回,當然這裡的靜態檔案跟Nginx是同一臺伺服器,我們也可以在另外一臺伺服器,然後通過反向代理和負載均衡配置過去就好了,只要搞清楚了最基本的流程,很多配置就很簡單了,另外localtion後面其實是一個正規表示式,所以非常靈活;
正向代理
意思是一個位於客戶端和原始伺服器(origin server)之間的伺服器,為了從原始伺服器取得內容,客戶端向代理髮送一個請求並指定目標(原始伺服器),然後代理向原始伺服器轉交請求並將獲得的內容返回給客戶端。客戶端才能使用正向代理。
nginx常用命令
啟動nginx
service nginx start
複製程式碼
停止nginx
nginx -s stop
複製程式碼
檢視nginx程式
ps -ef | grep nginx
複製程式碼
平滑啟動nginx
nginx -s reload
複製程式碼
平滑啟動的意思是在不停止nginx的情況下,重啟nginx,重新載入配置檔案,啟動新的工作執行緒,完美停止舊的工作執行緒。
強制停止nginx
pkill -9 nginx
複製程式碼
檢查對nginx.conf檔案的修改是否正確
nginx -t -c /etc/nginx/nginx.conf
or
nginx -t
複製程式碼
檢視nginx的版本
nginx -v
or
nginx -V
複製程式碼
埠開放
因阿里雲預設是安裝了nginx 1.6版本,因此這塊不說明;
直接在阿里雲找到安全組規則,新增對應的對口,就可以用公網IP訪問啦;
修改預設埠
因nginx預設是使用80埠的,如果需要修改,需要去配置檔案修改;
nginx安裝檔案在/etc/nginx
,開啟後發現裡面有個nginx.conf
,檢視發現裡面沒有埠資訊,但是最後一行插入了*.conf
檔案,那我們就跟著這目錄找;
cd
到conf.d目錄,發現裡面只有一個default.conf
檔案,編輯檢視,發現裡面有個listen埠,這個就是了,修改成像要的埠,儲存即可;
然後輸入nginx -s reload
重啟伺服器,然後再用公網IP+埠訪問下,也會顯示Welcome to nginx!
增加埠
有同學可能問,那想加多幾個埠可以嗎?
沒問題的,還是來到default.conf
檔案,在原來的server下新增一個就好啦,如下:
server {
listen 8083;
server_name location;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /home/file_dir;
autoindex on; #開啟nginx目錄瀏覽功能
autoindex_exact_size off; #檔案大小從KB開始顯示
autoindex_localtime on; #顯示檔案修改時間為伺服器本地時間
add_after_body /autoindex.html;
charset utf-8;
}
複製程式碼
autoindex
nginx有一個目錄瀏覽功能(autoindex),但是呢,預設是不允許列出整個目錄的,如果有需求,就用上面的新增埠的方式來操作就好啦;
而上面這個autoindex.html檔案點選下發連結下載即可;
連結:https://pan.baidu.com/s/1oiukkMAILzq9lHwCKzy-0w
提取碼:7ytc
複製程式碼
最後,整個效果如下:
還可以解析README.md
,騷啊;
root&alias
在弄ng的配置檔案時,看到過別人是這樣弄的;
location /ware {
alias /lvdata/warehouse/;
複製程式碼
而自己基本上只會這麼弄的:
location / {
root e:\wwwroot;
複製程式碼
當時心裡就想,這兩者有什麼區別?
簡介
root與alias主要區別在於nginx如何解釋location後面的uri,這會使兩者分別以不同的方式將請求對映到伺服器檔案上。
語法
root的用法
句法: root path;
預設: root html;
語境: http,server,location,if in location
複製程式碼
示例1:
location ^~ /request_path/dirt/ {
root /local_path/dirt/;
}
複製程式碼
當客戶端請求 /request_path/image/file.ext
的時候,Nginx把請求解析對映為/local_path/dirt/request_path/dirt/file.ext
例項2:
location ^~ /t/ {
root /www/root/html/;
}
複製程式碼
如果一個請求的URI是/t/a.html
時,web伺服器將會返回伺服器上的/www/root/html/t/a.html
的檔案;
alias的用法
句法: alias path;
預設: -
語境: location
複製程式碼
示例1:
location /request_path/dirt/ {
alias /local_path/dirt/file/;
}
複製程式碼
當客戶端請求 /request_path/dirt/file.ext
的時候,Nginx把請求對映為/local_path/dirt/file/file.ext
注意這裡是file目錄,因為alias
會把location
後面配置的路徑丟棄掉(比如/request_path/dirt/one.html
,到alias
那裡就剩one.html
了),把當前匹配到的目錄指向到指定的目錄。
示例2:
location ^~ /t/ {
alias /www/root/html/new_t/;
}
複製程式碼
如果一個請求的URI是/t/a.html
時,web伺服器將會返回伺服器上的/www/root/html/new_t/a.html
的檔案;
綜合例子
location /abc/ {
alias /home/html/abc/;
}
複製程式碼
在這段配置下,http://test/abc/a.html
就指定的是 /home/html/abc/a.html
;
這段配置亦可改成使用 root
標籤:
location /abc/ {
root /home/html/;
}
複製程式碼
這樣,nginx 就會去找 /home/html/
目錄下的 abc
目錄了,得到的結果是相同的。
但是,如果把 alias
的配置改成
location /abc/ {
alias /home/html/def/;
}
複製程式碼
那麼 nginx 將會直接從 /home/html/def/
取資料,例如訪問 http://test/abc/a.html
指向的是 /home/html/def/a.html
;
這段配置還不能直接使用 root
配置,如果非要配置,只有在 /home/html/
下建立一個 def->abc
的軟 link(快捷方式)
了。
一般情況下,在 location / 中配置 root,在 location /other 中配置 alias 是一個好習慣。
其他
-
使用alias時,目錄名後面一定要加"/",不然會認為是個檔案。
-
alias在使用正則匹配時,location後uri中捕捉到要匹配的內容後,並在指定的alias規則內容處使用。
location ~ ^/users/(.+\.(?:gif|jpe?g|png))$ { alias /data/w3/images/$1; } 複製程式碼
-
alias只能位於location塊中,而root的許可權不限於location。
舉個例子
你提供地址,當你在家時:
1.朋友想去找你,看到阿姨,阿姨說在家,那朋友得到的是你提供的地址+阿姨說的,這就是root,會把兩個地址串起來;
2.班主任想去找你,看到阿姨,阿姨說,你直接跟我說就好了,那班主任得到的就是阿姨說的,這就是alias,會把location後面配置的路徑棄掉,把當前匹配到的目錄指向到指定目錄;
複製程式碼
當你不在家時(動靜分離):
1.(alias)班主任找你的結果不變,結果依然是以阿姨說的為主;
2.(root)因為root是把連個地址串一起的原因,這種情況不太適用,如果非要用root,要在阿姨身上加一個電話,直接call你(軟link)
複製程式碼
上傳檔案
既然可以訪問了,那就寫個簡單的HTML上傳檔案吧,關於後端的話,本來想用php
,畢竟這塊網上例子很多,比如這裡,都有原始碼了;
但是呢,畢竟不懂php
,即使它是最棒的語言,考慮到後面維護麻煩,還是選擇用flask
吧;
直接通過pip命令進行安裝即可:
pip install flask
複製程式碼
官方的一個最簡單示例:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello Flask!"
if __name__ == "__main__":
app.run()
#如果想在公網訪問,就修改如下:
#app.run((host='0.0.0.0',port=5000,debug=True))
#直接開啟ip:5000就可以了,記得開放埠許可權
複製程式碼
這裡不會詳細介紹flask,感興趣的同學可以來官網看看;
對於簡單的上傳,一般只需要3個步驟:
1. 建立上傳表單
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
複製程式碼
2. 獲取檔案
當點選上傳/提交按鈕,要獲取到上傳的檔案,通過requests物件中的files就可以獲取到啦~
file = request.files['file']
複製程式碼
3. 儲存檔案 獲取到檔案,接著就是儲存了,指定路徑和檔名;
file.save(path + filename)
複製程式碼
配置檔案
實際在上傳檔案的時候,會做下限制,比如限制檔案大小、資料夾地址、上傳副檔名等,而在實際專案,還會有金鑰、資料庫地址等等,這些都是屬於配置項;
一般有3種方式:
直接寫入指令碼
當你指令碼是輕量,配置項不多的情況下,可以直接寫到指令碼里面;
from flask import Flask
app = Flask(__name__)
app.config['name'] = 'jb'
app.config['DEBUG'] = True
app.config['age'] = 18
複製程式碼
當然也可以用字典來簡化程式碼:
from flask import Flask
app = Flask(__name__)
app.config.update(
DEBUG=True,
name='jb',
age=18
)
複製程式碼
單獨配置檔案
這種適用於配置項多離的情況,可以建立一個獨立的配置檔案,如config.py
;
name = 'jb'
DEBUG = True
age = 18
複製程式碼
然後匯入配置:
import config
...
app = Flask(__name__)
app.config.from_object(config)
...
複製程式碼
或者:
...
app = Flask(__name__)
app.config.from_pyfile('config.py')
...
複製程式碼
不同的配置類
當需要多個配置配合,比如測試配置、開發配置、運營配置,這時候就需要在配置檔案中建立不同的配置類,然後在建立程式例項時引入相應的配置類;
這裡繼續以config.py
為例子,建立一個儲存通用配置的類;
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class BaseConfig: # 基本配置類
SECRET_KEY = os.getenv('SECRET_KEY', 'some secret words')
ITEMS_PER_PAGE = 10
class DevelopmentConfig(BaseConfig):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.getenv('DEV_DATABASE_URL', 'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.getenv('TEST_DATABASE_URL', 'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
WTF_CSRF_ENABLED = False
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'default': DevelopmentConfig
}
複製程式碼
這裡說明下,上面是把配置寫入系統環境變數,然後使用os模組的getenv()方法獲取,第二個引數作為預設值;
通過from_object()方法匯入配置:
from config import config # 匯入儲存配置的字典
...
app = Flask(__name__)
app.config.from_object(config['development']) # 獲取相應的配置類
...
複製程式碼
那回到這次的功能上,我們只需要寫到指令碼里面即可;
app.config['UPLOAD_FOLDER'] = os.getcwd()
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
複製程式碼
當然還要考慮安全問題,如檔名校驗之類的,具體的話,看原始碼:
import os
from flask import Flask, request, url_for, send_from_directory
from werkzeug import secure_filename
# 副檔名
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.getcwd()+"/file_upload" #上傳地址
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #檔案大小,單位N
html = '''
<!DOCTYPE html>
<title>Upload File</title>
<h1>圖片上傳</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=上傳>
</form>
'''
# 檢查檔案型別
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
# 獲取上傳後的檔案
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
#判斷上傳檔名
if file and allowed_file(file.filename):
# 檢查檔名
filename = secure_filename(file.filename)
#如果目錄不存在則建立
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.mkdir(app.config['UPLOAD_FOLDER'])
# 儲存圖片
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
# 獲取url
file_url = url_for('uploaded_file', filename=filename)
return html + '<br><img src=' + file_url + '>'
return html
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8087,debug=True)
複製程式碼
這裡說個小事情,一開始執行上面的程式碼,會報錯:
網上找了下,原因是在Subline3遇到的都是看似空格實則沒有空格引起的::
解決方法: 就是開啟subline的空格製表顯示就可以清楚的顯示出自己是否真的空格了。
第一次遇到這問題,詳情請點選這裡檢視;
執行指令碼,上傳檔案,上傳的檔案就是在file_upload
目錄下的;
功能是有的,就是介面low了點。。
身為一個測試同學,對UI肯定要有點追求,並且希望可以提供下載,因此就上網找了個外掛,點選這裡,看看github上的截圖:
聽漂亮的,這樣上傳就有了進度,並且支援多檔案上傳,但依然會有個問題,上傳完去哪裡看?想下載怎麼辦?
這裡想說一個問題,上面的例子,如果大家有細心看的話,會發現上傳的檔案都會去到一個叫file_upload
資料夾,上傳是沒問題的,但是下載就有問題了;
jb在下載的時候,不管這個下載地址怎麼拼,頁面都會無情報錯,說這個地址不存在;
但是呢,如果把上傳目錄修改成static
,卻是正常的;
包括如果這個static
不在根目錄,也一樣有問題,那為什麼當static
是根目錄且放到這裡面就這樣?
Flask資源定位是依靠
app = Flask(__name__)
複製程式碼
__name__
引數(檔名或包名),所以相對定位一定要基於這個檔案路徑。
為什麼會在static資料夾路徑下會正確**?Flask預設靜態檔案在static檔案下。**
那如果一定要上傳到file_upload
資料夾,怎麼辦?
那就修改的flask預設的static資料夾只需要在建立Flask例項的時候,把static_folder
和static_url_path
引數設定為空字串
即可。
app = Flask(__name__, static_folder='', static_url_path='')
複製程式碼
訪問的時候用url_for函式,res資料夾和static資料夾同一級:
url_for('static', filename='res/sheeta.jpg')
複製程式碼
最終就成了這樣:
<img src ="/file_upload/a.png">
//或者
<img src = "{{ url_for('file_upload', filename = 'a.png') }}>
複製程式碼
good,問題搞定了,體驗下:
搞定,那接著就是生成二維碼;
生成二維碼
支援點選開啟/下載,那如果是上傳apk/ipa那就麻煩了,jenkins那邊,所以還是希望能生成一個二維碼;
而Python生成二維碼的話,有qrcode,同時也需要處理下二維碼圖片,因此需要安裝qrcode
、pillow
:
pip install qrcode
pip install pillow
複製程式碼
最簡單的demo:
import qrcode
qr = qrcode.QRCode(version=2, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=1)
qr.add_data("我是jb啊~")
qr.make(fit=True)
img = qr.make_image()
img.save("jb_qrcode.png")
複製程式碼
這裡面是有一些引數的,但是邊幅原因,請各自去了解;
這時候就生成一個二維碼了,但是呢,上面的demo圖片太小了,而且想弄個定製化的二維碼,怎麼破?不細說,直接原始碼拿走不謝~
# 生成二維碼
def get_QRCode(filename):
#檢測目錄的方法,不存在則建立
checkdir(存放二維碼目錄路徑)
# 初步生成二維碼影象
qr = qrcode.QRCode(
version=5,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=8,
border=4
)
# 二維碼存放的內容,可文案,可連結
qr.add_data("二維碼路徑")
qr.make(fit=True)
# 獲得Image例項並把顏色模式轉換為RGBA
img = qr.make_image()
img = img.convert("RGBA")
# 開啟logo檔案
icon = Image.open("定製的logo")
# 計算logo的尺寸
img_w,img_h = img.size
factor = 4
size_w = int(img_w / factor)
size_h = int(img_h / factor)
# 比較並重新設定logo檔案的尺寸
icon_w,icon_h = icon.size
if icon_w >size_w:
icon_w = size_w
if icon_h > size_h:
icon_h = size_h
icon = icon.resize((icon_w,icon_h),Image.ANTIALIAS)
# 計算logo的位置,並複製到二維碼影象中
w = int((img_w - icon_w)/2)
h = int((img_h - icon_h)/2)
icon = icon.convert("RGBA")
img.paste(icon,(w,h),icon)
# 儲存二維碼
img.save(os.path.join(二維碼路徑, filename))
複製程式碼
可以直接拿來複用,需要修改的也就幾個引數,簡單便捷,效果圖如下:
其他小系列
在處理過程,遇到一些小問題,簡單羅列下;
中文名稱被吃了
是這樣的,如果一個檔案裡面有中文,比如訊息.jpg
,把這個地址塞到二維碼,然後掃描,會這樣的:
很簡單,encode下就好了;
編碼:
from urllib.parse import quote
text = quote(text, 'utf-8')
複製程式碼
解碼:
from urllib.parse import unquote
text = unquote(text, 'utf-8')
複製程式碼
這樣後,就能在微信開啟啦~
ios不能直接下載ipa包
來到這裡,主路徑都是通的,二維碼也是可以生成的,用android手機試下,沒問題,可以線上預覽圖片或者下載檔案;
但是用ios手機掃描ipa包
的連結,結果不會像安卓那樣下載ipa包安裝,而是load半天,然後這樣顯示:
???這跟劇本不一樣啊;
網上找了下,很多這樣的例子,這裡參考的是這裡,
大致的步驟就是:
- 準備plist、ipa包、icon、HTMLdemo;
- plist上傳到https地址;
- HTML裡面加一個點選事件;
原理:
是通過Safari解析連結中的"itms-services://"來實現的。
例如:
<a title="iPhone" href="itms-services://?action=download-manifest&url=https://192.168.10.193/installIPA.plist"> Iphone Download</a>
Safari會去讀取installIPA.plist中的資訊,如:iOS應用的名稱、版本、安裝地址等。
複製程式碼
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/layer/2.3/layer.js"></script>
<title>File Manager</title>
</head>
<body>
<h1>File Manager</h1>
<div id="btnContainer">
<a id="btnA" href="itms-services://?action=download-manifest&url=你的plist地址,必須要https,不然會提示簽名無效">
<span id="btnSpan2">v1.1.2</span>
</a>
</div>
</html>
複製程式碼
這裡的href是填入plist
的地址,必須要https,原因是iOS7.1
以後, Apple不再支援HTTP
方式的OTA ,所以需要開啟HTTPS
服務,不然會提示無效證照;
test.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>ipa包地址</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>包名</string>
<key>bundle-version</key>
<string>版本號</string>
<key>kind</key>
<string>software</string>
<key>subtitle</key>
<string>應用名稱</string>
<key>title</key>
<string>應用名稱</string>
</dict>
</dict>
</array>
</dict>
</plist>
複製程式碼
因篇幅問題,只留必備項,只輸入填入ipa
包地址,底部那塊後設資料資訊即可;
最終的結果就是這樣啦:
主流程總算通了,深呼一口氣;
這裡說明下,如果沒有https,測試的話,可以試試上傳的github
,這裡是https的;
當長期考慮,還是要弄一個,公司有就用公司的,公司沒有就自己買一個,jb是用aly,所以也在aly買了個,有興趣的同學點選這裡 ,購買、認證、解析、申請證照,就可以了,這塊不說明,感興趣的可自行上網查詢,
這裡演示的是demo,因此url都是hardcore的,實際還要處理plist
的路徑等,上傳一個ipa
就生成一個plist
,這塊自行處理吧;
原始碼
連結:https://pan.baidu.com/s/1SFFtGJHmUHhpqq2MwwhpXw
提取碼:2hdd
複製程式碼
原始碼在此,就不再單獨解釋了,可能會有一些問題,但是模型大致就這樣啦,因時間問題,年後再優化,反正就是缺什麼就import什麼就好了;
小結
本文折騰很久,主要是因為重新看回flask
跟學習下ng
,以及ios的解決方案,外加年底工作很繁忙,因此陸陸續續折騰了快半個月的時間,本來還想把所有優化都做好再放出來,但怕開年後更加忙了,那這文章就爛尾了,因此就先發出來了;
本文涉及到的內容比較多,包括jenkins如何顯示圖片
、pgy分發平臺的使用
、自己搭一個檔案上傳的輪子
,涉及到的只是有ng
、flask
,都是比較簡單的內容,但是是否做過是兩回事,從小白的角度出發來落地;
體驗地址點選這裡,如果伺服器沒掛的話,開啟是這樣的;
如果有更好的方案,歡迎一起交流~
最後,大家新年快啦~一路順風,明年見~
最後的最後,謝謝大家~
這裡還有一節,主要是介紹下jenkins的內建變數,感興趣的同學可以看看~
jenkins內建變數
郵件的配置變數
變數名 | 說明 |
---|---|
${GIT_BRANCH} |
build 的 Git 分支; |
${FILE,path="xxx"} |
xxx 為指定的檔案,檔案內容可以在郵件中顯示。注意:xxx 是工作區目錄的相對路徑; |
${JOB_DESCRIPTION} |
顯示專案描述; |
${BUILD_NUMBER} |
顯示當前構建的編號; |
${SVN_REVISION} |
顯示 svn 版本號; |
${CAUSE} |
顯示誰、通過什麼渠道觸發這次構建; |
${CHANGES} |
顯示上一次構建之後的變化; |
${BUILD_ID} |
顯示當前構建生成的ID; |
${PROJECT_NAME} |
顯示專案的全名; |
${PROJECT_DISPLAY_NAME} |
顯示專案的顯示名稱; |
${JENKINS_URL} |
顯示 Jenkins 伺服器的 url 地址(可以在系統配置頁更改); |
${BUILD_LOG_MULTILINE_REGEX} |
按正規表示式匹配並顯示構建日誌; |
${BUILD_LOG} |
顯示最終構建日誌; |
${PROJECT_URL} |
顯示專案的URL地址; |
${BUILD_STATUS} |
顯示當前構建的狀態(失敗、成功等等); |
${BUILD_URL} |
顯示當前構建的URL地址; |
${CHANGES_SINCE_LAST_SUCCESS} |
顯示上一次成功構建之後的變化; |
${CHANGES_SINCE_LAST_UNSTABLE} |
顯示顯示上一次不穩固或者成功的構建之後的變化; |
${ENV} |
顯示一個環境變數; |
${FAILED_TESTS} |
如果有失敗的測試,顯示這些失敗的單元測試資訊; |
${PROJECT_URL} |
顯示專案的 URL; |
${TEST_COUNTS} |
顯示測試的數量; |
環境變數
變數名 | 說明 |
---|---|
BRANCH_NAME |
設定為正在構建的分支的名稱; |
CHANGE_ID |
更改ID,例如拉取請求號; |
CHANGE_URL |
設定為更改URL; |
CHANGE_TITLE |
設定為更改的標題; |
CHANGE_AUTHOR |
設定為擬議更改的作者的使用者名稱; |
CHANGE_AUTHOR_DISPLAY_NAME |
設定為作者的人名; |
CHANGE_AUTHOR_EMAIL |
設定為作者的電子郵件地址; |
CHANGE_TARGET |
設定為可以合併更改的目標或基本分支; |
BUILD_NUMBER |
目前的編號,如“153”; |
BUILD_ID |
當前版本ID,與BUILD_NUMBER相同; |
BUILD_DISPLAY_NAME |
當前版本的顯示名稱; |
JOB_NAME |
此構建專案的名稱,如“foo”; |
JOB_BASE_NAME |
此建立專案的名稱將剝離資料夾路徑,例如“bar”。 |
BUILD_TAG |
jenkins- JOBNAME− {BUILD_NUMBER} 的字串; |
EXECUTOR_NUMBER |
識別執行此構建的當前執行程式(在同一臺計算機的執行程式中)的唯一編號; |
NODE_NAME |
代理的名稱; |
NODE_LABELS |
空格分隔的節點分配的標籤列表; |
WORKSPACE |
分配給構建作為工作區的目錄的絕對路徑; |
JENKINS_HOME |
Jenkins主節點上分配的目錄絕對路徑儲存資料; |
JENKINS_URL |
完整的Jenkins網址,如http://server:port/jenkins/ ; |
BUILD_URL |
此構建的完整URL,如http://server:port/jenkins/job/foo/15/ ; |
JOB_URL |
此作業的完整URL,如http://server:port/jenkins/ job/foo/ ; |
SVN_REVISION |
Subversion版本號,當前已被檢出到工作區,如“12345”; |
SVN_URL |
當前已經檢出到工作空間的Subversion URL; |