Python实战案例:tornado接口vue客户端的堂食点餐系统(中)
用Vue+ElementUI技术完成的堂食点餐系统的界面已经搭建成功,但这里面有一个问题,当用户点击其中某一个商品的时候,对应的商品和金额就要去更新底部购物车信息区。这就需要vue的methods方法的设定
一、Vue实现购买商品信息的更新
在vue中,函数被定义成为方法来使用,这些方法定义在methods属性中,然后就可以在vue 表达式中调用函数。
vue 选项对象中有一个叫methods的属性.这个属性里面专门来存放一些函数,用来给别人调用。这个methods属性也是写在script中,与data()并驾齐趋的。格式如下。
export default{
data(){
return{
}
},
methods:{
方法名()
}
}
methods这个方法发生的条件是餐饮列表中“添加”这一项能够被选中。在目前的布局中,是没有添加一个商品的按钮的,我们把布局文件中按钮用ElementUI中的“+”号图标来表示添加按钮,这样就需要把布局做一下修改,同时把“+”按钮对应的动作设定为methods中的方法,在这个方法里暂时实现添加一个商品的数量,每点击一次添加一个商品,在data()中定义初始化的商品数量,在底部购物车信息中显示具体的商品数量。代码如下。
<template>
<div >
<el-header >
绝妙订餐系统
</el-header>
<el-row>
<el-col :span="4">
<el-menu
background-color="darkgreen"
text-color="#fff"
>
<el-menu-item>
<template>
<i class="el-icon-s-platform"></i>
<span>厨师推荐</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-s-tools"></i>
<span>精品凉菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-user-solid"></i>
<span>特色小炒</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>家常热菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>营养主食</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>调味饮料</span>
</template>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="20">
<el-table :data="foodlist" height="330">
<el-table-column label="菜名" prop="name"></el-table-column>
<el-table-column label="菜品图片" prop="img">
<template slot-scope="scope">
<img :src="scope.row.img" width="170" height="124"/>
</template>
</el-table-column>
<el-table-column label="菜品价格" prop="price"></el-table-column>
<el-table-column label="操作" >
<i class="el-icon-circle-plus" @click="plus_goods()"></i>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<div class="bg-green-dark">
<i class="el-icon-cart"></i>
</div>
</el-col>
<el-col :span="18">
<div class="bg-green-dark">
商品{{num_food}}件,0元
</div>
</el-col>
<el-col :span="4">
<div class="bg-red-dark">
去结算
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import "../assets/logo.png"
import axios from "axios"
export default {
name: 'OrderFood',
data(){
return {
num_food:0,
/*定义全局booklist*/
foodlist:[{
"name":"野蘑菇炖鸡",
"img":"./static/images/mogu_ji.png",
"price":58.00
},{
"name":"大盘鸡",
"img":"./static/images/dapan_ji.png",
"price":62.00
},
{
"name":"爆椒鸡",
"img":"./static/images/baojiao_ji.png",
"price":48.00
},{
"name":"辣子鸡丁",
"img":"./static/images/lazi_ji.png",
"price":39.00
},
{
"name":"麻辣鱼头",
"img":"./static/images/mala_yu.png",
"price":52.00
},{
"name":"酸菜鱼",
"img":"./static/images/lazi_ji.png",
"price":53.00
},
{
"name":"红烧带鱼",
"img":"./static/images/mala_yu.png",
"price":42.00
},{
"name":"水煮肉片",
"img":"./static/images/dapan_ji.png",
"price":35.00
},
{
"name":"毛血旺",
"img":"./static/images/baojiao_ji.png",
"price":30.00
},{
"name":"鱼香肉丝",
"img":"./static/images/lazi_ji.png",
"price":18.00
},
{
"name":"烧大肠",
"img":"./static/images/mala_yu.png",
"price":28.00
},{
"name":"农家小炒肉",
"img":"./static/images/lazi_ji.png",
"price":25.00
},
{
"name":"青椒肉丝",
"img":"./static/images/mala_yu.png",
"price":20.00
},{
"name":"京酱肉丝",
"img":"./static/images/mala_yu.png",
"price":19.00
},{
"name":"红烧肉(带饼)",
"img":"./static/images/lazi_ji.png",
"price":30.00
},
{
"name":"回锅肉",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"木须肉",
"img":"./static/images/mala_yu.png",
"price":18.00
},{
"name":"酸豆角炒肉末",
"img":"./static/images/lazi_ji.png",
"price":22.00
},
{
"name":"鲜辣烤鱼",
"img":"./static/images/mala_yu.png",
"price":79.00
},{
"name":"干锅菜花",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"干锅千页豆腐",
"img":"./static/images/lazi_ji.png",
"price":15.00
},
{
"name":"西红柿烧牛楠",
"img":"./static/images/mala_yu.png",
"price":29.00
},{
"name":"绿豆牙炒馓子",
"img":"./static/images/mala_yu.png",
"price":13.00
},{
"name":"西红柿炒鸡蛋",
"img":"./static/images/lazi_ji.png",
"price":12.00
},
{
"name":"酸辣土豆丝",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"香菇青菜",
"img":"./static/images/mala_yu.png",
"price":17.00
},{
"name":"麻辣豆腐",
"img":"./static/images/lazi_ji.png",
"price":10.00
},
{
"name":"鱼香茄子",
"img":"./static/images/mala_yu.png",
"price":15.00
},{
"name":"爆炒花哈",
"img":"./static/images/mala_yu.png",
"price":23.00
},{
"name":"豆芽粉条",
"img":"./static/images/lazi_ji.png",
"price":11.00
},
{
"name":"红油耳丝",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"变蛋黄豆",
"img":"./static/images/mala_yu.png",
"price":9.00
},{
"name":"麻辣花生米",
"img":"./static/images/lazi_ji.png",
"price":9.00
},
{
"name":"水蒸蛋",
"img":"./static/images/mala_yu.png",
"price":10.00
},{
"name":"烧茄子",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"炒饼丝",
"img":"./static/images/lazi_ji.png",
"price":8.00
}]
}
},
methods:{
plus_goods(){
this.num_food++;
}
}
}
</script>
<style scoped>
.el-header{
background-color:darkgreen;
color:white;
line-height:60px;
font-size:20px;
}
.el-menu-item:hover{
background:yellow!important;
color:purple!important;
}
.el-menu-item.is-active{
background:purple!important;
color:white!important;
}
.el-icon-cart{
width:60px;
height:60px;
background: url("../assets/image/cart.png") center no-repeat;
background-size:100% 100%;
margin-top:-10px;
}
.el-icon-cart:before{
visibility: hidden;
}
.bg-green-dark{
background-color:black;
height:60px;
line-height:60px;
color:white;
}
.bg-red-dark{
background-color:darkred;
color:white;
height:60px;
line-height:60px;
}
</style>
从代码上看,在右侧商品列表区的<el-table>又多出了<el-table-column>,在这个<el-table-column>中加入了一个i标签,类别是el-icon-circle-plus,并且在这个i标签上添加@click属性,对应methods中的方法plusgoods函数,函数体中的语句使用this.numfoods+=1实现商品数量的添加。效果图如下图所示。
从图中效果可以看到,当点击点餐区中的“+”号按钮时,底部购物车状态信息中的商品就会一件一件的添加。接下来要实现的就是商品总价格的更新。这需要寻找“+”号前面的“菜品价格”维度中的值,对于表格中调用每一行中的数值可以使用scope来实现,这就需要模板template中使用属性slot-scope来使模板内部的元素对scope的使用。这样可以在调用methods里面方法的时候传入scope的数据。当把scope数据传送进plusgoods函数中时,函数接收对应的参数,并把scope中的$index索引获取出来,就可以取出对应索引的“菜品价格”数据,每点击一次“+”号添加按钮,就添加一个商品,对应的“商品价格”数据就会累加到商品总价格中。data()数据初始化方法中也需要定义商品求和的初值为0,在plusgoods中实现商品和值的累加。代码如下。
<template>
<div >
<el-header >
绝妙订餐系统
</el-header>
<el-row>
<el-col :span="4">
<el-menu
background-color="darkgreen"
text-color="#fff"
>
<el-menu-item>
<template>
<i class="el-icon-s-platform"></i>
<span>厨师推荐</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-s-tools"></i>
<span>精品凉菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-user-solid"></i>
<span>特色小炒</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>家常热菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>营养主食</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>调味饮料</span>
</template>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="20">
<el-table :data="foodlist" height="330">
<el-table-column label="菜名" prop="name"></el-table-column>
<el-table-column label="菜品图片" prop="img">
<template slot-scope="scope">
<img :src="scope.row.img" width="170" height="124"/>
</template>
</el-table-column>
<el-table-column label="菜品价格" prop="price"></el-table-column>
<el-table-column label="操作" >
<template slot-scope="scope" >
<i class="el-icon-circle-plus" @click="plus_goods({scope})"></i>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<div class="bg-green-dark">
<i class="el-icon-cart"></i>
</div>
</el-col>
<el-col :span="18">
<div class="bg-green-dark">
商品{{num_food}}件,{{num_price}}元
</div>
</el-col>
<el-col :span="4">
<div class="bg-red-dark">
去结算
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import "../assets/logo.png"
import axios from "axios"
export default {
name: 'OrderFood',
data(){
return {
num_food:0,
num_price:0,
/*定义全局booklist*/
foodlist:[{
"name":"野蘑菇炖鸡",
"img":"./static/images/mogu_ji.png",
"price":58.00
},{
"name":"大盘鸡",
"img":"./static/images/dapan_ji.png",
"price":62.00
},
{
"name":"爆椒鸡",
"img":"./static/images/baojiao_ji.png",
"price":48.00
},{
"name":"辣子鸡丁",
"img":"./static/images/lazi_ji.png",
"price":39.00
},
{
"name":"麻辣鱼头",
"img":"./static/images/mala_yu.png",
"price":52.00
},{
"name":"酸菜鱼",
"img":"./static/images/lazi_ji.png",
"price":53.00
},
{
"name":"红烧带鱼",
"img":"./static/images/mala_yu.png",
"price":42.00
},{
"name":"水煮肉片",
"img":"./static/images/dapan_ji.png",
"price":35.00
},
{
"name":"毛血旺",
"img":"./static/images/baojiao_ji.png",
"price":30.00
},{
"name":"鱼香肉丝",
"img":"./static/images/lazi_ji.png",
"price":18.00
},
{
"name":"烧大肠",
"img":"./static/images/mala_yu.png",
"price":28.00
},{
"name":"农家小炒肉",
"img":"./static/images/lazi_ji.png",
"price":25.00
},
{
"name":"青椒肉丝",
"img":"./static/images/mala_yu.png",
"price":20.00
},{
"name":"京酱肉丝",
"img":"./static/images/mala_yu.png",
"price":19.00
},{
"name":"红烧肉(带饼)",
"img":"./static/images/lazi_ji.png",
"price":30.00
},
{
"name":"回锅肉",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"木须肉",
"img":"./static/images/mala_yu.png",
"price":18.00
},{
"name":"酸豆角炒肉末",
"img":"./static/images/lazi_ji.png",
"price":22.00
},
{
"name":"鲜辣烤鱼",
"img":"./static/images/mala_yu.png",
"price":79.00
},{
"name":"干锅菜花",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"干锅千页豆腐",
"img":"./static/images/lazi_ji.png",
"price":15.00
},
{
"name":"西红柿烧牛楠",
"img":"./static/images/mala_yu.png",
"price":29.00
},{
"name":"绿豆牙炒馓子",
"img":"./static/images/mala_yu.png",
"price":13.00
},{
"name":"西红柿炒鸡蛋",
"img":"./static/images/lazi_ji.png",
"price":12.00
},
{
"name":"酸辣土豆丝",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"香菇青菜",
"img":"./static/images/mala_yu.png",
"price":17.00
},{
"name":"麻辣豆腐",
"img":"./static/images/lazi_ji.png",
"price":10.00
},
{
"name":"鱼香茄子",
"img":"./static/images/mala_yu.png",
"price":15.00
},{
"name":"爆炒花哈",
"img":"./static/images/mala_yu.png",
"price":23.00
},{
"name":"豆芽粉条",
"img":"./static/images/lazi_ji.png",
"price":11.00
},
{
"name":"红油耳丝",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"变蛋黄豆",
"img":"./static/images/mala_yu.png",
"price":9.00
},{
"name":"麻辣花生米",
"img":"./static/images/lazi_ji.png",
"price":9.00
},
{
"name":"水蒸蛋",
"img":"./static/images/mala_yu.png",
"price":10.00
},{
"name":"烧茄子",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"炒饼丝",
"img":"./static/images/lazi_ji.png",
"price":8.00
}]
}
},
methods:{
plus_goods(datas){
this.num_food++;
console.log(this.foodlist[datas.scope.$index]["price"])
this.num_price+=this.foodlist[datas.scope.$index]["price"];
console.log(this.num_price)
}
}
}
</script>
<style scoped>
.el-header{
background-color:darkgreen;
color:white;
line-height:60px;
font-size:20px;
}
.el-menu-item:hover{
background:yellow!important;
color:purple!important;
}
.el-menu-item.is-active{
background:purple!important;
color:white!important;
}
.el-icon-cart{
width:60px;
height:60px;
background: url("../assets/image/cart.png") center no-repeat;
background-size:100% 100%;
margin-top:-10px;
}
.el-icon-cart:before{
visibility: hidden;
}
.bg-green-dark{
background-color:black;
height:60px;
line-height:60px;
color:white;
}
.bg-red-dark{
background-color:darkred;
color:white;
height:60px;
line-height:60px;
}
</style>
代码最终执行结果如下图所示。
二、tornado框架的认识
Tornado是一种 Web 服务器软件的开源版本。Tornado 和主流Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。
像web的框架中,django这类框架,采用WSGI协议与服务器对接的,而这类服务器通常是基于多线程/多进程的,也就是说每有一个网络请求,服务器都会有一个线程/进程进行处理。
遇到高并发的场景就不容易处理,高并发场景情况有以下两种:
1.用户量大,高并发。如秒杀抢购,双十一,618和春节抢票。
2.大量的HTTP持久连接。
基于上述高并发场景,引出了C10k问题,是由一名叫DanKegel的软件工程师提出的,即当同时的连接数以万计的时候,服务器性能会出现急剧下降甚至直接崩溃的情况,这就是著名的C10k问题。
值得一提的,腾讯QQ就遇到过C10k问题,当时采用了udp的方式避开了这个问题,当然过程是相当痛苦的,后来也就专用了tcp,主要是当时还没有epoll技术。
在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。
相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
define _FDSETSIZE 1024
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
如何解决c10k问题:
解决一、对于每个连接处理分配一个独立的进程/线程。提升单台机器的能力,尽可能多提供进程/线程,一台机器不够就增加多台机器。
解决二、用一个进程/线程来同时处理若干个连接。
针对方法一,假设每台机器都达到了一万连接,同时有一亿个请求,那么就需要一万台机器,所以这种解决方法不太实际。
针对方法二,需要有新的技术支持这种方案,实际上是可行的,也是现在普遍采取的方法,针对这种方法,tornado采用epoll技术。
Tornado采用了epoll技术,通过这种技术也就解决了著名的C10k问题,实现了用一个进程/线程来同时处理若干个连接的想法,减少了硬件资源的浪费。
总的来说, 一个tornado服务器可以分为四层, 工作流程大致是下面这样:
三、tornado框架Hello World的实现
tornado框架需要调用基本的三个模块:
tornado.web,tornado.ioloop,tornado.httpserver。
通过tornado框架实现Hello world的代码如下。
import tornado.httpserver
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write('hello world')
if __name__ == '__main__':
application = tornado.web.Application(
handlers=[
(r'/', MainHandler)
]
)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8001)
tornado.ioloop.IOLoop.instance().start()
代码中首先定义一个类MainHandler,这是处理用户请求的类,也就是一个地址对应一个请求的处理类,这个类需要继承于tornado.web.RequestHandler,这是tornado中实现封装好的处理http请求的基类。在这个基类中有get和post方法,其实对应处理的就是前端页面提交到服务器的post或get方式。在这段代码中,实现了一个get请求的方法,get中的参数是类方法所具备的self参数,在函数体中直接在网页上输出“hello World”,采用self.write方法。
在主程序调用方面,首先定义一个application应用程序,代码上直接实例化tornado.web.Application,调用了tornado框架封装好的Application的应用,在其中定义参数handlers,其实就是实现地址与实现类的映射关系,这里的地址使用正则表达式进行匹配,实现类就是在代码最开始定义的类MainHandler。
接下来实例化tornado.httpserver中的HTTPServer类,传入参数application的应用,这是httpserver服务器的启动类,然后监听这个tornado程序的8001端口,也就是web服务器的端口访问号。ioloop是tornado的关键,是他的最底层。ioloop就是对I/O多路复用的封装,它实现了一个单例,调用instance()单例的start()方法就实现了启动这个web服务。最后,可以在浏览器中输入http://localhost:8001进行访问。
运行效果如下图所示。
四、tornado框架实现json接口
通过访问http://localhost:8001可以在网页中写入“hello word”,那也可以写入json数据,这样tornado框架就相当于返回了前端一个接口数据。
我们处理的是Vue堂食点餐系统,数据一定是后端传输到前端所获取的。现在将tornado框架的hello world程序做一些修改,使其返回前端需要的foodlist这个餐饮列表数据,这样就相当于实现了餐饮列表数据的接口。代码如下。
import tornado.httpserver
import tornado.ioloop
import tornado.web
import json
class MainHandler(tornado.web.RequestHandler):
def get(self):
foods_json = [{
"name": "野蘑菇炖鸡",
"img": "./static/images/mogu_ji.png",
"price": 58.00
}, {
"name": "大盘鸡",
"img": "./static/images/dapan_ji.png",
"price": 62.00
},
{
"name": "爆椒鸡",
"img": "./static/images/baojiao_ji.png",
"price": 48.00
}, {
"name": "辣子鸡丁",
"img": "./static/images/lazi_ji.png",
"price": 39.00
},
{
"name": "麻辣鱼头",
"img": "./static/images/mala_yu.png",
"price": 52.00
}, {
"name": "酸菜鱼",
"img": "./static/images/lazi_ji.png",
"price": 53.00
},
{
"name": "红烧带鱼",
"img": "./static/images/mala_yu.png",
"price": 42.00
}, {
"name": "水煮肉片",
"img": "./static/images/dapan_ji.png",
"price": 35.00
},
{
"name": "毛血旺",
"img": "./static/images/baojiao_ji.png",
"price": 30.00
}, {
"name": "鱼香肉丝",
"img": "./static/images/lazi_ji.png",
"price": 18.00
},
{
"name": "烧大肠",
"img": "./static/images/mala_yu.png",
"price": 28.00
}, {
"name": "农家小炒肉",
"img": "./static/images/lazi_ji.png",
"price": 25.00
},
{
"name": "青椒肉丝",
"img": "./static/images/mala_yu.png",
"price": 20.00
}, {
"name": "京酱肉丝",
"img": "./static/images/mala_yu.png",
"price": 19.00
}, {
"name": "红烧肉(带饼)",
"img": "./static/images/lazi_ji.png",
"price": 30.00
},
{
"name": "回锅肉",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "木须肉",
"img": "./static/images/mala_yu.png",
"price": 18.00
}, {
"name": "酸豆角炒肉末",
"img": "./static/images/lazi_ji.png",
"price": 22.00
},
{
"name": "鲜辣烤鱼",
"img": "./static/images/mala_yu.png",
"price": 79.00
}, {
"name": "干锅菜花",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "干锅千页豆腐",
"img": "./static/images/lazi_ji.png",
"price": 15.00
},
{
"name": "西红柿烧牛楠",
"img": "./static/images/mala_yu.png",
"price": 29.00
}, {
"name": "绿豆牙炒馓子",
"img": "./static/images/mala_yu.png",
"price": 13.00
}, {
"name": "西红柿炒鸡蛋",
"img": "./static/images/lazi_ji.png",
"price": 12.00
},
{
"name": "酸辣土豆丝",
"img": "./static/images/mala_yu.png",
"price": 14.00
}, {
"name": "香菇青菜",
"img": "./static/images/mala_yu.png",
"price": 17.00
}, {
"name": "麻辣豆腐",
"img": "./static/images/lazi_ji.png",
"price": 10.00
},
{
"name": "鱼香茄子",
"img": "./static/images/mala_yu.png",
"price": 15.00
}, {
"name": "爆炒花哈",
"img": "./static/images/mala_yu.png",
"price": 23.00
}, {
"name": "豆芽粉条",
"img": "./static/images/lazi_ji.png",
"price": 11.00
},
{
"name": "红油耳丝",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "变蛋黄豆",
"img": "./static/images/mala_yu.png",
"price": 9.00
}, {
"name": "麻辣花生米",
"img": "./static/images/lazi_ji.png",
"price": 9.00
},
{
"name": "水蒸蛋",
"img": "./static/images/mala_yu.png",
"price": 10.00
}, {
"name": "烧茄子",
"img": "./static/images/mala_yu.png",
"price": 14.00
}, {
"name": "炒饼丝",
"img": "./static/images/lazi_ji.png",
"price": 8.00
}];
foods = json.dumps(foods_json)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(foods)
从代码上看,首先定义一个餐饮食品的列表,调用json.jumps()方法把列表转成json数据,因为在餐饮食品列表中有中文字符,setheader()方法实现将显示的内容编码类型设置成“UTF-8”,setheader()中前一个值是设置请求头的哪一个选项,Content-Type就是显示的内容类型选项。“application/json;charset=utf8”指的是显示的是json数据,并且数据的文字编码格式为“UTF-8”。最后在页面上写入foods这个json化的数据。
现在,用http://localhost:8001这个地址去访问,这里返回的就是json数据。如下图所示。
从图片上显示的数据上来看,其显示的是键值对形式的json数据。
五、tornado框架实现并发请求json接口
tornado框架的特性在于性能,也就是并发的特点。如果没有发挥出tornado框架并发的特点,使用起来的tornado框架与django或者flask框架都是一样的。这里就需要处理tornado框架是如何处理并发的。
tornado框架实现并发需要调用户tornado.gen这个模块,在tornado.gen中有coroutine,这是个多协程的技术,这个技术解决并发问题是可以解决c10K的问题的。可以在用户请求类的get方法上加上一个装饰器,这个装饰器的名字就是多协程的技术,也就是在get方法上加入装饰器@tornado.gen.coroutine。这样,get方法中就需要yield来配合去执行对应的方法来实现并发的技术。于是,在被协程修饰的get函数体中加入yield 方法名来实现当并发请求访问数据时,就调用yield后面的方法来实现功能。这样执行的方法调用了多协程的技术,但每次都需要不断产生新的协程,不能对其实现管理,这种协程也叫野协程。而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。为了防止这种现象的出现,引入线程池的概念,更多的引用该技术进行协程的管理。
python可以使用concurrent.futures模块,可以利用ThreadPoolExecutor实现线程池。concurrent.futures会以子进程的形式,平行的运行多个python解释器,从而令python程序可以利用多核CPU来提升执行速度。由于子进程与主解释器相分离,所以他们的全局解释器锁也是相互独立的。每个子进程都能够完整的使用一个CPU内核。
实现这个线程池技术需要实例化用ThreadPoolExecutor,参数表明线程池中的线程大小,比如这里输入10000,那就是这个线程池对10000个协程进行统一的管理,不会无限制的产生协程,导致资源过多而系统瘫痪。
定义结束一个线程池的管理变量executor,然后就可以指定get方法中yield后面的方法在指导的线程池中工作,为yield后面的方法指明装饰器@runonexecutor,这样处理后函数体内的方法逻辑就可以在线程池中运行了。代码如下。
import tornado.httpserver
import tornado.ioloop
import tornado.web
import json
from concurrent.futures import ThreadPoolExecutor
import tornado.concurrent
class MainHandler(tornado.web.RequestHandler):
executor = ThreadPoolExecutor(1000)
@tornado.gen.coroutine
def get(self,*args,**kwargs):
yield self.get_response()
@tornado.concurrent.run_on_executor
def get_response(self,*args,**kwargs):
foods_json = [{
"name": "野蘑菇炖鸡",
"img": "./static/images/mogu_ji.png",
"price": 58.00
}, {
"name": "大盘鸡",
"img": "./static/images/dapan_ji.png",
"price": 62.00
},
{
"name": "爆椒鸡",
"img": "./static/images/baojiao_ji.png",
"price": 48.00
}, {
"name": "辣子鸡丁",
"img": "./static/images/lazi_ji.png",
"price": 39.00
},
{
"name": "麻辣鱼头",
"img": "./static/images/mala_yu.png",
"price": 52.00
}, {
"name": "酸菜鱼",
"img": "./static/images/lazi_ji.png",
"price": 53.00
},
{
"name": "红烧带鱼",
"img": "./static/images/mala_yu.png",
"price": 42.00
}, {
"name": "水煮肉片",
"img": "./static/images/dapan_ji.png",
"price": 35.00
},
{
"name": "毛血旺",
"img": "./static/images/baojiao_ji.png",
"price": 30.00
}, {
"name": "鱼香肉丝",
"img": "./static/images/lazi_ji.png",
"price": 18.00
},
{
"name": "烧大肠",
"img": "./static/images/mala_yu.png",
"price": 28.00
}, {
"name": "农家小炒肉",
"img": "./static/images/lazi_ji.png",
"price": 25.00
},
{
"name": "青椒肉丝",
"img": "./static/images/mala_yu.png",
"price": 20.00
}, {
"name": "京酱肉丝",
"img": "./static/images/mala_yu.png",
"price": 19.00
}, {
"name": "红烧肉(带饼)",
"img": "./static/images/lazi_ji.png",
"price": 30.00
},
{
"name": "回锅肉",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "木须肉",
"img": "./static/images/mala_yu.png",
"price": 18.00
}, {
"name": "酸豆角炒肉末",
"img": "./static/images/lazi_ji.png",
"price": 22.00
},
{
"name": "鲜辣烤鱼",
"img": "./static/images/mala_yu.png",
"price": 79.00
}, {
"name": "干锅菜花",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "干锅千页豆腐",
"img": "./static/images/lazi_ji.png",
"price": 15.00
},
{
"name": "西红柿烧牛楠",
"img": "./static/images/mala_yu.png",
"price": 29.00
}, {
"name": "绿豆牙炒馓子",
"img": "./static/images/mala_yu.png",
"price": 13.00
}, {
"name": "西红柿炒鸡蛋",
"img": "./static/images/lazi_ji.png",
"price": 12.00
},
{
"name": "酸辣土豆丝",
"img": "./static/images/mala_yu.png",
"price": 14.00
}, {
"name": "香菇青菜",
"img": "./static/images/mala_yu.png",
"price": 17.00
}, {
"name": "麻辣豆腐",
"img": "./static/images/lazi_ji.png",
"price": 10.00
},
{
"name": "鱼香茄子",
"img": "./static/images/mala_yu.png",
"price": 15.00
}, {
"name": "爆炒花哈",
"img": "./static/images/mala_yu.png",
"price": 23.00
}, {
"name": "豆芽粉条",
"img": "./static/images/lazi_ji.png",
"price": 11.00
},
{
"name": "红油耳丝",
"img": "./static/images/mala_yu.png",
"price": 16.00
}, {
"name": "变蛋黄豆",
"img": "./static/images/mala_yu.png",
"price": 9.00
}, {
"name": "麻辣花生米",
"img": "./static/images/lazi_ji.png",
"price": 9.00
},
{
"name": "水蒸蛋",
"img": "./static/images/mala_yu.png",
"price": 10.00
}, {
"name": "烧茄子",
"img": "./static/images/mala_yu.png",
"price": 14.00
}, {
"name": "炒饼丝",
"img": "./static/images/lazi_ji.png",
"price": 8.00
}];
foods = json.dumps(foods_json)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.write(foods)
if __name__ == '__main__':
application = tornado.web.Application(
handlers=[
(r'/', MainHandler)
]
)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8001)
tornado.ioloop.IOLoop.instance().start()
代码运行结果如下图所示。
结果与没有实现并发是一样的,但其实质是在发生并发的时候也会显示这样的json数据结果。
六、Vue使用axios获取tornado接口数据
访问http://localhost:8001这个地址后可以获取到餐饮食品的列表数据后,前端就需要请求获取这个数据,然后在前端页面上展示出来。
前面布局好的前端页面中,foodlist餐饮食品列表是通过data()数据初始化中直接给定的,现在需要使用ajax进行数据的获取,vue一般结合axios技术获取后端传送过来的ajax数据。
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
安装axios可以使用npm软件管理包,安装格式如下。
npm install axios --save
命令中的参数“--save”表示会把axios插件注册到vue项目目录package.json里面,也就意味着当前的项目可以使用axios插件。
现在就可以使用axios插件发送get请求,获取数据。axios发送get请求获取数据的格式如下。
axios.get("请求地址",{params:参数字典}).then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
堂食点餐系统中现在的foodlist就可以用axios的get请求,把接口的数据获取,赋值给foodlist,也就是说foodlist的数据来源是由axios的get方式去请求tornado的web服务器得来的。这个axios的方法应该写到哪里呢,是不能写入 初始化的,这跟Vue的生命周期有关系。
Vue的生命周期如下图所示。
从生命周期中可以看到一些钩子函数,如:
根据对钩子函数的解释,created是组件实例创建完成后,即el-table被创建后,但dom树还未生成,这时加载数据是比较合适的。因此,将axios的ajax请求放在created钩子函数中,同时要注意在data()初始化数据中把foodlist置空。created钩子函数执行时把foodlist的数据获取,在创建dom树时就形成了el-table的表格数据,也就展示出了所有的餐饮数据列表。代码如下。
<template>
<div >
<el-header >
绝妙订餐系统
</el-header>
<el-row>
<el-col :span="4">
<el-menu
background-color="darkgreen"
text-color="#fff"
>
<el-menu-item>
<template>
<i class="el-icon-s-platform"></i>
<span>厨师推荐</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-s-tools"></i>
<span>精品凉菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-user-solid"></i>
<span>特色小炒</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>家常热菜</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>营养主食</span>
</template>
</el-menu-item>
<el-menu-item>
<template>
<i class="el-icon-message-solid"></i>
<span>调味饮料</span>
</template>
</el-menu-item>
</el-menu>
</el-col>
<el-col :span="20">
<el-table :data="foodlist" ref="myTable" height="330">
<el-table-column label="菜名" prop="name"></el-table-column>
<el-table-column label="菜品图片" prop="img">
<template slot-scope="scope">
<img :src="scope.row.img" width="170" height="124"/>
</template>
</el-table-column>
<el-table-column label="菜品价格" prop="price"></el-table-column>
<el-table-column label="操作" >
<template slot-scope="scope" >
<i class="el-icon-circle-plus" @click="plus_goods({scope})"></i>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
<el-row>
<el-col :span="2">
<div class="bg-green-dark">
<i class="el-icon-cart"></i>
</div>
</el-col>
<el-col :span="18">
<div class="bg-green-dark">
商品{{num_food}}件,{{num_price}}元
</div>
</el-col>
<el-col :span="4">
<div class="bg-red-dark">
去结算
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import "../assets/logo.png"
import axios from "axios"
export default {
name: 'OrderFood',
data(){
return {
num_food:0,
num_price:0,
foodlist:[]
}
},
created(){
axios.get("http://localhost:8000/index").then((res)=>{
this.foodlist=res.data
})
},
methods:{
plus_goods(datas){
this.num_food++;
console.log(this.foodlist[datas.scope.$index]["price"])
this.num_price+=this.foodlist[datas.scope.$index]["price"];
console.log(this.num_price)
}
}
}
</script>
<style scoped>
.el-header{
background-color:darkgreen;
color:white;
line-height:60px;
font-size:20px;
}
.el-menu-item:hover{
background:yellow!important;
color:purple!important;
}
.el-menu-item.is-active{
background:purple!important;
color:white!important;
}
.el-icon-cart{
width:60px;
height:60px;
background: url("../assets/image/cart.png") center no-repeat;
background-size:100% 100%;
margin-top:-10px;
}
.el-icon-cart:before{
visibility: hidden;
}
.bg-green-dark{
background-color:black;
height:60px;
line-height:60px;
color:white;
}
.bg-red-dark{
background-color:darkred;
color:white;
height:60px;
line-height:60px;
}
</style>
代码执行的结果如下图所示。
从图中下面的控制台看出有错误“已拦截跨源请求”。
七、tornado跨域请求的处理
tornado服务器web程序可以通过设置请求头属性Access-Control-Allow-Origin,表明允许访问的源头是哪里,这里可以指定“”,即所有的都可以访问这个接口,或者是单独的IP或域名都可以,其次还要设置请求头属性的Access-Control-Allow-Headers,将其设置成“”,所有的内容都可以允许。就可以从tornado服务器这里处理跨域请求,当然,客户端也可以实现,这里主要介绍tornado服务器的处理。
代码如下。
self.set_header("Access-Control-Allow-Origin","*")
self.set_header("Access-Control-Allow-Headers","*,Content-Type")
最终服务器端的完整代码如下。
import tornado.web
import tornado.ioloop
import tornado.gen
from concurrent.futures import ThreadPoolExecutor
import tornado.concurrent
import json
class IndexHandler(tornado.web.RequestHandler):
executor=ThreadPoolExecutor(1000)
@tornado.gen.coroutine
def get(self, *args, **kwargs):
yield self.index_response()
@tornado.concurrent.run_on_executor
def index_response(self,*args,**kwargs):
foods_json=[{
"name":"野蘑菇炖鸡",
"img":"./static/images/mogu_ji.png",
"price":58.00
},{
"name":"大盘鸡",
"img":"./static/images/dapan_ji.png",
"price":62.00
},
{
"name":"爆椒鸡",
"img":"./static/images/baojiao_ji.png",
"price":48.00
},{
"name":"辣子鸡丁",
"img":"./static/images/lazi_ji.png",
"price":39.00
},
{
"name":"麻辣鱼头",
"img":"./static/images/mala_yu.png",
"price":52.00
},{
"name":"酸菜鱼",
"img":"./static/images/lazi_ji.png",
"price":53.00
},
{
"name":"红烧带鱼",
"img":"./static/images/mala_yu.png",
"price":42.00
},{
"name":"水煮肉片",
"img":"./static/images/dapan_ji.png",
"price":35.00
},
{
"name":"毛血旺",
"img":"./static/images/baojiao_ji.png",
"price":30.00
},{
"name":"鱼香肉丝",
"img":"./static/images/lazi_ji.png",
"price":18.00
},
{
"name":"烧大肠",
"img":"./static/images/mala_yu.png",
"price":28.00
},{
"name":"农家小炒肉",
"img":"./static/images/lazi_ji.png",
"price":25.00
},
{
"name":"青椒肉丝",
"img":"./static/images/mala_yu.png",
"price":20.00
},{
"name":"京酱肉丝",
"img":"./static/images/mala_yu.png",
"price":19.00
},{
"name":"红烧肉(带饼)",
"img":"./static/images/lazi_ji.png",
"price":30.00
},
{
"name":"回锅肉",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"木须肉",
"img":"./static/images/mala_yu.png",
"price":18.00
},{
"name":"酸豆角炒肉末",
"img":"./static/images/lazi_ji.png",
"price":22.00
},
{
"name":"鲜辣烤鱼",
"img":"./static/images/mala_yu.png",
"price":79.00
},{
"name":"干锅菜花",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"干锅千页豆腐",
"img":"./static/images/lazi_ji.png",
"price":15.00
},
{
"name":"西红柿烧牛楠",
"img":"./static/images/mala_yu.png",
"price":29.00
},{
"name":"绿豆牙炒馓子",
"img":"./static/images/mala_yu.png",
"price":13.00
},{
"name":"西红柿炒鸡蛋",
"img":"./static/images/lazi_ji.png",
"price":12.00
},
{
"name":"酸辣土豆丝",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"香菇青菜",
"img":"./static/images/mala_yu.png",
"price":17.00
},{
"name":"麻辣豆腐",
"img":"./static/images/lazi_ji.png",
"price":10.00
},
{
"name":"鱼香茄子",
"img":"./static/images/mala_yu.png",
"price":15.00
},{
"name":"爆炒花哈",
"img":"./static/images/mala_yu.png",
"price":23.00
},{
"name":"豆芽粉条",
"img":"./static/images/lazi_ji.png",
"price":11.00
},
{
"name":"红油耳丝",
"img":"./static/images/mala_yu.png",
"price":16.00
},{
"name":"变蛋黄豆",
"img":"./static/images/mala_yu.png",
"price":9.00
},{
"name":"麻辣花生米",
"img":"./static/images/lazi_ji.png",
"price":9.00
},
{
"name":"水蒸蛋",
"img":"./static/images/mala_yu.png",
"price":10.00
},{
"name":"烧茄子",
"img":"./static/images/mala_yu.png",
"price":14.00
},{
"name":"炒饼丝",
"img":"./static/images/lazi_ji.png",
"price":8.00
}];
foods=json.dumps(foods_json)
self.set_header("Access-Control-Allow-Origin","*")
self.set_header("Access-Control-Allow-Headers","*,Content-Type")
self.set_header("Content-Type","application/json; charset=UTF-8")
self.write(foods)
启动服务器端命令:
Python tst_web.py
这里tst_web.py是服务器端python程序代码文件名
启动客户端程序需要在客户端程序目录下执行。
npm run dev
通过浏览器访问客户端程序,地址是http://localhost:8080,执行结果如下图所示。
代码github地址:https://github.com/wawacode/tornado-vue-order_food_system。
项目对应的视频对址:
vue堂食系统2-vue技术实现组件间的通信
https://www.bilibili.com/video/BV1ef4y1z7FS/
vue堂食系统3-tornado并发的实现
https://www.bilibili.com/video/BV13N411d7Gp/