1.Flask-JSONRPC簡介
1.什麼是Flask-JSONRPC??
所謂的RPC,Remote Procedure Call
的簡寫,中文譯作遠端過程呼叫或者遠端服務呼叫。
直觀的理解就是,通過網路請求遠端服務,獲取指定介面的資料,而不用知曉底層網路協議的細節。
RPC
支援的格式很多,比如XML
格式,JSON
格式等等。最常用的肯定是json-rpc。
-----------------------------------------------------
git地址:https://github.com/cenobites/flask-jsonrpc
文件:http://wiki.geekdream.com/Specification/json-rpc_2.0.html
2.Flask-JSONRPC的實現原理
客戶端請求服務端完成某一個服務行為,所以RPC規範要求: 客戶端傳送的所有請求都是POST請求!!!
所有的傳輸資料都是單個物件,用JSON格式進行序列化。
3.Flask-JSONRPC的請求和響應
請求要求包含三個特定屬性:
jsonrpc: 用來宣告JSON-RPC協議的版本,現在基本固定為“2.0” method,方法,是等待呼叫的遠端方法名,字串型別 params,引數,物件型別或者是陣列,向遠端方法傳遞的多個引數值 id,任意型別值,用於和最後的響應進行匹配,也就是這裡設定多少,後面響應裡這個值也設定為相同的 響應的接收者必須能夠給出所有請求以正確的響應。這個值一般不能為Null,且為數字時不能有小數。
響應也有三個屬性:
jsonrpc, 用來宣告JSON-RPC協議的版本,現在基本固定為“2.0” result,結果,是方法的返回值,呼叫方法出現錯誤時,必須不包含該成員。 error,錯誤,當出現錯誤時,返回一個特定的錯誤編碼,如果沒有錯誤產生,必須不包含該成員。 id,就是請求帶的那個id值,必須與請求物件中的id成員的值相同。請求物件中的id時發生錯誤(如:轉換錯誤或無效的請求),它必須為Null
當然,有一些場景下,是不用返回值的,比如只對客戶端進行通知,由於不用對請求的id進行匹配,所以這個id就是不必要的,置空或者直接不要了。
2.
pip install Flask-JSONRPC==0.3.1
3.快速實現一個測試的RPC介面
1.初始化jsonRPC
import os,logging from flask_jsonrpc import JSONRPC # 初始化jsonrpc模組 jsonrpc = JSONRPC(service_url='/api') def init_app(config_path): """全域性初始化""" # 初始化json-rpc jsonrpc.init_app(app)
2.編寫介面程式碼
# 實現rpc介面 from application import jsonrpc @jsonrpc.method(name="Home.index") def index(): return "hello world!"
3.當然,我們可以通過postman發起post請求:
請求地址:http://127.0.0.1:5000/api 請求體: { "jsonrpc":"2.0", "method":"Home.index", "params":{}, "id":"1" }
3.基於api介面接受來自客戶端的引數
1.postman向http://127.0.0.1:5000/api傳送POST請求
請求體內容:
請求地址:http://127.0.0.1:5000/api 請求體: { "jsonrpc":"2.0", "method":"Home.index", "params":{"id":"abc"}, "id":"1" }
2.後端介面程式碼
from application import jsonrpc @jsonrpc.method(name="Home.index") def index(id): return "hello world!id=%s" % id
3.響應結果
響應內容: { "id":1, "jsonrpc":"2.0", "result":"hello world!id=abc" }
4.移動端訪問測試介面
因為當前我們的服務端專案安裝在虛擬機器裡面,並且我們設定了虛擬機器的網路連線模式為NAT,所以一般情況下,我們無法直接通過手機訪問虛擬機器。因此,我們需要配置一下。
1.開啟VM的“編輯“選單,選中虛擬網路編輯器。
2.開啟編輯器視窗,使用管理員許可權,並點選“NAT設定”。
3.填寫閘道器IP地址,必須和子網IP在同一網段。末尾一般為1。接著在埠轉發下方點選“新增”。
4.在對映傳入埠中,填寫轉發的埠和實際虛擬機器的IP埠,填寫完成以後,全部點選“確定”,關閉所有視窗。將來,手機端訪問PC主機的8083埠就自動訪問到虛擬機器。8083是自定義的,可以是其他埠。
5.此時在手機上訪問你windows電腦本機IP+埠/api/browse即可成功訪問到測試介面
2.客戶端展示介面
1.首頁/登入頁面/註冊頁面初始化介面
<!DOCTYPE html> <html lang="en"> <head> <title>首頁</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta name="format-detection" content="telephone=no,email=no,date=no,address=no"> <link rel="stylesheet" href="../static/css/main.css"> <script src="../static/js/vue.js"></script> <script src="../static/js/main.js"></script> </head> <body> <div class="app" id="app"> <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png"> <div class="bg"> <img src="../static/images/bg0.jpg"> </div> <ul> <li><img class="module1" src="../static/images/image1.png"></li> <li><img class="module2" src="../static/images/image2.png"></li> <li><img class="module3" src="../static/images/image3.png"></li> <li><img class="module4" src="../static/images/image4.png"></li> </ul> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); // 允許ajax傳送請求時附帶cookie,設定為不允許 Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { music_play:true, // 預設播放背景音樂 prev:{name:"",url:"",params:{}}, // 上一頁狀態 current:{name:"index",url:"index.html","params":{}}, // 下一頁狀態 } }, watch:{ music_play(){ if(this.music_play){ this.game.play_music("../static/mp3/bg1.mp3"); }else{ this.game.stop_music(); } } }, methods:{ } }) } </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>登入</title> <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta charset="utf-8"> <link rel="stylesheet" href="../static/css/main.css"> <script src="../static/js/vue.js"></script> <script src="../static/js/main.js"></script> </head> <body> <div class="app" id="app"> <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png"> <div class="bg"> <img src="../static/images/bg0.jpg"> </div> <div class="form"> <div class="form-title"> <img src="../static/images/login.png"> <img class="back" src="../static/images/back.png"> </div> <div class="form-data"> <div class="form-data-bg"> <img src="../static/images/bg1.png"> </div> <div class="form-item"> <label class="text">手機</label> <input type="text" name="mobile" placeholder="請輸入手機號"> </div> <div class="form-item"> <label class="text">密碼</label> <input type="password" name="password" placeholder="請輸入密碼"> </div> <div class="form-item"> <input type="checkbox" class="agree remember" name="agree" checked> <label><span class="agree_text ">記住密碼,下次免登入</span></label> </div> <div class="form-item"> <img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"> </div> <div class="form-item"> <p class="toreg">立即註冊</p> <p class="tofind">忘記密碼</p> </div> </div> </div> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); // 在 #app 標籤下渲染一個按鈕元件 Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { music_play:true, prev:{name:"",url:"",params:{}}, current:{name:"login",url:"login.html",params:{}}, } }, watch:{ music_play(){ if(this.music_play){ this.game.play_music("../static/mp3/bg1.mp3"); }else{ this.game.stop_music(); } } }, methods:{ } }) } </script> </body> </html>
<!DOCTYPE html> <html> <head> <title>註冊</title> <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"> <meta charset="utf-8"> <link rel="stylesheet" href="../static/css/main.css"> <script src="../static/js/vue.js"></script> <script src="../static/js/main.js"></script> </head> <body> <div class="app" id="app"> <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png"> <div class="bg"> <img src="../static/images/bg0.jpg"> </div> <div class="form"> <div class="form-title"> <img src="../static/images/register.png"> <img class="back" @click="backpage" src="../static/images/back.png"> </div> <div class="form-data"> <div class="form-data-bg"> <img src="../static/images/bg1.png"> </div> <div class="form-item"> <label class="text">手機</label> <input type="text" name="mobile" placeholder="請輸入手機號"> </div> <div class="form-item"> <label class="text">驗證碼</label> <input type="text" class="code" name="code" placeholder="請輸入驗證碼"> <img class="refresh" src="../static/images/refresh.png"> </div> <div class="form-item"> <label class="text">密碼</label> <input type="password" name="password" placeholder="請輸入密碼"> </div> <div class="form-item"> <label class="text">確認密碼</label> <input type="password" name="password2" placeholder="請再次輸入密碼"> </div> <div class="form-item"> <input type="checkbox" class="agree" name="agree" checked> <label><span class="agree_text">同意磨方《使用者協議》和《隱私協議》</span></label> </div> <div class="form-item"> <img class="commit" @click="game.play_music('../static/mp3/btn1.mp3')" src="../static/images/commit.png"/> </div> </div> </div> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { music_play:true, prev:{name:"",url:"",params:{}}, current:{name:"register",url:"register.html","params":{}}, } }, watch:{ music_play(){ if(this.music_play){ this.game.play_music("../static/mp3/bg1.mp3"); }else{ this.game.stop_music(); } } }, methods:{ backpage(){ this.prev.name = api.pageParam.name; this.prev.url = api.pageParam.url; this.prev.params = api.pageParam.params; this.game.back(this.prev); } } }) } </script> </body> </html>
3.在APP進行視窗和頁面操作
1.window 視窗
window是APICloud提供的最頂級的頁面單位.一個APP至少會存在一個以上的window視窗,在使用者開啟APP應用,應用在初始化的時候預設就會建立了一個name=root 的頂級window視窗顯示當前APP配置的首頁.
1.新建視窗
api.openWin({ name: 'page1', // 自定義視窗名稱 bounces: false, // 視窗是否上下拉動 reload: true, // 如果頁面已經在之前被開啟了,是否要重新載入當前視窗中的頁面 url: './page1.html', // 視窗建立時展示的html頁面的本地路徑[相對於當前程式碼所在檔案的路徑] animation:{ // 開啟新建視窗時的過渡動畫效果 type:"none", //動畫型別(詳見動畫型別常量) subType:"from_right", //動畫子型別(詳見動畫子型別常量) duration:300 //動畫過渡時間,預設300毫秒 }, pageParam: { // 傳遞給下一個視窗使用的引數.將來可以在新視窗中通過 api.pageParam.name 獲取 name: 'test' // name只是舉例, 將來可以傳遞更多自定義資料的. } });
2.在客戶端APP的main.js(主程式指令碼)中,新增一個新建視窗的方法
main.js
class Game{ ...... goWin(name,url,pageParam){ api.openWin({ name: name, // 自定義視窗名稱 bounces: false, // 視窗是否上下拉動 reload: true, // 如果頁面已經在之前被開啟了,是否要重新載入當前視窗中的頁面 url: url, // 視窗建立時展示的html頁面的本地路徑[相對於當前程式碼所在檔案的路徑] animation:{ // 開啟新建視窗時的過渡動畫效果 type: "push", //動畫型別(詳見動畫型別常量) subType: "from_right", //動畫子型別(詳見動畫子型別常量) duration:300 //動畫過渡時間,預設300毫秒 }, pageParam: pageParam // 傳遞給下一個視窗使用的引數.將來可以在新視窗中通過 api.pageParam.name 獲取 }); } ...... }
3.在登入頁面中,使用者點選立即註冊,會去到一個新的視窗
html/index.html
<div class="form-item"> <p class="toreg" @click="goto_register">立即註冊</p> <p class="tofind">忘記密碼</p> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"login",url:"login.html",params:{}}, } }, methods:{ goto_register(){ this.game.goWin("register","./register.html", this.current); } } }) } </script>
-------------------------------------------------------
4.關閉視窗
//關閉當前window,使用預設動畫 api.closeWin(); //關閉指定window,若待關閉的window不在最上面,則無動畫 api.closeWin({ name: 'page1' });
Tip:如果當前APP中只有剩下一個頂級視窗root,則無法通過當前方法關閉! 也有部分手機直接退出APP了
5.接下來我們可以把關閉視窗的程式碼封裝到主程指令碼main.js中
main.js
class Game{ ...... outWin(name){ // 關閉視窗 api.closeWin(name); } ...... }
6.在註冊頁面點選返回鍵呼叫關閉視窗的方法
html/register.html
<div class="form-title"> <img src="../static/images/register.png"> <img class="back" @click="back" src="../static/images/back.png"> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"register",url:"register.html","params":{}}, } }, methods:{ back(){ this.game.outWin(); } } }) } </script>
2.frame 幀頁面
幀相對於視窗的優點以及使用幀頁面時需要注意的點:
如果APP中所有的頁面全部視窗進行展開,則APP需要耗費大量的記憶體來維護這個視窗列表,從而導致, 使用者操作APP時,APP響應緩慢甚至卡頓的現象.所以APP中除了通過新建視窗的方式展開頁面以外, 還提供了幀的方式來展開頁面.
幀,代表的就是一個視窗下開打的某個頁面記錄.所謂的幀就有點類似於瀏覽器中視窗通過位址列新建的一個頁面一樣.
使用的時候注意:
1. APP每一個window視窗都可以開啟1到多個幀.新建視窗的時候,系統會預設順便建立第一幀出來.
2. 每一幀代表的都是一個html頁面,
3. 預設情況下, APP的window的視窗會自動預設滿屏展示.而幀可以設定矩形的寬高.如果頂層的幀頁面沒有滿屏顯示,則使用者可以看到當前這一幀下的其他幀的內容.
1.新建幀頁面
api.openFrame({ name: 'page2', // 幀頁面的名稱 url: './page2.html', // 幀頁面開啟的url地址 data: '', // 可選引數,如果填寫了data,則不要使用url, data表示頁面資料,可以是html程式碼 bounces:false, // 頁面是否可以下拉拖動 reload: true, // 幀頁面如果已經存在,是否重新重新整理載入 useWKWebView:true, historyGestureEnabled:true, animation:{ type:"push", //動畫型別(詳見動畫型別常量) subType:"from_right", //動畫子型別(詳見動畫子型別常量) duration:300 //動畫過渡時間,預設300毫秒 }, rect: { // 當前幀的寬高範圍 // 方式1,設定矩形大小寬高 x: 0, // 左上角x軸座標 y: 0, // 左上角y軸座標 w: 'auto', // 當前幀頁面的寬度, auto表示滿屏 h: 'auto' // 當前幀頁面的高度, auto表示滿屏 // 方式2,設定矩形大小寬高 marginLeft:, //相對父頁面左外邊距的距離,數字型別 marginTop:, //相對父頁面上外邊距的距離,數字型別 marginBottom:, //相對父頁面下外邊距的距離,數字型別 marginRight: //相對父頁面右外邊距的距離,數字型別 }, pageParam: { // 要傳遞新建幀頁面的引數,在新頁面可通過 api.pageParam.name 獲取 name: 'test' // name只是舉例, 可以傳遞任意自定義引數 } });
2.關閉幀頁面
// 關閉當前 frame頁面 api.closeFrame(); // 關閉指定名稱的frame頁面 api.closeFrame({ name: 'page2' });
3.在主程指令碼main.js中, 建立一個方法專門建立frame和刪除frame頁面
main.js
class Game{ goFrame(name,url,pageParam,rect=null){ // 建立幀頁面 if(rect === null){ rect = { // 方式1,設定矩形大小寬高 x: 0, // 左上角x軸座標 y: 0, // 左上角y軸座標 w: 'auto', // 當前幀頁面的寬度, auto表示滿屏 h: 'auto' // 當前幀頁面的高度, auto表示滿屏 } } api.openFrame({ name: name, // 幀頁面的名稱 url: url, // 幀頁面開啟的url地址 bounces:false, // 頁面是否可以下拉拖動 reload: true, // 幀頁面如果已經存在,是否重新重新整理載入 useWKWebView: true, historyGestureEnabled:true, animation:{ type:"push", //動畫型別(詳見動畫型別常量) subType:"from_right", //動畫子型別(詳見動畫子型別常量) duration:300 //動畫過渡時間,預設300毫秒 }, rect: rect, // 當前幀的寬高範圍 pageParam: pageParam, // 要傳遞新建幀頁面的引數,在新頁面可通過 api.pageParam.name 獲取 }); } outFrame(name){ // 關閉幀頁面 api.closeFrame({ name: name, }); } }
4.在登入頁面使用新建幀頁面
登入頁面,點選立即註冊跳轉到註冊頁面
<div class="form-item"> <p class="toreg" @click="goto_register">立即註冊</p> <p class="tofind">忘記密碼</p> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"login",url:"login.html",params:{}}, } }, methods:{ goto_register(){ this.game.goFrame("register","./register.html", this.current); } } }) } </script>
5.在註冊頁面使用關閉幀頁面
註冊頁面,點選返回關閉頁面
<div class="form-title"> <img src="../static/images/register.png"> <img class="back" @click="back" src="../static/images/back.png"> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"register",url:"register.html","params":{}}, } }, methods:{ back(){ this.game.outFrame(); } } }) } </script>
3.framegroup 幀頁面組
1.新建幀頁面組
api.openFrameGroup({ name: 'group1', // 組名 rect: { // 幀頁面組的顯示矩形範圍 // 方式1: x:, //左上角x座標,數字型別 y:, //左上角y座標,數字型別 w:, //寬度,若傳'auto',頁面從x位置開始自動充滿父頁面寬度,數字或固定值'auto' h:, //高度,若傳'auto',頁面從y位置開始自動充滿父頁面高度,數字或固定值'auto' // 方式2: marginLeft:, //相對父頁面左外邊距的距離,數字型別 marginTop:, //相對父頁面上外邊距的距離,數字型別 marginBottom:, //相對父頁面下外邊距的距離,數字型別 marginRight: //相對父頁面右外邊距的距離,數字型別 }, frames: [{ name:'', //frame名字,字串型別,不能為空字串 url:'', // 頁面地址 useWKWebView:true, historyGestureEnabled:false, //(可選項)是否可以通過手勢來進行歷史記錄前進後退。 pageParam:{}, // 頁面引數 bounces:true, // 是否能下拉拖動 }, { name:'', //frame名字,字串型別,不能為空字串 url:'', // 頁面地址 useWKWebView:true, historyGestureEnabled:false, //(可選項)是否可以通過手勢來進行歷史記錄前進後退。 pageParam:{}, // 頁面引數 bounces:true, // 是否能下拉拖動 },{ ... },... ] }, function(ret, err) { // 當前幀頁面發生頁面顯示變化時,當前幀的索引. var index = ret.index; });
2.關閉幀頁面組
api.closeFrameGroup({ name: 'group1' // 組名 });
api.setFrameGroupIndex({ name: 'group1', // 組名 index: 2 // 索引,從0開始 });
4.將開啟幀頁面組/關閉幀頁面組/切換幀頁面封裝到main.js中
class Game{ ...... openGroup(name,frames,preload=1,rect=null){ // 建立frame組 if(rect === null){ rect = { // 幀頁面組的顯示矩形範圍 x:0, //左上角x座標,數字型別 y:0, //左上角y座標,數字型別 w:'auto', //寬度,若傳'auto',頁面從x位置開始自動充滿父頁面寬度,數字或固定值'auto' h:'auto', //高度,若傳'auto',頁面從y位置開始自動充滿父頁面高度,數字或固定值'auto' }; } api.openFrameGroup({ name: name, // 組名 scrollEnabled: false, // 頁面組是否可以左右滾動 index: 0, // 預設顯示頁面的索引 rect: rect, // 頁面寬高範圍 preload: preload, // 預設預載入的頁面數量 frames: frames, // 幀頁面組的幀頁面成員 }, (ret, err)=>{ // 當前幀頁面發生頁面顯示變化時,當前幀的索引. this.groupindex = ret.index; }); } outGroup(name){ // 關閉 frame組 api.closeFrameGroup({ name: name // 組名 }); } goGroup(name,index){ // 切換顯示frame組下某一個幀頁面 api.setFrameGroupIndex({ name: name, // 組名 index: index // 索引,從0開始 }); } }
5.在index.html/login.html/register.html頁面使用幀頁面組
<ul> <li><img class="module1" src="../static/images/image1.png"></li> <li><img class="module2" @click="gohome" src="../static/images/image2.png"></li> <li><img class="module3" src="../static/images/image3.png"></li> <li><img class="module4" src="../static/images/image4.png"></li> </ul> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); // 允許ajax傳送請求時附帶cookie,設定為不允許 Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, // 上一頁狀態 current:{name:"index",url:"index.html","params":{}}, // 下一頁狀態 } }, methods:{ gohome(){ frames = [{ name: 'login', url: './login.html', },{ name: 'register', url: './register.html', }] this.game.openGroup("user",frames,frames.length); } } }) } </script> </body> </html>
<div class="form-item"> <p class="toreg" @click="goto_register">立即註冊</p> <p class="tofind">忘記密碼</p> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); // 在 #app 標籤下渲染一個按鈕元件 Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { prev:{name:"",url:"",params:{}}, current:{name:"login",url:"login.html",params:{}}, } }, created(){ }, methods:{ goto_register(){ // this.game.goWin("register","./register.html", this.current); // this.game.goFrame("register","./register.html", this.current); this.game.goGroup("user",1); }, } }) } </script>
<div class="form-title"> <img src="../static/images/register.png"> <img class="back" @click="back" src="../static/images/back.png"> </div> <script> apiready = function(){ var game = new Game("../static/mp3/bg1.mp3"); Vue.prototype.game = game; new Vue({ el:"#app", data(){ return { music_play:true, prev:{name:"",url:"",params:{}}, current:{name:"register",url:"register.html","params":{}}, } }, methods:{ back(){ // this.game.outWin(); // this.game.outFrame(); this.game.goGroup("user",0); } } }) } </script>