day99:MoFang:Flask-JSONRPC提供RPC接口&在APP进行窗口页面操作(窗口-帧-帧组)
目录
1.服务端基于Flask-JSONRPC提供RPC接口
1.Flask-JSONRPC简介
1.什么是Flask-JSONRPC??
JSON-RPC是一个无状态的、轻量级的远程过程调用(RPC)协议。
所谓的RPC,Remote Procedure Call
的简写,中文译作远程过程调用或者远程服务调用。
直观的理解就是,通过网络请求远程服务,获取指定接口的数据,而不用知晓底层网络协议的细节。
RPC
支持的格式很多,比如XML
格式,JSON
格式等等。最常用的肯定是json-rpc。
-----------------------------------------------------
在flask中要实现提供json-rpc接口,开发中一般使用Flask JSON-RPC模块来实现。
git地址:https://github.com/cenobites/flask-jsonrpc
文档:http://wiki.geekdream.com/Specification/json-rpc_2.0.html
2.Flask-JSONRPC的实现原理
JSON-RPC协议中的客户端一般是为了向远程服务器请求执行某个方法/函数。客户端向实现了JSON-RPC协议的服务端发送请求,多个输入参数能够通过数组或者对象传递到远程方法,这个远程方法也能返回多个输出数据,具体是什么,当然要看具体的方法实现。因为RPC可以通俗理解为:
客户端请求服务端完成某一个服务行为,所以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.安装Flask-JSONRPC模块
pip install Flask-JSONRPC==0.3.1
3.快速实现一个测试的RPC接口
1.初始化jsonRPC
例如,我们直接在application/__init__.py
项目初始化文件中进行初始化jsonrpc并关闭csrf防范机制
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.编写接口代码
application/apps/home/views.py
,编写接口代码
# 实现rpc接口
from application import jsonrpc
@jsonrpc.method(name="Home.index")
def index():
return "hello world!"
1.客户端需要发起post请求,访问地址为:http://服务器地址:端口/api
2.注意:默认情况下,/api
接口只能通过post请求访问。如果要使用jsonrpc提供的界面调试工具,则访问地址为:http://服务器地址端口/api/browse/
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>
html/index.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>
html/login.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>
html/register.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' // 组名
});
3.切换当前帧页面组显示的帧页面
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>
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");
// 在 #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>
html/login.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 {
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>
html/register.html
day99:MoFang:Flask-JSONRPC提供RPC接口&在APP进行窗口页面操作(窗口-帧-帧组)的更多相关文章
- 转载-- http接口、api接口、RPC接口、RMI、webservice、Restful等概念
http接口.api接口.RPC接口.RMI.webservice.Restful等概念 收藏 Linux一叶 https://my.oschina.net/heavenly/blog/499661 ...
- 程序员的自我救赎---11.1:RPC接口使用规范
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- rpc接口调用以太坊智能合约
rpc接口调用以太坊智能合约 传送门: 柏链项目学院 在以太坊摸爬滚打有些日子了,也遇到了各种各样的问题.这几天主要研究了一下如何通过rpc接口编译.部署和调用合约.也遇到了一些困难和问题,下面将 ...
- python调用RPC接口
要调用RPC接口,python提供了一个框架grpc,这是google开源的 rpc相关文档: https://grpc.io/docs/tutorials/basic/python.html 需要安 ...
- Python一秒提供Rest接口
Python一秒提供Rest接口 使用的是Anaconda安装的Python环境; 新建py文件(例如:restapi.py) # -*- coding: utf-8 -*- from flask i ...
- Spring Boot提供RESTful接口时的错误处理实践
使用Spring Boot开发微服务的过程中,我们会使用别人提供的接口,也会设计接口给别人使用,这时候微服务应用之间的协作就需要有一定的规范. 基于rpc协议,我们一般有两种思路:(1)提供服务的应用 ...
- RPC 接口必须是业务职责
https://mp.weixin.qq.com/s/MYSF8lCF92ItG_Lc8nOspg 一个加班多新人多团队,我们的代码问题与重构 陈于喆 高可用架构 2020-10-21 微服务编码 ...
- 亿级用户下的新浪微博平台架构 前端机(提供 API 接口服务),队列机(处理上行业务逻辑,主要是数据写入),存储(mc、mysql、mcq、redis 、HBase等)
https://mp.weixin.qq.com/s/f319mm6QsetwxntvSXpKxg 亿级用户下的新浪微博平台架构 炼数成金前沿推荐 2014-12-04 序言 新浪微博在2014年3月 ...
- docker&flask快速构建服务接口(二)
系列其他内容 docker快速创建轻量级的可移植的容器✓ docker&flask快速构建服务接口✓ docker&uwsgi高性能WSGI服务器生产部署必备 docker&g ...
随机推荐
- CodeForces 1409E Two Platforms
题意 有 \(n\) 个点,分别位于 \((x_i,y_i)\),求最多能用两个长度为 \(k\) 的平台接住多少个点. \(\texttt{Data Range:}n\leq 2\times 10^ ...
- 【轻松学编程】如何快速学会一门高级编程语言,以python为例
python文章目录 关注公众号"轻松学编程"了解更多. 写在前面:如何快速(比如在一个月内)学会一门高级编程语言? 现在想学一门编程语言并不难,网上有很多资料,包括书籍.博客.视 ...
- [Luogu P4178]Tree (点分治+splay)
题面 传送门:https://www.luogu.org/problemnew/show/P4178 Solution 首先,长成这样的题目一定是淀粉质跑不掉了. 考虑到我们不知道K的大小,我们可以开 ...
- struct.pack()和struct.unpack() 详解(转载)
原文链接:https://blog.csdn.net/weiwangchao_/article/details/80395941 python 中的struct主要是用来处理C结构数据的,读入时先转换 ...
- VC获取文件后缀名
VC获取文件后缀名 2011-07-28 10:30:50| 分类: Visual C++ and O | 标签: |举报 |字号大中小 订阅 1. CString GetSuffix(C ...
- day87:luffy:结算页面积分&支付宝接口
目录 1.积分 2.支付 1.积分 1.关于积分的表结构 1.在user表中添加credit字段 + 设计一个积分的表结构 user/models.py class User(AbstractUser ...
- jquery实现回车键执行ajax
$('#txtKey').bind('keypress',function(event){ if(event.keyCode == "13") { alert(1) }});
- 基于tensorflow的bilstm_crf的命名实体识别(数据集是msra命名实体识别数据集)
github地址:https://github.com/taishan1994/tensorflow-bilstm-crf 1.熟悉数据 msra数据集总共有三个文件: train.txt:部分数据 ...
- linux命令使用 cut/sort/uniq
我记得之前去XX网面试的那个面试题是这样的:有个apache.log 文件文本内容如下:======================[niewj@centSvr ~]$ cat apache.log ...
- Docker安装Oracle11g
为什么使用docker安装oracle,因为自己搭建配置的话可能时间太久太繁琐等等原因,也因为docker实在太方便了 本文主要是使用docker-compose安装Oracle 11g,因为使用do ...