Tornado 用户注册&登录&登出(User regist&login&logout)
############################################
@Tornado 用户认证(User authentication )示例文档
############################################
##############################################################
环境:
Python 3.6.5 (default, Jun 25 2018, 17:02:26)
tornado 6.0.3
Tornado-MySQL 0.5.1
mysql Ver 14.14 Distrib 5.6.36, for Linux (x86_64) using EditLine wrapper
##############################################################
##################################################################
#HTML 效果图
<!--tornado/template/base.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="description" content="GotIT Manage Network Automatically">
<meta name="keywords" content="GotIT">
<meta name="author" content="GotIT Web Dev Team">
<link rel="shortcut icon" href="/static/icon/favicon.ico" type="image/x-icon" />
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<!--Base模板里的title标签,如果有传入替代标签就用替代的,没有就用默认的-->
<title>{% block title %}Please checkout your page-title!{% end %}</title>
</head>
<body>
<!--模板里的body标签,用于body标签代入替换-->
{% block body %}<p>Set up you body-block first!</p>{% end %}
<!--模板里的js标签,用于js标签代入替换-->
{% block js %}{% end %}
<!--模板里的css标签,用于css标签代入替换-->
{% block css %}{% end %}
</body>
</html>
<!--tornado/template/regis.html-->
{% extends "base.html"%} <!--调用模板,把block标签的部分插入模板中-->
{% block title %}User Regis{% end %} //title
{% block body %} //body
<div id="container">
<form action="/regis" method="post"> <!--注册Form
<!--xsrf 参数,render模块会把生成的xsrf的html替换到这里,在提交Form时
这个隐藏的xsrf信息用于Tornado服务端校验,以确保本次提交的Form信息未被黑客篡改。-->
{% module xsrf_form_html() %}
<input name="uname" type="text" placeholder="uname" required> <!--input类型,输入值后 uname=xxx -->
<input name="upass" type="password" placeholder="upass" required> <!--input类型,输入值后 upass=xxx -->
<input name="umail" type="email" placeholder="umail" autocomplete>
<input name="utell" type="text" placeholder="utell" autocomplete>
<!--submit为html的提交类型,点击后会自动提交对应的表单Form(包含表单内的输入内容)-->
<button type="submit">注册(Regist)</button>
</form>
</div>
{% end %}
{% block js %}
<script>
</script>
{% end %}
{% block css %} <!--插入到模板里的 css标签-->
<style>
body{ /*body是css的html标签选择器,这里对body标签进行文字颜色,背景颜色,对齐方式进行设定*/
color:green; /*文字颜色绿色*/
background-color:black; /*背景颜色黑色*/
ext-align:center; /*文字中间对齐*/
}
#container{ /*#container 是css语言的id选择器*/
width:128px;
height:256px;
margin-top:64px;
margin-left:auto;
margin-right:auto;
}
input{
width:128px;
height:64px;
margin-top:2px;
border-bottom:dashed green;
border-top:none;
border-left:none;
border-right:none;
outline:none;
color:green;
background-color:black;
}
button{
width:128px;
height:32px;
margin-top:8px;
border:solid green;
outline:none;
color:green;
background-color:black;
}
</style>
{% end %}
<!--tornado/template/login.html-->
{% extends "base.html" %} <!--扩展base模板,替换里面的标签部分-->
{% block title %}Login Page{% end %}
{% block body %}
<div id="container">
<form action="/login" method="post">
{% module xsrf_form_html() %}
<input name="uname" type="text" placeholder="uname" required>
<input name="upass" type="password" placeholder="upass" required>
<!--submit为html的提交类型,点击后会自动提交对应的表单-->
<button type="submit">登入(LOGIN)</button>
<!--onclick添加JS里的relocal()功能,this为传入这个HTML的button对象-->
<button type="button" onclick="relocal(this)">注册(REGIS)</button>
</form>
{% end %}
{% block js %} <!--JS模板标签-->
<script>
function relocal(self){
window.location.href= "/regis"; <!--button被点击后,页面跳转到/regist注册页面-->
}
</script>
{% end %}
{% block css %}<!--CSS模板标签-->
<style>
body{
color:green;
background-color:black;
ext-align:center;
}
#container{
width:128px;
height:256px;
margin-top:64px;
margin-left:auto;
margin-right:auto;
}
input{
width:128px;
height:64px;
margin-top:2px;
border-bottom:dashed green;
border-top:none;
border-left:none;
border-right:none;
outline:none;
color:green;
background-color:black;
}
button{
width:128px;
height:32px;
margin-top:8px;
border:solid green;
outline:none;
color:green;
background-color:black;
}
</style>
{% end %}
<!--tornado/template/index.html-->
{% extends "base.html" %}
{% block title %}GotIT Now!{% end %}
{% block body %}
<h1>Hello {{uname}}, Welcome to GotIT's Main Page!</h1>
<form action="/logout" method="get"> <!--用户登出表单Form-->
<!--render模块会代入的xsrf隐藏HTML,提交表单时服务端校验用-->
{% module xsrf_form_html() %}
<!--checkbox默认值value为空,当勾选时value为on即logout=on,服务端只要校验logout=on就可以判断是否要执行登出-->
<input name="logout" type="checkbox"/>
<!--submit为html的提交类型,点击后会自动提交对应的表单-->
<input type="submit" value="Logout"/>
</form>
<!--websocket 测试区域-->
<div id="msgPanel"></div>
<textarea id="msgInput" row=20 placeholder="Message"></textarea>
<button type="button" onclick="send()">发送(Send)</button>
{% end %}
{% block js %}
<script>
var ws = new WebSocket("ws://192.168.159.133:1010/ws");
ws.onopen = function(e){
console.log("ws 已连接!");
};
ws.onclose = function(e){
console.log("ws 已关闭!");
};
function send(){
var msg = $('#msgInput').val();
sendMsg(msg);
ws.onmessage = function(e){
console.log(e.data);
var msgHtml = "<div>" + e.data + "</div>";
console.log(msgHtml);
$("#msgPanel").append(msgHtml);
};
};
function sendMsg(msg){
ws.send(JSON.stringify({
'type': 'message',
'user': '{{ uname }}',
'msg': msg,
}));
$('#msgInput').val('');
};
</script>
{% end %}
###################################################################
##########################
tornado/manage.py #主程序文件 Tornado Web Server
from tornado.web import Application
from tornado import ioloop, gen
from tornado import options
from views import Home, UserLogin, UserLogout, UserRegis
from tornado.options import define ,options, parse_command_line
from ws import WSEcho
define('WEB_HOST', default='192.168.159.133', help='Web Listen address')
define('WEB_PORT', type=int, default=1010, help='Web Listen port')
#重写tornado.web.Application并初始化
class MyApp(Application):
def __init__(self):
#urls正则匹配对应的url,把匹配到的请求推送给对应的view视图接口
urls = [
(r'/index', Home),
(r'/login', UserLogin),
(r'/logout', UserLogout),
(r'/regis', UserRegis),
(r'/ws', WSEcho),
]
#定义Tornado web App初始的设定
settings = dict(
static_path = 'static', #静态文件路径
template_path = 'template', #HTML模板文件路径
cookie_secret = '#404@*#', #cookie加密密码
login_url = '/login', #未认证用户跳转路径
xsrf_cookies = True, #启用xsrf防劫持
debug=True, #启用debug,打印出debug信息
)
super(Application, self).__init__(urls, **settings) #初始化
if __name__ == "__main__":
parse_command_line()
app = MyApp()
app.listen(options.WEB_PORT, options.WEB_HOST) #绑定IP及端口
print(app.settings) #打印出当前设定信息
ioloop.IOLoop.current().start()
tornado/views.py #视图文件 对浏览器(Brower)的请求(Request)进行分类处理
from __future__ import print_function
from tornado.web import RequestHandler, hashlib
from tornado.websocket import WebSocketHandler as ws
from tornado.web import authenticated as authed
from tornado.gen import coroutine
from tornado_mysql import pools
#使用tornado_mysql建立mysql连接池
pools.DEBUG = True
POOL = pools.Pool(dict(host='192.168.159.133', port=3306, user='tornado', passwd='tornado_pass', db='Tornado'),
max_idle_connections=2,max_recycle_sec=3)
#重写RequestHandler类,定义get_current_user功能,使能记录当前登录用户
#当用户登入后会写入一个Cookie [self.set_secure_cookie('uname', uname)]
#tornado的auth模块会调用get_current_user功能返回的cookie值,并记录此cookie值
#代表的用户为登录状态
class req(RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("uname")
class Home(req):
#添加tornado的authenticated修饰,当用户为未认证状态时,跳转到settings定义好的
#[login_url = '/login',] URL
@authed
async def get(self):
uname = self.current_user.decode('utf-8') #获取当前已认证用户名
#render模块把uname代入到template模板[index.html]中替换模板语句jinjia2
#对应的变量{{uname}}
return self.render("index.html", uname=uname)
async def post(self):
return self.write('post')
def delete(self):
return self.write('delete')
#用户登入
class UserLogin(req):
#异步处理客户端GET请求,加快处理请求速度。
async def get(self):
return self.render("login.html", title="User Login Form")
#协程处理花费较长时间的数据库查询事件,不会block Tornado进程。
@coroutine
def post(self):
uname = self.get_argument('uname')
upass = self.get_argument('upass')
text = uname + upass
md5pass = hashlib.md5(text.encode()).hexdigest()
#使用Tornado-Mysql,yield异步处理数据库查询
cur = yield POOL.execute("SELECT password FROM user WHERE name=(%s)", (uname,))
r = cur.fetchone()
cur.close()
if r:
password = r[0]
if password == md5pass:
self.set_secure_cookie('uname', uname)
print(self.current_user)
return self.redirect("/index")
else:
return self.redirect("/login")
else:
return self.redirect("/login")
class UserLogout(req):
async def get(self):
logout = self.get_argument('logout', None)
if logout=='on':
self.clear_cookie("uname")
return self.redirect('/index')
class UserRegis(req):
async def get(self):
return self.render("regis.html")
@coroutine
def post(self):
uname = self.get_argument('uname', None)
upass = self.get_argument('upass', None)
umail = self.get_argument('umail', None)
utell = self.get_argument('utell', None)
text = uname + upass
md5pass = hashlib.md5(text.encode()).hexdigest()
cur = yield POOL.execute("INSERT INTO `user` (`id`, `name`, `password`, `email`, `tel`) VALUES (NULL, %s, %s, %s, %s)",(uname, md5pass, umail, utell,))
cur.close()
return self.redirect('/login')
tornado/mysqldb.py #连接mysql数据库的文件
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
HOSTNAME = '192.168.159.133' #mysql服务器IP
PORT = '3306' #mysql服务端口
DATABASE = 'Tornado' #数据库名称
USERNAME = 'tornado' #数据库用户,需要提前到mysql添加
PASSWORD = 'tornado_pass' #用户密码
URL = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(
USERNAME,
PASSWORD,
HOSTNAME,
PORT,
DATABASE
)
engine = create_engine(URL)
Base = declarative_base(bind=engine)
Session = sessionmaker(engine)
session = Session()
tornado/model.py #数据表的model文件,设计表格结构内容
- python 控制台中,model.create_db()创建数据库
- main函数运行时去检查数据库是否存在,不存在就 model.create_db()
from sqlalchemy import Column, Integer, String
from mysqldb import Base
#创建表
def create_db():
Base.metadata.create_all()
#删除表
def drop_db():
Base.metadata.drop_all()
#User表的类,定义每一列的属性
class User(Base):
id = Column(Integer, primary_key=True, autoincrement=True) #id列每个表都有的自动增长列
name = Column(String(16), primary_key=False, unique=True, nullable=False) #用户名
password = Column(String(32), primary_key=False,unique=False, nullable=False) #密码
email = Column(String(32), primary_key=False, unique=False, nullable=False) #邮箱
tel = Column(String(16), primary_key=False, unique=False, nullable=False) #电话
__tablename__ = 'user' #表名
tornado/ws.py #Tornado Websocket consumer文件,处理ws&wss服务请求
from tornado.websocket import WebSocketHandler as ws
class WSEcho(ws):
def open(self):
print("WebSocket open")
def on_message(self, message): #客户端向服务端发送msg后触发
self.write_message(message) #广播回复msg
def on_pong(self, data):
print(data)
def on_close(self):
print("WebSocket closed")