目录
环境
django+ajax(juqery)+vue(可选)
不使用websocket长连接
目的
需要解决的问题
当我们需要在服务器上实现一些复杂逻辑功能时,可能会在一个函数(称为mission)中打印很多内容到python端的控制台,但是这些内容并不是mission的返回值,因此无法直接被view.py中的函数(称为getData)捕获并渲染到页面(称为display)。
问题解析
关键点在于
- 要让getData能够捕获到mission打印到控制台的信息
- 前端获取后端信息,动态更新页面,当mission完成后,停止更新前端
- 多线程,getData和mission需要运行在两个线程下
解决方案
- 在view.py中建立全局变量存储mission打印的信息
- 创建线程类MyThread,包含线程控制指令
- 创建线程控制接口函数thread_controller,实现线程的创建、杀死等控制逻辑
- console.html中周期性向后端发送ajax请求,并接受数据,当接收到stop信号时,停止发送请求
- display.html中负责解析和显示数据
- getData只负责调用thread_controller即可
实战
需要实现的功能
console.html中显示一个按钮和文本框,点击按钮后,周期性getData发送get请求,getData中运行mission函数,并动态的将mission中需要打印的内容返回至display.html,并将display.html显示在console.html的文本框中,当mission运行完后,向console.html发送停止指令。
工程结构
---demo //项目容器
|---demo //主目录,整个项目的配置和调度中心。
| |---__init__.py //告诉python,该目录是一个包 。
| |---asgi.py //一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
| |---settings.py //该 Django 项目的设置/配置。
| |---urls.py //该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"
| |---wsgi.py //一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目
|---first_app //新建的应用
| |---migrations //用于数据库迁移,便于数据库管理
| | |---__init__.py
| |---templates //用于存放html文件,即网页结构
| | |---first_app
| | |---js_lib //存放js库文件
| | |---console_vue.js //自己写的控制代码
| | |---jquery.min.js //jquery,包装了ajax
| | |---vue.js //vue库
| | |---console.html //主页,控制台
| | |---display.html //显示后端打印的信息
| |---__init__.py
| |---admin.py //自带的后台管理
| |---apps.py //创建对应app类的文件
| |---models.py //mtv中的m,用于和数据库交互
| |---tests.py //用于开发测试用例
| |---urls.py //first_app的urls目录
| |---views.py //mtv中的v,用于处理网页的后端业务逻辑
|---third_project //功能代码
| |--mission.py //具体功能的实现
| |--my_thread.py //线程类及控制函数
|---db.sqlite3 //轻量级数据库,用于和models交互
|---manage.py //一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
具体实现
主页功能
console.html
使用v-html=”message”接收数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>动态显示</title>
<style type="text/css">
#result {
width: 100%;
height: 600px;
border: solid 1px #90b;
}
</style>
</head>
<body>
<div id="contain">
<button id="btn" @click="funA">funA</button>
<div id="result">
<p v-html="message"></p>
</div>
</div>
<script src="/static/first_app/js_lib/jquery.min.js"></script>
<script src="/static/first_app/js_lib/vue.js"></script>
<script src="/static/first_app/js_lib/console_vue.js"></script>
</body>
</html>
周期性发送请求
console_vue.js
这里使用id = setInterval(function_handle, millisecond)实现周期性的发送get请求,并且使用clearInterval(id)停止周期性的发送请求。
这里需要注意的是function_handle不能包含参数,但是我们可以通过嵌套的方式返回一个不带参数的函数句柄。
var app = new Vue({
el: "#contain",
data: {
message: "",
},
methods: {
funA: function () {
var that = this;
var timer = window.setInterval(_send(that), 4 * 1000); // 不能调用带参数的函数句柄,可以通过嵌套的方式返回一个不带参数的函数句柄
function _send(_param){
return function() { // 返回不带参数的函数句柄
send(_param);
}
};
function send(obj){
$.get("/display/", function (data) {
console.log(data)
if(data == "stop") {
window.clearInterval(timer); // 使用setInterval返回的id作为参数
console.log("timer is stoped")
}
else
obj.message = data;
});
}
},
},
});
获取数据并返回display
view.py
两个全局变量result用来存放mission打印的信息和thread_dict用来存放线程的状态。
这里直接用getData调用thread_controller接口即可,接口的参数会在后面解释。
from django.shortcuts import render
from third_project.my_thread import thread_controller
from third_project.mission import print_message
# Create your views here.
result = {}
thread_dict = {}
def home(request):
'''首页'''
return render(request, 'first_app/console.html')
def getData(request):
'''getData'''
return thread_controller(request, result, thread_dict,
"getData", print_message, [1, 10],
'first_app/display.html')
生成数据的功能函数
mission.py
间隔一秒向列表中添加递增的整数,并将列表存放在字典中。
import time
def print_message(start, end, dict):
dict['print_message'] = []
for i in range(end - start):
dict['print_message'].append(i + start)
time.sleep(1)
e.g.
print_message(1,5,{})
{‘print_message’:[1,2,3,4]}
线程类和控制函数
my_thread.py
# result = {} 存放需要打印的结果,key为被调用的函数名,value为list
# thread_dict = {}
- # 存放线程状态,key为view中开启线程的函数名,value为state
- # state 意义
- # stop 停止或未创建
- # start 开始运行
- # pause 暂停运行
- # end 被调用的函数执行完毕了
- # killed 线程已杀死
- # request http响应内容
- # result 需要打印的结果
- # thread_dict 线程状态
- # func_name view.py中函数的名字
- # callback 被调用的函数名
- # args callback执行需要的参数
- # html_path 返回的url
from django.http import HttpResponse
from django.shortcuts import render
import threading
def thread_controller(request, result, thread_dict, func_name, callback, args, html_path):
if result.get(func_name, "NA") == "NA":
result[func_name] = {}
thread_dict[func_name] = "stop"
res = result[func_name]
state = thread_dict[func_name]
if state == "stop":
test_thread = MyThread(thread_dict, func_name, callback, args, res)
test_thread.start()
test_thread.resume()
elif state == "end":
thread_dict[func_name] = "killed"
return render(request, html_path, res)
elif state == "killed":
result[func_name] = {}
thread_dict[func_name] = "stop"
return HttpResponse("stop")
return render(request, html_path, res)
class MyThread(threading.Thread):
def __init__(self, state_dict, thread_name, func_handle, p_list, g_dict):
super(MyThread, self).__init__()
self.g_dict = g_dict
self.func_handle = func_handle
self.p_list = p_list
self.p_list.append(g_dict)
self.paused = True # Start out paused.
self.isend = False # thread is end.
self.thread_name = thread_name
self.state_dict = state_dict
self.cond = threading.Condition()
def run(self):
while True:
with self.cond: # 在该条件下操作
self.resume()
list(map(self.func_handle, *zip(self.p_list))) # 调用外部函数
self.end()
if self.paused:
self.cond.wait() # Block execution until notified.
if self.isend:
break
def resume(self): # 用来恢复/启动run
with self.cond: # 在该条件下操作
self.paused = False
self.state_dict[self.thread_name] = "start"
self.cond.notify() # Unblock self if waiting.
def pause(self): # 用来暂停run
with self.cond: # 在该条件下操作
self.paused = True # Block self.
self.state_dict[self.thread_name] = "paused"
def end(self): # 用来结束run
with self.cond: # 在该条件下操作
self.isend = True # Block self.
self.state_dict[self.thread_name] = "end"
def kill(self): # 用来杀死线程
with self.cond: # 在该条件下操作
self._delete()
self.state_dict[self.thread_name] = "killed"
注意这里有一个关键点,我们在写线程类的时候,没法知道需要调用什么函数,以及需要什么参数,因此这里使用了map函数,实现将函数句柄和作为参数,便于封装和使用。
另外要注意Python2和3中map的使用方法有些区别
#python2直接返回列表
map(func_handle, [1,2,3])
def square(x) : # 计算平方数
return x ** 2
# 计算列表各个元素的平方
map(square, [1,2,3,4,5])
# [1, 4, 9, 16, 25]
# 使用 lambda 匿名函数
map(lambda x: x ** 2, [1, 2, 3, 4, 5])
# [1, 4, 9, 16, 25]
# 多参数函数
map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
# [3, 7, 11, 15, 19]
#python3返回的是迭代器
def square(x) : # 计算平方数
return x ** 2
# 计算列表各个元素的平方,返回迭代器
map(square, [1,2,3,4,5])
# <map object at 0x100d3d550>
# 使用 list() 转换为列表
list(map(square, [1,2,3,4,5]))
# [1, 4, 9, 16, 25]
# 使用 lambda 匿名函数
list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))
# [1, 4, 9, 16, 25]
# 多参数函数,使用zip函数进行捆绑
def add(x, y) : # 计算和
return x + y
list(map(add, *zip([1, 3, 5, 7, 9])))
# [4, 8, 12, 16]
最终效果
左边的文本框显示了打印的结果
右边的waterfall可以看出请求时周期进行的
底部的stop和timer is stoped说明前端的周期性请求已经停止
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/100783.html