小程式在2017年1月上線之初,被社會極力吹捧,刻意去將其製造為一個“風口”,透支其價值。但是在之後一個月裡,石破天驚迅速歸為沉寂。媒體又開始過度消費小程式,大談其雞肋之處。
個人認為小程式的一個分水嶺是在12月28日。微信升級到6.6.1版本,將小程式入口移植主介面,下拉主介面即可選擇進入,並且支援新類目“小遊戲”。小程式逐漸升溫,在整個微信生態中扮演越來越重要的角色。時至今日,小程式的風潮如日中天,優秀的小程式很容易得到融資。這究竟是是另一輪泡沫,還是小程式本身真正已經進入了成熟期?我個人更相信後者。
我個人做過兩款小程式,對小程式有一定認識,但理解還是比較淺的。今天就僅從如何快速搭建一個小程式談起,揭開小程式的一絲神祕面紗。
概述
- 小程式是前後端分離的。
- 前端使用的是微信自定義的一套規範
wxml
+wxss
+json
+js
,我認為本質還是html
+css
+js
。 - 後臺可以選用任何你熟悉的語言:
Java
,Python
,PHP
,Ruby
等等,在這篇文章裡我選用Python
的Flask
框架+Gunicorn
+Nginx
來快速搭建。 - 資料庫我選擇
MySQL
,nosql資料庫我選擇Redis
。當然,你的小程式可以很輕量級,甚至不需要使用到資料庫。小程式一大思想“用完即走”。 - 後臺需要跑在一臺自己的伺服器上,同時你也需要一個已備案的
https
域名來進行對映。
附註
現在市面上也有一些第三方快速生成小程式的工具,和以前那些快速生成網站的是同一門生意。我個人並不推薦去使用那些,因為那些小程式幾乎千篇一律,無法結合你自己的創意,無法定製你需要提供的服務,而且必定存在一些收費。
當然,這些平臺既然存在,那麼必定是市場需求,假如確實適合你,能為你帶來一些效益,不妨一試。
今天,我們是以學習者的角度去構建小程式。
準備工作
- 一臺雲伺服器,可以上各大雲提供商平臺租用,我使用的是學生低配,¥10/月。
- 我在伺服器上使用的作業系統為
ubuntu
- 購買一個域名,並通過備案。域名價格在1-10000000不等,我使用的是某
com
域名,¥50/年。 - 在微信公眾平臺註冊一個賬號並下載小程式開發工具。詳細說明
- 從
http
到https
。現在很多SSL證照可以免費申請,下面會詳細說下如何配置。
目標
我們的目標是實現一個簡單的小程式,能夠實現前後端對接。
從http到https
- 首先擁有一個已備案域名,並已經解析到你的伺服器上了。
- 如果你在阿里/騰訊雲租用了伺服器,可以申請免費的SSL證照。找到相應入口並申請就可以了。稽核一般很快,我的在一小時以內。
- 稽核通過後下載頒發的證照,先儲存在本地。之後通過ftp傳到伺服器的相應路徑。
- 在伺服器上安裝
Nginx
。 - 首先測試你的Nginx服務是否能正常執行,配置完開啟自己的域名能顯示nginx的歡迎頁時即為成功配置。
- 然後將你的證照通過ftp上傳到伺服器的任意路徑下(建議和
Nginx
在同一路徑下) - 開啟
Nginx
的配置檔案,如圖配置(證照路徑填寫自己的) - 重啟服務,瀏覽器通過https訪問,能正常顯示頁面即為配置成功。
前端
現在,開啟你的小程式開發工具,並使用你的APPID新建一個專案。(我這裡沒有多餘的APPID,所以先使用測試環境)
可以先勾選“建立普通快速啟動模板”來生成一個官方測試demo,如下圖:
讓我們來觀察一下目錄結構。app.js
,app.json
,app.wxss
分別對應全域性的方法,全域性配置引數和全域性樣式。而在具體包下的index.js
,index.wxml
,index.wxss
則對應相應的元素。
現在讓我們來寫一點簡單的頁面的程式碼。
<!-- index.wxml -->
<view class="main-card">
<view class="main-card-item" id="toast" wx:if="{{news_flag}}">
<view class="card-item-hd" >
<image class="card-item-icon" src="/images/index/toast.png"></image>
<text class="card-item-name">大事兒</text>
</view>
<view class='toast'>
Hello,歡迎觀看此教程,希望對你有幫助。
</view>
</view>
</view>
複製程式碼
/* index.wxss */
page {
background-color: #f8f8f8;
}
.main-card {
padding-bottom: 100rpx;
}
.main-card-item{
display: flex;
flex-direction: column;
background: #fff;
border-top: 1rpx solid #F6F6EF;
border-bottom: 1rpx solid #F6F6EF;
margin-bottom: 20rpx;
background-repeat: no-repeat;
background-size: 100% auto;
background-position: bottom center;
overflow: hidden;
margin-left: 12rpx;
margin-right: 12rpx;
border-radius: 15rpx;
}
.card-item-hd{
display: flex;
align-items: center;
height: 75rpx;
border-bottom: 1rpx solid #e5e5e5;
margin-left: 30rpx;
}
.card-item-icon{
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
.card-item-name{
letter-spacing: 1px;
font-size: 25rpx;
}
.toast{
letter-spacing:3rpx;
line-height: 50rpx;
font-size: 28rpx;
margin-left: 20rpx;
margin-top: 20rpx;
margin-bottom: 40rpx;
}
複製程式碼
此時,一個簡單的頁面已經生成了,讓我們來看看效果。
很簡單,但是可以看出來「大事兒」裡的內容是寫死的,此時我們需要後端來提供資料。
###伺服器環境
在編寫後端之前,我們先把伺服器的環境部署一下。
安裝:
- 安裝了Python環境
apt-get install python-dev
- 安裝Flask
pip install flask
- 安裝UWSGI
pip install uwsgi
- 安裝了Nginx
apt-get install nginx
- 安裝了Gunicorn
pip install gunicorn
準備
首先在你的/var/www/
目錄下建立一個測試目錄,比如/var/www# mkdir test
然後使用chmod
更改此目錄的許可權chmod 777 /var/www/test
這裡講一下chmod
的規則,因為這裡是測試用例,所以為了方便,直接使用777。
Nginx
Ubuntu下的Nginx的目錄結構大致如下:
- 所有的配置檔案都在
/etc/nginx
下,每個虛擬主機已經安排在了/etc/nginx/sites-available
目錄下 - 啟動程式檔案在
/usr/sbin/nginx
- 日誌檔案放在了
/var/log/nginx
中,分別是access.log
和error.log
- 在
/etc/init.d/
下建立了啟動指令碼nginx - 預設的虛擬主機的目錄設定在了
/usr/share/nginx/www
啟動服務:/etc/init.d/nginx start
,重啟服務:/etc/init.d/nginx restart
現在,我們需要進入到Nginx的配置中,改動配置檔案。vim /etc/nginx/site-avalidable/default
更改配置檔案後重啟服務/etc/init.d/nginx restart
,或者service nginx restart
Gunicorn
Gunicorn 綠色獨角獸 是一個Python WSGI UNIX的HTTP伺服器。這是一個pre-fork worker的模型,從Ruby的獨角獸(Unicorn )專案移植。該Gunicorn伺服器大致與各種Web框架相容,只需非常簡單的執行,輕量級的資源消耗,以及相當迅速。
此時需要在“準備”步驟中建立的測試目錄下放入我們的測試執行專案,我選擇的FTP工具是:xftp。我傳入了一個簡單的用來測試的Python檔案wsgi.py
,使用命令/var/www/myflask# vim wsgi.py
預覽。
此時在測試目錄下鍵入命令gunicorn -w 4 -b 127.0.0.1:8000 wsgi:app
執行。
此時,訪問伺服器,可以看到“Hello World”已經可以正常顯示了。
關於Flask
後端我們採用Python
的Flask
框架+Gunicorn
+Nginx
來快速搭建。首先需要一些Python
的基礎知識,相信大家在菜鳥學Python學了這麼久,這完全不是問題。現在,讓我們瞭解一下Flask
如何使用。
一位使用多種語言開發複雜程式並且擁有十多年經驗的軟體工程師,曾經用 PHP, Ruby, Smalltalk 甚至 C++ 寫過 web 應用,他認為,在所有這些中,Python/Flask 組合是最為自由的一種。
在使用了Flask
之後,我也不得不承認,它確實很便捷快速。當然也會有一定的缺點,這是後話。
迴歸正題。
獲得物件
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World'
if __name__ == '__main__':
app.run()
複製程式碼
這是一個最簡單的Demo。
執行流程為:從flask模組獲取物件app,通過路由,執行方法,返回內容。
此時在瀏覽器訪問(預設埠5000):127.0.0.1:5000/ ,可以看到國際慣例Helloworld的介面。
路由
-
唯一URL:
@app.route('/hello')
@app.route('/hello/')
#這兩種需要區分
#
@app.route('/hello/')
#在使用這種尾部帶斜線的url時,假如使用者沒有輸入尾部/,也將訪問到正確的頁面
#
@app.route('/hello')
#在使用這種尾部不帶斜線的url時,假如使用者在尾部輸入了/,將返回404
複製程式碼
這個規則似乎有點拗口,但其實也不能理解。優點是:
- 使得使用者在遺忘尾斜線時,允許關聯的 URL 接任工作,與 Apache 和其它的伺服器的行為並無二異
- 保證了 URL 的唯一,有助於避免搜尋引擎索引同一個頁面兩次。
如果實在記不清,最好的方法是破罐子破摔:統一不帶尾部“/”
-
構造URL中的動態部分
@app.route('/var/<name>')
def var(name):
return 'hello'+' '+name
複製程式碼
這點就不贅述了,可以看一下演示效果:
模板渲染
大部分時候,在使用者訪問了一個URL的時候,我們都需要給他/她返回一個介面,我們當然不會用Python本身去渲染HTML,為此,Flask 配備了Jinja2 模板引擎。
看完以下程式碼示例,相信你就能理解。
首先,我們建立“templates”資料夾用於儲存模板。
Flask 會在 templates 資料夾裡尋找模板。所以,如果你的應用是個模組,這個資料夾應該與模組同級;如果它是一個包,那麼這個資料夾作為包的子目錄:
#情況 1: 模組:
/application.py
/templates
/hello.html
#情況 2: 包:
/application
/__init__.py
/templates
/hello.html
複製程式碼
- 不含引數示例 在程式執行:
@app.route('/redi/')
def redi():
return render_template('hello.html')
複製程式碼
- 再看另一個例子,加入動態引數:
@app.route('/redi2/<name>')
def redi2(name):
return render_template('hello2.html',name=name)
複製程式碼
GET和POST
請求方式不止這個兩種,但是最常用的是這兩種,如果對這兩種不熟悉,可以先去查一下HTTP方法的資料,這裡只演示在flask中的用法。
@app.route('/met',methods=['GET','POST'])
def met():
if request.method=='GET':
return '這是get方法'
if request.method=='POST':
return '這是post方法'
複製程式碼
開啟Postman這款軟體(Web神器),模擬傳送HTTP請求。
請求物件
下面我來模擬一個簡單的登入操作。
首先是控制器:
@app.route('/login',methods=['POST','GET'])
def login():
error=None
if request.method=='POST':
print (request.form['username']+' '+request.form['password'])
if func.login_func.valid_login(request.form['username'],
request.form['password']):
return func.login_func.login_success(request.form['username'])
else:
error='Invalid username/password'
return render_template('login_error.html',error=error)
複製程式碼
可以看到執行流程:
- 獲得請求
- 判斷請求型別
- 獲得登陸資料
- valid_login()方法驗證登陸 4.1 若登陸成功,執行login_success()方法 4.2 若登入失敗,新增失敗資訊,返回失敗模板
下面是上述用到的兩個方法:
def valid_login(username,password):
if username=='admin' and password=='admin':
return True
複製程式碼
def login_success(username):
return render_template('login_success.html',username=username)
複製程式碼
下面使用Postman來模擬請求,看看能不能返回設想的結果。
這是一些簡單的Flask
操作,好了,我們現在對web有了一定的瞭解了。現在開始編寫我們的程式碼。
###後端 萬事俱備,只欠東風。 首先我們來寫兩個路由,一個用於更新通知,一個用於獲取通知。
@app.route("/updateToast",methods=['POST'])
@allow_cross_domain
def update_toast():
data=db_util.update_toast(request.form['toastUpdateInfo'])
return jsonify(data)
@app.route("/getToast",methods=['GET'])
@allow_cross_domain
def get_toast():
data = db_util.get_toast_info()
return jsonify(data)
複製程式碼
然後寫一個工具類,用於直接運算元據庫(這種設計並不規範,只是為了快速演示)
#db_util.py
def get_toast_info():
db = pymysql.Connect(
host='xxx',
port=3306,
user='xxx',
passwd='xxx',
db='xxx',
charset='utf8'
)
cursor = db.cursor()
sql = "select content from guohe_lite_toast order by id desc limit 1 "
try:
cursor.execute(sql)
result = cursor.fetchone()
return response_info.success('小程式通知查詢成功', result)
except:
return response_info.error('2', '小程式通知查詢失敗', result)
# 關閉資料庫連線
finally:
db.close()
def update_toast(toast_update_info):
db = pymysql.Connect(
host='xxx',
port=3306,
user='xxxx',
passwd='xxx',
db='xxxx',
charset='utf8'
)
cursor = db.cursor()
sql = "insert into guohe_lite_toast(content,update_time) values(%s,%s) "
try:
dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
cursor.execute(sql,(toast_update_info,dt))
db.commit()
return response_info.success('通知更新成功', toast_update_info)
except:
db.rollback()
return response_info.error("2",'更新失敗', toast_update_info)
finally:
db.close()
複製程式碼
現在,讓我們使用postman來測試一下介面。
首先更新一下通知:
然後看能不能成功獲取:
這一切都生效了,資料介面已經準備就緒。
資料渲染
那麼,現在如何在小程式端獲取資料並顯示呢?我們去簡要讀下小程式的官方文件。
請注意,小程式是純非同步方式來傳送請求的。
依葫蘆畫瓢,我們來模仿一下:
#index.js
wx.request({
url: 'https://example.com/getToast',
method: 'GET',
header: {
'content-type': 'application/x-www-form-urlencoded' // 預設值
},
success: function (res) {
var message = res.data.info[0]
console.log(message)
that.setData({
toast: message
})
}
})
複製程式碼
我們將獲取的資料已經儲存在"toast"這個變數中了,再去讀文件,看看小程式是如何進行資料繫結的。然後我們將之前寫死的文字換成"{{toast}}",這時再重新整理,可以看到,資料已經顯示了。
<!-- index.wxml -->
<view class="main-card">
<view class="main-card-item" id="toast" wx:if="{{news_flag}}">
<view class="card-item-hd" >
<image class="card-item-icon" src="/images/index/toast.png"></image>
<text class="card-item-name">大事兒</text>
</view>
<view class='toast'>
{{toast}}
</view>
</view>
</view>
複製程式碼
此時,一套完整的流程已經結束,雖然實現了一個微小的功能,但麻雀雖小,五臟俱全。接下來,就是去進一步學習,去如何改造以及豐富我們的專案了。
比如稍微努力一下: