Tornado入门
慕课网:https://coding.imooc.com/class/290.html
工具列表
Peewee
简介:Peewee is a simple and small ORM. It has few (but expressive) concepts, making it easy to learn and intuitive to use.
官网:http://docs.peewee-orm.com/en/latest/index.html
peewee-async
peewee-async is a library providing asynchronous interface powered by asyncio for peewee ORM.
官网:https://peewee-async.readthedocs.io/en/latest/
WTForms
提供表单验证
官网地址:https://wtforms.readthedocs.io/en/stable/
aiofiles
github: https://github.com/Tinche/aiofiles
表单的增删改查
HTML

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户注册案例</title> </head> <body> <form action="/register" method="post" enctype="multipart/form-data"> {% module xsrf_form_html() %} <!--隐藏表单项,应用场景:某些数据对用户而言是没有意义的,用户不需要看到这些数据,但是服务器又需要,那么这--> <!--时候就可以使用隐藏表单项--> <input type="hidden" name="flag" value="true"/> <table width="400px" height="400px"> <tr> <!--colspan:单元格跨几列--> <td colspan="2"> <h4>用户注册</h4> <hr/> </td> </tr> <tr> <td>用户名:</td> <td> <!--用户名:普通文本输入框--> <input type="text" name="username" value="请输入用户名..."/> </td> </tr> <tr> <td>密码:</td> <td> <!--密码:密码框--> <input type="password" name="password"/> </td> </tr> <tr> <td>性别:</td> <td> <!--性别:单选框,单选框input标签,type=radio 同一组的单选框只能选择其中一个 单选框如果需要分组,那么必须保持name的属性值一致。如果需要默认选中某一个,那么需要添加 checked属性--> 男:<input type="radio" name="sex" value="man" checked/> 女:<input type="radio" name="sex" value="woman"/> </td> </tr> <tr> <td>爱好:</td> <td> <!--兴趣爱好:复选框--> 游泳:<input type="checkbox" name="hobbit" value="swimm"/> 上网:<input type="checkbox" name="hobbit" value="surf"/> 电影:<input type="checkbox" name="hobbit" value="movie"/> 看书:<input type="checkbox" name="hobbit" value="read"/> </td> </tr> <tr> <td>学历:</td> <td> <!--学历:下拉框,下拉框使用的是select标签--> <select name="education"> <option value="bs">博士</option> <option value="ss">硕士</option> <option value="bk">本科</option> <option value="dz">大专</option> </select> </td> </tr> <tr> <td>照片:</td> <td> <!--照片:文件表单项--> <input type="file" name="image"/> </td> </tr> <tr> <td>个人简介:</td> <td> <!--个人简介:文本域 标签:textarea--> <textarea cols="40" rows="10" name="introduction"></textarea> </td> </tr> <tr align="center"> <!--colspan:单元格跨几列--> <td colspan="2"> <!--普通按钮--> <input type="button" value="普通按钮"/> <!--重置表单项--> <input type="reset" value="重置"/> <!--提交按钮:提交表单的数据到服务器上--> <input type="submit" value="注册"/> </td> </tr> </table> </form> </body> </html>
handler.py

class RegisterHandler(BaseHandler): def get(self): return self.render("register.html") def post(self): username = self.get_argument('username') password = self.get_argument('password') sex = self.get_argument('sex') hobbits = self.get_arguments('hobbit') education = self.get_argument('education') introduction = self.get_argument('introduction') img_files = self.request.files.get('image') if img_files: img_file = img_files[0] handle_uploaded_file(img_file) context = { 'username': username, 'password': password, 'sex': sex, 'hobbits': hobbits, 'education': education, 'introduction': introduction, } return self.render("register.html", **context)
正式项目案例
项目结构
server.py 管理服务器
# coding:utf-8 import tornado.locks import tornado.web import tornado.ioloop import tornado.options import tornado.httpserver from tornado.options import options, define import config import aiomysql from urls import urls define("port", default=8000, type=int, help="run server on the given port") class Application(tornado.web.Application): def __init__(self, db): handlers = urls settings = config.settings self.db = db super(Application, self).__init__(handlers, **settings) async def main(): options.log_file_prefix = config.log_path options.logging = config.log_level tornado.options.parse_command_line() pool = await aiomysql.create_pool( **config.mysql_base_options, ) async with pool.acquire() as db: app = Application(db) app.listen(options.port) # In this demo the server will simply run until interrupted # with Ctrl-C, but if you want to shut down more gracefully, # call shutdown_event.set(). shutdown_event = tornado.locks.Event() await shutdown_event.wait() pool.close() await pool.wait_closed() if __name__ == "__main__": tornado.ioloop.IOLoop.current().run_sync(main)
config.py管理项目配置
# coding:utf-8 import os BASE_ROOT = os.path.dirname(__file__) MEDIA_ROOT = os.path.join(BASE_ROOT, 'media') # Application配置参数 settings = dict( template_path=os.path.join(BASE_ROOT, "templates"), static_path=os.path.join(BASE_ROOT, "static"), cookie_secret="FhLXI+BRRomtuaG47hoXEg3JCdi0BUi8vrpWmoxaoyI=", xsrf_cookies=True, login_url='/login', debug=True ) # 数据库配置参数 mysql_options = dict( host="127.0.0.1", port=3306, user="root", password="123456", db="my_db", ) # Redis配置参数 # redis_options = dict( # host="127.0.0.1", # port=6379 # ) # 日志配置 log_path = os.path.join(os.path.dirname(__file__), "logs/log") log_level = "debug" # 密码加密密钥 passwd_hash_key = "nlgCjaTXQX2jpupQFQLoQo5N4OkEmkeHsHD9+BBx2WQ="
BaseHandler主要用来被继承
# coding:utf-8 import tornado import tornado.util from tornado.web import RequestHandler class NoResultError(Exception): pass class BaseHandler(tornado.web.RequestHandler): def row_to_obj(self, row, cur): """Convert a SQL row to an object supporting dict and attribute access.""" obj = tornado.util.ObjectDict() for val, desc in zip(row, cur.description): obj[desc[0]] = val return obj async def execute(self, stmt, *args): """Execute a SQL statement. Must be called with ``await self.execute(...)`` """ async with self.application.db.cursor() as cur: await cur.execute(stmt, args) async def query(self, query, *args): """Query for a list of results. Typical usage:: results = await self.query(...) Or:: for row in await self.query(...) """ async with self.application.db.cursor() as cur: await cur.execute(query, args) return [self.row_to_obj(row, cur) for row in await cur.fetchall()] async def query_one(self, query, *args): """Query for exactly one result. Raises NoResultError if there are no results, or ValueError if there are more than one. """ results = await self.query(query, *args) if len(results) == 0: raise NoResultError() elif len(results) > 1: raise ValueError("Expected 1 result, got %d" % len(results)) return results[0] async def prepare(self): # get_current_user cannot be a coroutine, so set # self.current_user in prepare instead. user_id = self.get_secure_cookie("user_id") if user_id: self.current_user = await self.query_one( "SELECT * FROM zd_user WHERE id = %s", user_id ) # async def any_author_exists(self): # return bool(await self.query("SELECT * FROM authors LIMIT 1"))
IndexHandler主要用来处理业务逻辑,如:用户登录逻辑
import tornado.web from handlers.BaseHandler import BaseHandler class Index(BaseHandler): @tornado.web.authenticated async def get(self): self.render("index.html") # 渲染主页模板,并返回给客户端。 # def handle_uploaded_file(img_file): # destination = os.path.join(config.MEDIA_ROOT, img_file['filename']) # if os.path.exists(destination): # return # with open(destination, 'wb+') as file: # file.write(img_file['body']) class LoginHandler(BaseHandler): def get(self): self.render('login.html') async def post(self): username = self.get_argument('username') password = self.get_argument('password') query = "SELECT * FROM zd_user where login_name=%s" user = await self.query_one(query, username) if user: self.set_secure_cookie("user_id", user.id) self.redirect('/index') else: self.write({'msg': 'fail_user'})
JS加载JSON数据
JSON.parse('{% raw resources %}')
URL路由传参
# urls.py from tornado.web import url from apps.pile.views import * urlpatterns = [ url("/pile/open", PileHandler, {'command': "open"}), url("/pile/close", PileHandler, {'command': "close"}), ] # views.py class PileHandler(BaseHandler): # 获取动态设置的参数command async def initialize(self, command): # 动态参数要与url路由中设置的参数必须一样 self.command = command async def get(self, *args, **kwargs): name = self.get_query_argument("name") await self.finish(json.dumps({"command": self.command}, default=json_serial)) async def post(self, *args, **kwargs): name = self.get_body_argument("name") await self.finish(json.dumps({"a": name}, default=json_serial))