Commit b720eb3c authored by jackfrued's avatar jackfrued

添加了Tornado相关的文档和代码

parent 1f67d39f
This diff is collapsed.
## Django实战(04) - 表单的应用
我们继续来完成上一章节中的项目,实现“用户注册”和“用户登录”的功能。Django框架中提供了对表单的封装,而且提供了多种不同的使用方式。
我们继续来完成上一章节中的项目,实现“用户注册”和“用户登录”的功能,并限制只有登录的用户才能为老师投票。Django框架中提供了对表单的封装,而且提供了多种不同的使用方式。
首先添加用户模型。
```Python
```
## 预备知识
### 并发编程
所谓并发编程就是让程序中有多个部分能够并发或同时执行,并发编程带来的好处不言而喻,其中最为关键的两点是提升了执行效率和改善了用户体验。下面简单阐述一下Python中实现并发编程的三种方式:
1. 多线程:Python中通过`threading`模块的`Thread`类并辅以`Lock``Condition``Event``Semaphore``Barrier`等类来支持多线程编程。Python解释器通过GIL(全局解释器锁)来防止多个线程同时执行本地字节码,这个锁对于CPython(Python解释器的官方实现)是必须的,因为CPython的内存管理并不是线程安全的。因为GIL的存在,Python的多线程并不能利用CPU的多核特性。
2. 多进程:使用多进程可以有效的解决GIL的问题,Python中的`multiprocessing`模块提供了`Process`类来实现多进程,其他的辅助类跟`threading`模块中的类类似,由于进程间的内存是相互隔离的(操作系统对进程的保护),进程间通信(共享数据)必须使用管道、套接字等方式,这一点从编程的角度来讲是比较麻烦的,为此,Python的`multiprocessing`模块提供了一个名为`Queue`的类,它基于管道和锁机制提供了多个进程共享的队列。
```Python
"""
用下面的命令运行程序并查看执行时间,例如:
time python3 example06.py
real 0m20.657s
user 1m17.749s
sys 0m0.158s
使用多进程后实际执行时间为20.657秒,而用户时间1分17.749秒约为实际执行时间的4倍
这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU
"""
import concurrent.futures
import math
PRIMES = [
1116281,
1297337,
104395303,
472882027,
533000389,
817504243,
982451653,
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419
] * 5
def is_prime(num):
"""判断素数"""
assert num > 0
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return num != 1
def main():
"""主函数"""
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
```
3. 异步编程(异步I/O):所谓异步编程是通过调度程序从任务队列中挑选任务,调度程序以交叉的形式执行这些任务,我们并不能保证任务将以某种顺序去执行,因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步编程通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过钩子函数(回调函数)或者`Future`对象来获取任务执行的结果。目前我们使用的Python 3通过`asyncio`模块以及`await``async`关键字(Python 3.5中引入,Python 3.7中正式成为关键字)提供了对异步I/O的支持。
```Python
import asyncio
async def fetch(host):
"""从指定的站点抓取信息(协程函数)"""
print(f'Start fetching {host}\n')
# 跟服务器建立连接
reader, writer = await asyncio.open_connection(host, 80)
# 构造请求行和请求头
writer.write(b'GET / HTTP/1.1\r\n')
writer.write(f'Host: {host}\r\n'.encode())
writer.write(b'\r\n')
# 清空缓存区(发送请求)
await writer.drain()
# 接收服务器的响应(读取响应行和响应头)
line = await reader.readline()
while line != b'\r\n':
print(line.decode().rstrip())
line = await reader.readline()
print('\n')
writer.close()
def main():
"""主函数"""
urls = ('www.sohu.com', 'www.douban.com', 'www.163.com')
# 获取系统默认的事件循环
loop = asyncio.get_event_loop()
# 用生成式语法构造一个包含多个协程对象的列表
tasks = [fetch(url) for url in urls]
# 通过asyncio模块的wait函数将协程列表包装成Task(Future子类)并等待其执行完成
# 通过事件循环的run_until_complete方法运行任务直到Future完成并返回它的结果
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
> 说明:目前大多数网站都要求基于HTTPS通信,因此上面例子中的网络请求不一定能收到正常的响应,也就是说响应状态码不一定是200,有可能是3xx或者4xx。当然我们这里的重点不在于获得网站响应的内容,而是帮助大家理解`asyncio`模块以及`async`和`await`两个关键字的使用。
我们对三种方式的使用场景做一个简单的总结。
以下情况需要使用多线程:
1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。
2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。
以下情况需要使用多进程:
1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。
最后,如果程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,异步I/O就是一种很好的选择。另一方面,当程序中有大量的等待与休眠时,也应该考虑使用异步I/O。
> 扩展:关于进程,还需要做一些补充说明。首先,为了控制进程的执行,操作系统内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程使之继续执行,这种行为被称为进程切换(也叫调度)。进程切换是比较耗费资源的操作,因为在进行切换时首先要保存当前进程的上下文(内核再次唤醒该进程时所需要的状态,包括:程序计数器、状态寄存器、数据栈等),然后还要恢复准备执行的进程的上下文。正在执行的进程由于期待的某些事件未发生,如请求系统资源失败、等待某个操作完成、新数据尚未到达等原因会主动由运行状态变为阻塞状态,当进程进入阻塞状态,是不占用CPU资源的。这些知识对于理解到底选择哪种方式进行并发编程也是很重要的。
### I/O模式和事件驱动
对于一次I/O操作(以读操作为例),数据会先被拷贝到操作系统内核的缓冲区中,然后从操作系统内核的缓冲区拷贝到应用程序的缓冲区(这种方式称为标准I/O或缓存I/O,大多数文件系统的默认I/O都是这种方式),最后交给进程。所以说,当一个读操作发生时(写操作与之类似),它会经历两个阶段:(1)等待数据准备就绪;(2)将数据从内核拷贝到进程中。
由于存在这两个阶段,因此产生了以下几种I/O模式:
1. 阻塞 I/O(blocking I/O):进程发起读操作,如果内核数据尚未就绪,进程会阻塞等待数据直到内核数据就绪并拷贝到进程的内存中。
2. 非阻塞 I/O(non-blocking I/O):进程发起读操作,如果内核数据尚未就绪,进程不阻塞而是收到内核返回的错误信息,进程收到错误信息可以再次发起读操作,一旦内核数据准备就绪,就立即将数据拷贝到了用户内存中,然后返回。
3. 多路I/O复用( I/O multiplexing):监听多个I/O对象,当I/O对象有变化(数据就绪)的时候就通知用户进程。多路I/O复用的优势并不在于单个I/O操作能处理得更快,而是在于能处理更多的I/O操作。
4. 异步 I/O(asynchronous I/O):进程发起读操作后就可以去做别的事情了,内核收到异步读操作后会立即返回,所以用户进程不阻塞,当内核数据准备就绪时,内核发送一个信号给用户进程,告诉它读操作完成了。
通常,我们编写一个处理用户请求的服务器程序时,有以下三种方式可供选择:
1. 每收到一个请求,创建一个新的进程,来处理该请求;
2. 每收到一个请求,创建一个新的线程,来处理该请求;
3. 每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求
第1种方式实现比较简单,但由于创建进程开销比较大,会导致服务器性能比较差;第2种方式,由于要涉及到线程的同步,有可能会面临竞争、死锁等问题;第3种方式,就是所谓事件驱动的方式,它利用了多路I/O复用和异步I/O的优点,虽然代码逻辑比前面两种都复杂,但能达到最好的性能,这也是目前大多数网络服务器采用的方式。
This diff is collapsed.
## 异步化
在前面的例子中,我们并没有对`RequestHandler`中的`get``post`方法进行异步处理,这就意味着,一旦在`get``post`方法中出现了耗时间的操作,不仅仅是当前请求被阻塞,按照Tornado框架的工作模式,其他的请求也会被阻塞,所以我们需要对耗时间的操作进行异步化处理。
在Tornado稍早一些的版本中,可以用装饰器实现请求方法的异步化或协程化来解决这个问题。
-`RequestHandler`的请求处理函数添加`@tornado.web.asynchronous`装饰器,如下所示:
```Python
class AsyncReqHandler(RequestHandler):
@tornado.web.asynchronous
def get(self):
http = httpclient.AsyncHTTPClient()
http.fetch("http://example.com/", self._on_download)
def _on_download(self, response):
do_something_with_response(response)
self.render("template.html")
```
- 给`RequestHandler`的请求处理函数添加`@tornado.gen.coroutine`装饰器,如下所示:
```Python
class GenAsyncHandler(RequestHandler):
@tornado.gen.coroutine
def get(self):
http_client = AsyncHTTPClient()
response = yield http_client.fetch("http://example.com")
do_something_with_response(response)
self.render("template.html")
```
- 使用`@return_future`装饰器,如下所示:
```Python
@return_future
def future_func(arg1, arg2, callback):
# Do stuff (possibly asynchronous)
callback(result)
async def caller():
await future_func(arg1, arg2)
```
在Tornado 5.x版本中,这几个装饰器都被标记为**deprcated**(过时),我们可以通过Python 3.5中引入的`async`和`await`(在Python 3.7中已经成为正式的关键字)来达到同样的效果。当然,要实现异步化还得靠其他的支持异步操作的三方库来支持,如果请求处理函数中用到了不支持异步操作的三方库,就需要靠自己写包装类来支持异步化。
下面的代码演示了在读写数据库时如何实现请求处理的异步化。我们用到的数据库建表语句如下所示:
```SQL
create database hrs default charset utf8;
use hrs;
/* 创建部门表 */
create table tb_dept
(
dno int not null comment '部门编号',
dname varchar(10) not null comment '部门名称',
dloc varchar(20) not null comment '部门所在地',
primary key (dno)
);
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
(30, '销售部', '重庆'),
(40, '运维部', '深圳');
```
我们通过下面的代码实现了查询和新增部门两个操作。
```Python
import json
import aiomysql
import tornado
import tornado.web
from tornado.ioloop import IOLoop
from tornado.options import define, parse_command_line, options
define('port', default=8000, type=int)
async def connect_mysql():
return await aiomysql.connect(
host='120.77.222.217',
port=3306,
db='hrs',
user='root',
password='123456',
)
class HomeHandler(tornado.web.RequestHandler):
async def get(self, no):
async with self.settings['mysql'].cursor(aiomysql.DictCursor) as cursor:
await cursor.execute("select * from tb_dept where dno=%s", (no, ))
if cursor.rowcount == 0:
self.finish(json.dumps({
'code': 20001,
'mesg': f'没有编号为{no}的部门'
}))
return
row = await cursor.fetchone()
self.finish(json.dumps(row))
async def post(self, *args, **kwargs):
no = self.get_argument('no')
name = self.get_argument('name')
loc = self.get_argument('loc')
conn = self.settings['mysql']
try:
async with conn.cursor() as cursor:
await cursor.execute('insert into tb_dept values (%s, %s, %s)',
(no, name, loc))
await conn.commit()
except aiomysql.MySQLError:
self.finish(json.dumps({
'code': 20002,
'mesg': '添加部门失败请确认部门信息'
}))
else:
self.set_status(201)
self.finish()
def make_app(config):
return tornado.web.Application(
handlers=[(r'/api/depts/(.*)', HomeHandler), ],
**config
)
def main():
parse_command_line()
app = make_app({
'debug': True,
'mysql': IOLoop.current().run_sync(connect_mysql)
})
app.listen(options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
```
上面的代码中,我们用到了`aiomysql`这个三方库,它基于`pymysql`封装,实现了对MySQL操作的异步化。操作Redis可以使用`aioredis`,访问MongoDB可以使用`motor`,这些都是支持异步操作的三方库。
\ No newline at end of file
This diff is collapsed.
"""
handlers.py - 用户登录和聊天的处理器
"""
import tornado.web
import tornado.websocket
nicknames = set()
connections = {}
class LoginHandler(tornado.web.RequestHandler):
def get(self):
self.render('login.html', hint='')
def post(self):
nickname = self.get_argument('nickname')
if nickname in nicknames:
self.render('login.html', hint='昵称已被使用,请更换昵称')
self.set_secure_cookie('nickname', nickname)
self.render('chat.html')
class ChatHandler(tornado.websocket.WebSocketHandler):
def open(self):
nickname = self.get_secure_cookie('nickname').decode()
nicknames.add(nickname)
for conn in connections.values():
conn.write_message(f'~~~{nickname}进入了聊天室~~~')
connections[nickname] = self
def on_message(self, message):
nickname = self.get_secure_cookie('nickname').decode()
for conn in connections.values():
if conn is not self:
conn.write_message(f'{nickname}说:{message}')
def on_close(self):
nickname = self.get_secure_cookie('nickname').decode()
del connections[nickname]
nicknames.remove(nickname)
for conn in connections.values():
conn.write_message(f'~~~{nickname}离开了聊天室~~~')
"""
chat_server.py - 聊天服务器
"""
import os
import tornado.web
import tornado.ioloop
from chat_handlers import LoginHandler, ChatHandler
def main():
app = tornado.web.Application(
handlers=[(r'/login', LoginHandler), (r'/chat', ChatHandler)],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
static_path=os.path.join(os.path.dirname(__file__), 'static'),
cookie_secret='MWM2MzEyOWFlOWRiOWM2MGMzZThhYTk0ZDNlMDA0OTU=',
)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example01.py - 五分钟上手Tornado
"""
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定义默认端口
define('port', default=8000, type=int)
class MainHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self):
# 向客户端(浏览器)写入内容
self.write('<h1>Hello, world!</h1>')
def main():
"""主函数"""
# 解析命令行参数,例如:
# python example01.py --port 8888
parse_command_line()
# 创建了Tornado框架中Application类的实例并指定handlers参数
# Application实例代表了我们的Web应用,handlers代表了路由解析
app = tornado.web.Application(handlers=[(r'/', MainHandler), ])
# 指定了监听HTTP请求的TCP端口(默认8000,也可以通过命令行参数指定)
app.listen(options.port)
# 获取Tornado框架的IOLoop实例并启动它(默认启动asyncio的事件循环)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example02.py - 路由解析
"""
import os
import random
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定义默认端口
define('port', default=8000, type=int)
class SayingHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self):
sayings = [
'世上没有绝望的处境,只有对处境绝望的人',
'人生的道路在态度的岔口一分为二,从此通向成功或失败',
'所谓措手不及,不是说没有时间准备,而是有时间的时候没有准备',
'那些你认为不靠谱的人生里,充满你没有勇气做的事',
'在自己喜欢的时间里,按照自己喜欢的方式,去做自己喜欢做的事,这便是自由',
'有些人不属于自己,但是遇见了也弥足珍贵'
]
# 渲染index.html模板页
self.render('index.html', message=random.choice(sayings))
class WeatherHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self, city):
# Tornado框架会自动处理百分号编码的问题
weathers = {
'北京': {'temperature': '-4~4', 'pollution': '195 中度污染'},
'成都': {'temperature': '3~9', 'pollution': '53 良'},
'深圳': {'temperature': '20~25', 'pollution': '25 优'},
'广州': {'temperature': '18~23', 'pollution': '56 良'},
'上海': {'temperature': '6~8', 'pollution': '65 良'}
}
if city in weathers:
self.render('weather.html', city=city, weather=weathers[city])
else:
self.render('index.html', message=f'没有{city}的天气信息')
class ErrorHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self):
# 重定向到指定的路径
self.redirect('/saying')
def main():
"""主函数"""
parse_command_line()
app = tornado.web.Application(
# handlers是按列表中的顺序依次进行匹配的
handlers=[
(r'/saying/?', SayingHandler),
(r'/weather/([^/]{2,})/?', WeatherHandler),
(r'/.+', ErrorHandler),
],
# 通过template_path参数设置模板页的路径
template_path=os.path.join(os.path.dirname(__file__), 'templates')
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example03.py - RequestHandler解析
"""
import os
import re
import tornado.ioloop
import tornado.web
from tornado.options import define, options, parse_command_line
# 定义默认端口
define('port', default=8000, type=int)
users = {}
class User(object):
"""用户"""
def __init__(self, nickname, gender, birthday):
self.nickname = nickname
self.gender = gender
self.birthday = birthday
class MainHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self):
# 从Cookie中读取用户昵称
nickname = self.get_cookie('nickname')
if nickname in users:
self.render('userinfo.html', user=users[nickname])
else:
self.render('userform.html', hint='请填写个人信息')
class UserHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def post(self):
# 从表单参数中读取用户昵称、性别和生日信息
nickname = self.get_body_argument('nickname').strip()
gender = self.get_body_argument('gender')
birthday = self.get_body_argument('birthday')
# 检查用户昵称是否有效
if not re.fullmatch(r'\w{6,20}', nickname):
self.render('userform.html', hint='请输入有效的昵称')
elif nickname in users:
self.render('userform.html', hint='昵称已经被使用过')
else:
users[nickname] = User(nickname, gender, birthday)
# 将用户昵称写入Cookie并设置有效期为7天
self.set_cookie('nickname', nickname, expires_days=7)
self.render('userinfo.html', user=users[nickname])
def main():
"""主函数"""
parse_command_line()
app = tornado.web.Application(
handlers=[
(r'/', MainHandler),
(r'/register', UserHandler),
],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example04.py - 同步请求的例子
"""
import json
import os
import requests
import tornado.gen
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.httpclient
from tornado.options import define, options, parse_command_line
define('port', default=8888, type=int)
REQ_URL = 'http://api.tianapi.com/guonei/'
API_KEY = '772a81a51ae5c780251b1f98ea431b84'
class MainHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
def get(self):
resp = requests.get(f'{REQ_URL}?key={API_KEY}')
newslist = json.loads(resp.text)['newslist']
self.render('news.html', newslist=newslist)
def main():
"""主函数"""
parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', MainHandler), ],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example05.py - 异步请求的例子
"""
import aiohttp
import json
import os
import tornado.gen
import tornado.ioloop
import tornado.web
import tornado.websocket
import tornado.httpclient
from tornado.options import define, options, parse_command_line
define('port', default=8888, type=int)
REQ_URL = 'http://api.tianapi.com/guonei/'
API_KEY = '772a81a51ae5c780251b1f98ea431b84'
class MainHandler(tornado.web.RequestHandler):
"""自定义请求处理器"""
async def get(self):
async with aiohttp.ClientSession() as session:
resp = await session.get(f'{REQ_URL}?key={API_KEY}')
json_str = await resp.text()
print(json_str)
newslist = json.loads(json_str)['newslist']
self.render('news.html', newslist=newslist)
def main():
"""主函数"""
parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', MainHandler), ],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example06.py - 异步操作MySQL
"""
import json
import aiomysql
import tornado
import tornado.web
from tornado.ioloop import IOLoop
from tornado.options import define, parse_command_line, options
define('port', default=8888, type=int)
async def connect_mysql():
return await aiomysql.connect(
host='120.77.222.217',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
user='root',
password='123456',
)
class HomeHandler(tornado.web.RequestHandler):
async def get(self, no):
async with self.settings['mysql'].cursor(aiomysql.DictCursor) as cursor:
await cursor.execute("select * from tb_dept where dno=%s", (no, ))
if cursor.rowcount == 0:
self.finish(json.dumps({
'code': 20001,
'mesg': f'没有编号为{no}的部门'
}))
return
row = await cursor.fetchone()
self.finish(json.dumps(row))
async def post(self, *args, **kwargs):
no = self.get_argument('no')
name = self.get_argument('name')
loc = self.get_argument('loc')
conn = self.settings['mysql']
try:
async with conn.cursor() as cursor:
await cursor.execute('insert into tb_dept values (%s, %s, %s)',
(no, name, loc))
await conn.commit()
except aiomysql.MySQLError:
self.finish(json.dumps({
'code': 20002,
'mesg': '添加部门失败请确认部门信息'
}))
else:
self.set_status(201)
self.finish()
def make_app(config):
return tornado.web.Application(
handlers=[(r'/api/depts/(.*)', HomeHandler), ],
**config
)
def main():
parse_command_line()
app = make_app({
'debug': True,
'mysql': IOLoop.current().run_sync(connect_mysql)
})
app.listen(options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
"""
example07.py - 将非异步的三方库封装为异步调用
"""
import asyncio
import concurrent
import json
import tornado
import tornado.web
import pymysql
from pymysql import connect
from pymysql.cursors import DictCursor
from tornado.ioloop import IOLoop
from tornado.options import define, parse_command_line, options
from tornado.platform.asyncio import AnyThreadEventLoopPolicy
define('port', default=8888, type=int)
def get_mysql_connection():
return connect(
host='120.77.222.217',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
user='root',
password='123456',
)
class HomeHandler(tornado.web.RequestHandler):
executor = concurrent.futures.ThreadPoolExecutor(max_workers=10)
async def get(self, no):
return await self._get(no)
@tornado.concurrent.run_on_executor
def _get(self, no):
con = get_mysql_connection()
try:
with con.cursor(DictCursor) as cursor:
cursor.execute("select * from tb_dept where dno=%s", (no, ))
if cursor.rowcount == 0:
self.finish(json.dumps({
'code': 20001,
'mesg': f'没有编号为{no}的部门'
}))
return
row = cursor.fetchone()
self.finish(json.dumps(row))
finally:
con.close()
async def post(self, *args, **kwargs):
return await self._post(*args, **kwargs)
@tornado.concurrent.run_on_executor
def _post(self, *args, **kwargs):
no = self.get_argument('no')
name = self.get_argument('name')
loc = self.get_argument('loc')
conn = get_mysql_connection()
try:
with conn.cursor() as cursor:
cursor.execute('insert into tb_dept values (%s, %s, %s)',
(no, name, loc))
conn.commit()
except pymysql.MySQLError:
self.finish(json.dumps({
'code': 20002,
'mesg': '添加部门失败请确认部门信息'
}))
else:
self.set_status(201)
self.finish()
def main():
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
parse_command_line()
app = tornado.web.Application(
handlers=[(r'/api/depts/(.*)', HomeHandler), ]
)
app.listen(options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
import asyncio
import re
import aiohttp
PATTERN = re.compile(r'\<title\>(?P<title>.*)\<\/title\>')
async def show_title(url):
async with aiohttp.ClientSession() as session:
resp = await session.get(url, ssl=False)
html = await resp.text()
print(PATTERN.search(html).group('title'))
def main():
urls = ('https://www.python.org/',
'https://git-scm.com/',
'https://www.jd.com/',
'https://www.taobao.com/',
'https://www.douban.com/')
# asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
# 获取事件循环()
loop = asyncio.get_event_loop()
tasks = [show_title(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
if __name__ == '__main__':
main()
import asyncio
async def fetch(host):
"""从指定的站点抓取信息(协程函数)"""
print(f'Start fetching {host}\n')
# 跟服务器建立连接
reader, writer = await asyncio.open_connection(host, 80)
# 构造请求行和请求头
writer.write(b'GET / HTTP/1.1\r\n')
writer.write(f'Host: {host}\r\n'.encode())
writer.write(b'\r\n')
# 清空缓存区(发送请求)
await writer.drain()
# 接收服务器的响应(读取响应行和响应头)
line = await reader.readline()
while line != b'\r\n':
print(line.decode().rstrip())
line = await reader.readline()
print('\n')
writer.close()
def main():
"""主函数"""
urls = ('www.sohu.com', 'www.douban.com', 'www.163.com')
# 获取系统默认的事件循环
loop = asyncio.get_event_loop()
# 用生成式语法构造一个包含多个协程对象的列表
tasks = [fetch(url) for url in urls]
# 通过asyncio模块的wait函数将协程列表包装成Task(Future子类)并等待其执行完成
# 通过事件循环的run_until_complete方法运行任务直到Future完成并返回它的结果
futures = asyncio.wait(tasks)
print(futures, type(futures))
loop.run_until_complete(futures)
loop.close()
if __name__ == '__main__':
main()
"""
协程(coroutine)- 可以在需要时进行切换的相互协作的子程序
"""
import asyncio
from example_of_multiprocess import is_prime
def num_generator(m, n):
"""指定范围的数字生成器"""
for num in range(m, n + 1):
print(f'generate number: {num}')
yield num
async def prime_filter(m, n):
"""素数过滤器"""
primes = []
for i in num_generator(m, n):
if is_prime(i):
print('Prime =>', i)
primes.append(i)
await asyncio.sleep(0.001)
return tuple(primes)
async def square_mapper(m, n):
"""平方映射器"""
squares = []
for i in num_generator(m, n):
print('Square =>', i * i)
squares.append(i * i)
await asyncio.sleep(0.001)
return squares
def main():
"""主函数"""
loop = asyncio.get_event_loop()
start, end = 1, 100
futures = asyncio.gather(prime_filter(start, end), square_mapper(start, end))
futures.add_done_callback(lambda x: print(x.result()))
loop.run_until_complete(futures)
loop.close()
if __name__ == '__main__':
main()
"""
用下面的命令运行程序并查看执行时间,例如:
time python3 example05.py
real 0m20.657s
user 1m17.749s
sys 0m0.158s
使用多进程后实际执行时间为20.657秒,而用户时间1分17.749秒约为实际执行时间的4倍
这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU
"""
import concurrent.futures
import math
PRIMES = [
1116281,
1297337,
104395303,
472882027,
533000389,
817504243,
982451653,
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419
] * 5
def is_prime(num):
"""判断素数"""
assert num > 0
if num % 2 == 0:
return False
for i in range(3, int(math.sqrt(num)) + 1, 2):
if num % i == 0:
return False
return num != 1
def main():
"""主函数"""
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
aiohttp==3.5.4
aiomysql==0.0.20
asn1crypto==0.24.0
async-timeout==3.0.1
attrs==19.1.0
certifi==2019.3.9
cffi==1.12.2
chardet==3.0.4
cryptography==2.6.1
idna==2.8
multidict==4.5.2
pycparser==2.19
PyMySQL==0.9.2
requests==2.21.0
six==1.12.0
tornado==5.1.1
urllib3==1.24.1
yarl==1.3.0
<!-- chat.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tornado聊天室</title>
</head>
<body>
<h1>聊天室</h1>
<hr>
<div>
<textarea id="contents" rows="20" cols="120" readonly></textarea>
</div>
<div class="send">
<input type="text" id="content" size="50">
<input type="button" id="send" value="发送">
</div>
<p>
<a id="quit" href="javascript:void(0);">退出聊天室</a>
</p>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
$(function() {
// 将内容追加到指定的文本区
function appendContent($ta, message) {
var contents = $ta.val();
contents += '\n' + message;
$ta.val(contents);
$ta[0].scrollTop = $ta[0].scrollHeight;
}
// 通过WebSocket发送消息
function sendMessage() {
message = $('#content').val().trim();
if (message.length > 0) {
ws.send(message);
appendContent($('#contents'), '我说:' + message);
$('#content').val('');
}
}
// 创建WebSocket对象
var ws= new WebSocket('ws://localhost:8888/chat');
// 连接建立后执行的回调函数
ws.onopen = function(evt) {
$('#contents').val('~~~欢迎您进入聊天室~~~');
};
// 收到消息后执行的回调函数
ws.onmessage = function(evt) {
appendContent($('#contents'), evt.data);
};
// 为发送按钮绑定点击事件回调函数
$('#send').on('click', sendMessage);
// 为文本框绑定按下回车事件回调函数
$('#content').on('keypress', function(evt) {
keycode = evt.keyCode || evt.which;
if (keycode == 13) {
sendMessage();
}
});
// 为退出聊天室超链接绑定点击事件回调函数
$('#quit').on('click', function(evt) {
ws.close();
location.href = '/login';
});
});
</script>
</body>
</html>
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tornado聊天室</title>
<style>
.hint { color: red; font-size: 0.8em; }
</style>
</head>
<body>
<div>
<div id="container">
<h1>进入聊天室</h1>
<hr>
<p class="hint">{{hint}}</p>
<form method="post" action="/login">
<label>昵称:</label>
<input type="text" placeholder="请输入你的昵称" name="nickname">
<button type="submit">登录</button>
</form>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新闻列表</title>
</head>
<body>
<h1>新闻列表</h1>
<hr>
{% for news in newslist %}
<div>
<img src="{{news['picUrl']}}" alt="">
<p>{{news['title']}}</p>
</div>
{% end %}
</body>
</html>
\ No newline at end of file
/**
* admin.css
*/
/*
fixed-layout 固定头部和边栏布局
*/
html,
body {
height: 100%;
overflow: hidden;
}
ul {
margin-top: 0;
}
.admin-icon-yellow {
color: #ffbe40;
}
.admin-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1500;
font-size: 1.4rem;
margin-bottom: 0;
}
.admin-header-list a:hover :after {
content: none;
}
.admin-main {
position: relative;
height: 100%;
padding-top: 51px;
background: #f3f3f3;
}
.admin-menu {
position: fixed;
z-index: 10;
bottom: 30px;
right: 20px;
}
.admin-sidebar {
width: 260px;
min-height: 100%;
float: left;
border-right: 1px solid #cecece;
}
.admin-sidebar.am-active {
z-index: 1600;
}
.admin-sidebar-list {
margin-bottom: 0;
}
.admin-sidebar-list li a {
color: #5c5c5c;
padding-left: 24px;
}
.admin-sidebar-list li:first-child {
border-top: none;
}
.admin-sidebar-sub {
margin-top: 0;
margin-bottom: 0;
box-shadow: 0 16px 8px -15px #e2e2e2 inset;
background: #ececec;
padding-left: 24px;
}
.admin-sidebar-sub li:first-child {
border-top: 1px solid #dedede;
}
.admin-sidebar-panel {
margin: 10px;
}
.admin-content {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
background: #fff;
}
.admin-content,
.admin-sidebar {
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.admin-content-body {
-webkit-box-flex: 1;
-webkit-flex: 1 0 auto;
-ms-flex: 1 0 auto;
flex: 1 0 auto;
}
.admin-content-footer {
font-size: 85%;
color: #777;
}
.admin-content-list {
border: 1px solid #e9ecf1;
margin-top: 0;
}
.admin-content-list li {
border: 1px solid #e9ecf1;
border-width: 0 1px;
margin-left: -1px;
}
.admin-content-list li:first-child {
border-left: none;
}
.admin-content-list li:last-child {
border-right: none;
}
.admin-content-table a {
color: #535353;
}
.admin-content-file {
margin-bottom: 0;
color: #666;
}
.admin-content-file p {
margin: 0 0 5px 0;
font-size: 1.4rem;
}
.admin-content-file li {
padding: 10px 0;
}
.admin-content-file li:first-child {
border-top: none;
}
.admin-content-file li:last-child {
border-bottom: none;
}
.admin-content-file li .am-progress {
margin-bottom: 4px;
}
.admin-content-file li .am-progress-bar {
line-height: 14px;
}
.admin-content-task {
margin-bottom: 0;
}
.admin-content-task li {
padding: 5px 0;
border-color: #eee;
}
.admin-content-task li:first-child {
border-top: none;
}
.admin-content-task li:last-child {
border-bottom: none;
}
.admin-task-meta {
font-size: 1.2rem;
color: #999;
}
.admin-task-bd {
font-size: 1.4rem;
margin-bottom: 5px;
}
.admin-content-comment {
margin-bottom: 0;
}
.admin-content-comment .am-comment-bd {
font-size: 1.4rem;
}
.admin-content-pagination {
margin-bottom: 0;
}
.admin-content-pagination li a {
padding: 4px 8px;
}
@media only screen and (min-width: 641px) {
.admin-sidebar {
display: block;
position: static;
background: none;
}
.admin-offcanvas-bar {
position: static;
width: auto;
background: none;
-webkit-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
overflow-y: visible;
min-height: 100%;
}
.admin-offcanvas-bar:after {
content: none;
}
}
@media only screen and (max-width: 640px) {
.admin-sidebar {
width: inherit;
}
.admin-offcanvas-bar {
background: #f3f3f3;
}
.admin-offcanvas-bar:after {
background: #BABABA;
}
.admin-sidebar-list a:hover, .admin-sidebar-list a:active{
-webkit-transition: background-color .3s ease;
-moz-transition: background-color .3s ease;
-ms-transition: background-color .3s ease;
-o-transition: background-color .3s ease;
transition: background-color .3s ease;
background: #E4E4E4;
}
.admin-content-list li {
padding: 10px;
border-width: 1px 0;
margin-top: -1px;
}
.admin-content-list li:first-child {
border-top: none;
}
.admin-content-list li:last-child {
border-bottom: none;
}
.admin-form-text {
text-align: left !important;
}
}
/*
* user.html css
*/
.user-info {
margin-bottom: 15px;
}
.user-info .am-progress {
margin-bottom: 4px;
}
.user-info p {
margin: 5px;
}
.user-info-order {
font-size: 1.4rem;
}
/*
* errorLog.html css
*/
.error-log .am-pre-scrollable {
max-height: 40rem;
}
/*
* table.html css
*/
.table-main {
font-size: 1.4rem;
padding: .5rem;
}
.table-main button {
background: #fff;
}
.table-check {
width: 30px;
}
.table-id {
width: 50px;
}
@media only screen and (max-width: 640px) {
.table-select {
margin-top: 10px;
margin-left: 5px;
}
}
/*
gallery.html css
*/
.gallery-list li {
padding: 10px;
}
.gallery-list a {
color: #666;
}
.gallery-list a:hover {
color: #3bb4f2;
}
.gallery-title {
margin-top: 6px;
font-size: 1.4rem;
}
.gallery-desc {
font-size: 1.2rem;
margin-top: 4px;
}
/*
404.html css
*/
.page-404 {
background: #fff;
border: none;
width: 200px;
margin: 0 auto;
}
.am-datatable-hd{margin-bottom:10px}.am-datatable-hd label{font-weight:400}.am-datatable-filter{text-align:right}.am-datatable-filter input{margin-left:.5em}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after{position:absolute;top:50%;margin-top:-12px;right:8px;display:block;font-weight:400}table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after{position:absolute;top:50%;margin-top:-12px;right:8px;display:block;opacity:.5;font-weight:400}table.dataTable thead .sorting:after{opacity:.2;content:"\f0dc"}table.dataTable thead .sorting_asc:after{content:"\f15d"}table.dataTable thead .sorting_desc:after{content:"\f15e"}div.DTFC_LeftBodyWrapper table.dataTable thead .sorting:after,div.DTFC_LeftBodyWrapper table.dataTable thead .sorting_asc:after,div.DTFC_LeftBodyWrapper table.dataTable thead .sorting_desc:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting_asc:after,div.DTFC_RightBodyWrapper table.dataTable thead .sorting_desc:after,div.dataTables_scrollBody table.dataTable thead .sorting:after,div.dataTables_scrollBody table.dataTable thead .sorting_asc:after,div.dataTables_scrollBody table.dataTable thead .sorting_desc:after{display:none}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}table.dataTable thead>tr>th{padding-right:30px}table.dataTable th:active{outline:none}table.dataTable.table-condensed thead>tr>th{padding-right:20px}table.dataTable.table-condensed thead .sorting:after,table.dataTable.table-condensed thead .sorting_asc:after,table.dataTable.table-condensed thead .sorting_desc:after{top:6px;right:6px}div.dataTables_scrollHead table{margin-bottom:0!important;border-bottom-left-radius:0;border-bottom-right-radius:0}div.DTFC_LeftHeadWrapper table thead tr:last-child td:first-child,div.DTFC_LeftHeadWrapper table thead tr:last-child th:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child td:first-child,div.DTFC_RightHeadWrapper table thead tr:last-child th:first-child,div.dataTables_scrollHead table thead tr:last-child td:first-child,div.dataTables_scrollHead table thead tr:last-child th:first-child{border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.dataTables_scrollBody table{border-top:none;margin-top:0!important;margin-bottom:0!important}div.DTFC_LeftBodyWrapper tbody tr:first-child td,div.DTFC_LeftBodyWrapper tbody tr:first-child th,div.DTFC_RightBodyWrapper tbody tr:first-child td,div.DTFC_RightBodyWrapper tbody tr:first-child th,div.dataTables_scrollBody tbody tr:first-child td,div.dataTables_scrollBody tbody tr:first-child th{border-top:none}div.dataTables_scrollFoot table{margin-top:0!important;border-top:none}table.table-bordered.dataTable{border-collapse:separate!important}table.table-bordered thead td,table.table-bordered thead th{border-left-width:0;border-top-width:0}table.table-bordered tbody td,table.table-bordered tbody th,table.table-bordered tfoot td,table.table-bordered tfoot th{border-left-width:0;border-bottom-width:0}table.table-bordered td:last-child,table.table-bordered th:last-child{border-right-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}.table.dataTable tbody tr.active td,.table.dataTable tbody tr.active th{background-color:#08c;color:#fff}.table.dataTable tbody tr.active:hover td,.table.dataTable tbody tr.active:hover th{background-color:#0075b0!important}.table.dataTable tbody tr.active td>a,.table.dataTable tbody tr.active th>a{color:#fff}.table-striped.dataTable tbody tr.active:nth-child(odd) td,.table-striped.dataTable tbody tr.active:nth-child(odd) th{background-color:#017ebc}table.DTTT_selectable tbody tr{cursor:pointer}div.DTTT .btn:hover{text-decoration:none!important}ul.DTTT_dropdown.dropdown-menu{z-index:2003}ul.DTTT_dropdown.dropdown-menu a{color:#333!important}ul.DTTT_dropdown.dropdown-menu li{position:relative}ul.DTTT_dropdown.dropdown-menu li:hover a{background-color:#08c;color:#fff!important}div.DTTT_collection_background{z-index:2002}div.DTTT_print_info,div.dataTables_processing{top:50%;left:50%;text-align:center;background-color:#fff}div.DTTT_print_info{color:#333;padding:10px 30px;opacity:.95;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0,0,0,.5);box-shadow:0 3px 7px rgba(0,0,0,.5);position:fixed;width:400px;height:150px;margin-left:-200px;margin-top:-75px}div.DTTT_print_info h6{font-weight:400;font-size:28px;line-height:28px;margin:1em}div.DTTT_print_info p{font-size:14px;line-height:20px}div.dataTables_processing{position:absolute;width:100%;height:60px;margin-left:-50%;margin-top:-25px;padding-top:20px;padding-bottom:20px;font-size:1.2em;background:-webkit-gradient(linear,left top,right top,color-stop(0%,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,.9)),color-stop(75%,rgba(255,255,255,.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0%,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,from(rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,.9)),color-stop(75%,rgba(255,255,255,.9)),to(rgba(255,255,255,0)));background:linear-gradient(to right,rgba(255,255,255,0) 0%,rgba(255,255,255,.9) 25%,rgba(255,255,255,.9) 75%,rgba(255,255,255,0) 100%)}div.DTFC_LeftHeadWrapper table{background-color:#fff}div.DTFC_LeftFootWrapper table{background-color:#fff;margin-bottom:0}div.DTFC_RightHeadWrapper table{background-color:#fff}div.DTFC_RightFootWrapper table,table.DTFC_Cloned tr.even{background-color:#fff;margin-bottom:0}div.DTFC_LeftHeadWrapper table,div.DTFC_RightHeadWrapper table{border-bottom:none!important;margin-bottom:0!important;border-top-right-radius:0!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}div.DTFC_LeftBodyWrapper table,div.DTFC_RightBodyWrapper table{border-top:none;margin:0!important}div.DTFC_LeftFootWrapper table,div.DTFC_RightFootWrapper table{border-top:none;margin-top:0!important}div.FixedHeader_Cloned table{margin:0!important}.am-datatable-pager{margin-top:0;margin-bottom:0}.am-datatable-info{padding-top:6px;color:#555;font-size:1.4rem}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before{top:8px;left:4px;height:16px;width:16px;display:block;position:absolute;color:#fff;border:2px solid #fff;border-radius:16px;text-align:center;line-height:14px;-webkit-box-shadow:0 0 3px #444;box-shadow:0 0 3px #444;-webkit-box-sizing:content-box;box-sizing:content-box;content:'+';background-color:#31b131}table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child.dataTables_empty:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child.dataTables_empty:before{display:none}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before{content:'-';background-color:#d33333}table.dataTable.dtr-inline.collapsed>tbody>tr.child td:before{display:none}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td:first-child:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th:first-child:before{top:5px;left:4px;height:14px;width:14px;border-radius:14px;line-height:12px}table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:16px;width:16px;margin-top:-10px;margin-left:-10px;display:block;position:absolute;color:#fff;border:2px solid #fff;border-radius:16px;text-align:center;line-height:14px;-webkit-box-shadow:0 0 3px #666;box-shadow:0 0 3px #666;-webkit-box-sizing:content-box;box-sizing:content-box;content:'+';background-color:#5eb95e}table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:'-';background-color:#dd514c}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:0 0!important}table.dataTable>tbody>tr.child ul{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:700}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
/*!
* FullCalendar v0.0.0 Print Stylesheet
* Docs & License: http://fullcalendar.io/
* (c) 2016 Adam Shaw
*/
/*
* Include this stylesheet on your page to get a more printer-friendly calendar.
* When including this stylesheet, use the media='print' attribute of the <link> tag.
* Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
*/
.fc {
max-width: 100% !important;
}
/* Global Event Restyling
--------------------------------------------------------------------------------------------------*/
.fc-event {
background: #fff !important;
color: #000 !important;
page-break-inside: avoid;
}
.fc-event .fc-resizer {
display: none;
}
/* Table & Day-Row Restyling
--------------------------------------------------------------------------------------------------*/
.fc th,
.fc td,
.fc hr,
.fc thead,
.fc tbody,
.fc-row {
border-color: #ccc !important;
background: #fff !important;
}
/* kill the overlaid, absolutely-positioned components */
/* common... */
.fc-bg,
.fc-bgevent-skeleton,
.fc-highlight-skeleton,
.fc-helper-skeleton,
/* for timegrid. within cells within table skeletons... */
.fc-bgevent-container,
.fc-business-container,
.fc-highlight-container,
.fc-helper-container {
display: none;
}
/* don't force a min-height on rows (for DayGrid) */
.fc tbody .fc-row {
height: auto !important; /* undo height that JS set in distributeHeight */
min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
}
.fc tbody .fc-row .fc-content-skeleton {
position: static; /* undo .fc-rigid */
padding-bottom: 0 !important; /* use a more border-friendly method for this... */
}
.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
}
.fc tbody .fc-row .fc-content-skeleton table {
/* provides a min-height for the row, but only effective for IE, which exaggerates this value,
making it look more like 3em. for other browers, it will already be this tall */
height: 1em;
}
/* Undo month-view event limiting. Display all events and hide the "more" links
--------------------------------------------------------------------------------------------------*/
.fc-more-cell,
.fc-more {
display: none !important;
}
.fc tr.fc-limited {
display: table-row !important;
}
.fc td.fc-limited {
display: table-cell !important;
}
.fc-popover {
display: none; /* never display the "more.." popover in print mode */
}
/* TimeGrid Restyling
--------------------------------------------------------------------------------------------------*/
/* undo the min-height 100% trick used to fill the container's height */
.fc-time-grid {
min-height: 0 !important;
}
/* don't display the side axis at all ("all-day" and time cells) */
.fc-agenda-view .fc-axis {
display: none;
}
/* don't display the horizontal lines */
.fc-slats,
.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
display: none !important; /* important overrides inline declaration */
}
/* let the container that holds the events be naturally positioned and create real height */
.fc-time-grid .fc-content-skeleton {
position: static;
}
/* in case there are no events, we still want some height */
.fc-time-grid .fc-content-skeleton table {
height: 4em;
}
/* kill the horizontal spacing made by the event container. event margins will be done below */
.fc-time-grid .fc-event-container {
margin: 0 !important;
}
/* TimeGrid *Event* Restyling
--------------------------------------------------------------------------------------------------*/
/* naturally position events, vertically stacking them */
.fc-time-grid .fc-event {
position: static !important;
margin: 3px 2px !important;
}
/* for events that continue to a future day, give the bottom border back */
.fc-time-grid .fc-event.fc-not-end {
border-bottom-width: 1px !important;
}
/* indicate the event continues via "..." text */
.fc-time-grid .fc-event.fc-not-end:after {
content: "...";
}
/* for events that are continuations from previous days, give the top border back */
.fc-time-grid .fc-event.fc-not-start {
border-top-width: 1px !important;
}
/* indicate the event is a continuation via "..." text */
.fc-time-grid .fc-event.fc-not-start:before {
content: "...";
}
/* time */
/* undo a previous declaration and let the time text span to a second line */
.fc-time-grid .fc-event .fc-time {
white-space: normal !important;
}
/* hide the the time that is normally displayed... */
.fc-time-grid .fc-event .fc-time span {
display: none;
}
/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
.fc-time-grid .fc-event .fc-time:after {
content: attr(data-full);
}
/* Vertical Scroller & Containers
--------------------------------------------------------------------------------------------------*/
/* kill the scrollbars and allow natural height */
.fc-scroller,
.fc-day-grid-container, /* these divs might be assigned height, which we need to cleared */
.fc-time-grid-container { /* */
overflow: visible !important;
height: auto !important;
}
/* kill the horizontal border/padding used to compensate for scrollbars */
.fc-row {
border: 0 !important;
margin: 0 !important;
}
/* Button Controls
--------------------------------------------------------------------------------------------------*/
.fc-button-group,
.fc button {
display: none; /* don't display any button-related controls */
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Amaze UI Admin index Examples</title>
<meta name="description" content="这是一个 index 页面">
<meta name="keywords" content="index">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="renderer" content="webkit">
<meta http-equiv="Cache-Control" content="no-siteapp" />
<link rel="icon" type="image/png" href="../i/favicon.png">
<link rel="apple-touch-icon-precomposed" href="../i/app-icon72x72@2x.png">
<meta name="apple-mobile-web-app-title" content="Amaze UI" />
<link rel="stylesheet" href="../css/amazeui.min.css" />
<link rel="stylesheet" href="../css/amazeui.datatables.min.css" />
<link rel="stylesheet" href="../css/app.css">
<script src="../js/jquery.min.js"></script>
</head>
<body data-type="login">
<script src="../js/theme.js"></script>
<div class="am-g tpl-g">
<!-- 风格切换 -->
<div class="tpl-skiner">
<div class="tpl-skiner-toggle am-icon-cog">
</div>
<div class="tpl-skiner-content">
<div class="tpl-skiner-content-title">
选择主题
</div>
<div class="tpl-skiner-content-bar">
<span class="skiner-color skiner-white" data-color="theme-white"></span>
<span class="skiner-color skiner-black" data-color="theme-black"></span>
</div>
</div>
</div>
<div class="tpl-login">
<div class="tpl-login-content">
<div class="tpl-login-logo"></div>
<form class="am-form tpl-form-line-form">
<div class="am-form-group">
<input type="text" class="tpl-form-input" id="user-name" placeholder="请输入账号">
</div>
<div class="am-form-group">
<input type="password" class="tpl-form-input" id="user-name" placeholder="请输入密码">
</div>
<div class="am-form-group tpl-login-remember-me">
<input id="remember-me" type="checkbox">
<label for="remember-me">记住密码</label>
</div>
<div class="am-form-group">
<button type="button" class="am-btn am-btn-primary am-btn-block tpl-btn-bg-color-success tpl-login-btn">提交</button>
</div>
</form>
</div>
</div>
</div>
<script src="../js/amazeui.min.js"></script>
<script src="../js/app.js"></script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Amaze UI Admin index Examples</title>
<meta name="description" content="这是一个 index 页面">
<meta name="keywords" content="index">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="renderer" content="webkit">
<meta http-equiv="Cache-Control" content="no-siteapp"/>
<link rel="icon" type="image/png" href="../i/favicon.png">
<link rel="apple-touch-icon-precomposed" href="../i/app-icon72x72@2x.png">
<meta name="apple-mobile-web-app-title" content="Amaze UI"/>
<link rel="stylesheet" href="../css/amazeui.min.css"/>
<link rel="stylesheet" href="../css/amazeui.datatables.min.css"/>
<link rel="stylesheet" href="../css/app.css">
<script src="../js/jquery.min.js"></script>
</head>
<body data-type="login">
<script src="../js/theme.js"></script>
<div class="am-g tpl-g">
<!-- 风格切换 -->
<div class="tpl-skiner">
<div class="tpl-skiner-toggle am-icon-cog">
</div>
<div class="tpl-skiner-content">
<div class="tpl-skiner-content-title">
选择主题
</div>
<div class="tpl-skiner-content-bar">
<span class="skiner-color skiner-white" data-color="theme-white"></span>
<span class="skiner-color skiner-black" data-color="theme-black"></span>
</div>
</div>
</div>
<div class="tpl-login">
<div class="tpl-login-content">
<div class="tpl-login-title">注册用户</div>
<span class="tpl-login-content-info">创建一个新的用户</span>
<form class="am-form tpl-form-line-form">
<div class="am-form-group">
<input type="text" class="tpl-form-input" id="user-name" placeholder="邮箱">
</div>
<div class="am-form-group">
<input type="text" class="tpl-form-input" id="user-name" placeholder="用户名">
</div>
<div class="am-form-group">
<input type="password" class="tpl-form-input" id="user-name" placeholder="请输入密码">
</div>
<div class="am-form-group">
<input type="password" class="tpl-form-input" id="user-name" placeholder="再次输入密码">
</div>
<div class="am-form-group tpl-login-remember-me">
<input id="remember-me" type="checkbox">
<label for="remember-me">我已阅读并同意 <a href="javascript:;">《用户注册协议》</a></label>
</div>
<div class="am-form-group">
<button type="button"
class="am-btn am-btn-primary am-btn-block tpl-btn-bg-color-success tpl-login-btn">提交
</button>
</div>
</form>
</div>
</div>
</div>
<script src="../js/amazeui.min.js"></script>
<script src="../js/app.js"></script>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
var saveSelectColor = {
'Name': 'SelcetColor',
'Color': 'theme-black'
}
// 判断用户是否已有自己选择的模板风格
if (storageLoad('SelcetColor')) {
$('body').attr('class', storageLoad('SelcetColor').Color)
} else {
storageSave(saveSelectColor);
$('body').attr('class', 'theme-black')
}
// 本地缓存
function storageSave(objectData) {
localStorage.setItem(objectData.Name, JSON.stringify(objectData));
}
function storageLoad(objectName) {
if (localStorage.getItem(objectName)) {
return JSON.parse(localStorage.getItem(objectName))
} else {
return false
}
}
\ No newline at end of file
"""
backend_server.py - 后台服务器
"""
import asyncio
import os
import threading
import aiomysql
import tornado.web
from tornado.ioloop import IOLoop
from tornado.platform.asyncio import AnyThreadEventLoopPolicy
from service.handlers.handlers_for_charts import send_data
from service.handlers.handlers_for_nav import IndexHandler
from service.handlers.handlers_for_tables import EmpHandler
from service.handlers.handlers_for_charts import ChartHandler
async def connect_mysql():
return await aiomysql.connect(
host='120.77.222.217',
port=3306,
db='hrs',
charset='utf8',
use_unicode=True,
user='root',
password='123456',
)
def main():
# Tornado 5开始使用线程必须指定事件循环的策略否则无法启动线程
asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy())
# 启动通过WebSocket长连接发送数据的线程
threading.Thread(target=send_data, daemon=True, args=(5, )).start()
app = tornado.web.Application(
handlers=[
(r'/', IndexHandler),
(r'/api/emps', EmpHandler),
(r'/ws/charts', ChartHandler),
],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
static_path=os.path.join(os.path.dirname(__file__), 'assets'),
cookie_secret='MWM2MzEyOWFlOWRiOWM2MGMzZThhYTk0ZDNlMDA0OTU=',
mysql=IOLoop.current().run_sync(connect_mysql),
debug=True
)
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
aiomysql==0.0.20
asn1crypto==0.24.0
cffi==1.12.2
cryptography==2.6.1
pycparser==2.19
PyMySQL==0.9.2
six==1.12.0
tornado==5.1.1
import tornado
class IndexHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
return await self.render('index.html')
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment