安装依赖
pip install tornado==4.5 -i https://pypi.doubanio.com/simple
配跨域
class BaseHandler(tornado.web.RequestHandler):
# 配置请求头,允许跨域,否者在浏览器调用的时候报错误,同时还得加上允许Authorization字段过来
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "*")
self.set_header("Access-Control-Allow-Headers", "Content-Type,Authorization,x-requested-with")
self.set_header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE")
# 这个函数是必要的,有些浏览器或者测试工具在访问之前都会预先访问,你不写的话会导致出错的
# 例如vue一般需要访问options方法
def options(self):
self.finish()
[第一个Tornado]输出一份 Hello Word(这是信仰)
from tornado.web import Application
from tornado.ioloop import IOLoop # 循环模型对象(事件循环)
from tornado.web import RequestHandler # 和django中的request一样(请求对象) 里面封装了常用的请求方法GET POST PUT DELETE
class IndexHandler(RequestHandler):
def get(self): # 从写get方法
self.write(
'<h1>Hello Word</h1>'
)
if __name__ == '__main__':
app = Application([ # 注册路由(里面是一个列表,列表中是一个元祖('路由', Tornado程序))
('/', IndexHandler)
])
app.listen(port=8001) # 绑定端口
print('http://127.0.0.1:8001')
IOLoop.current().start() # 获取事件循环对象,启动这个服务
# 现在请求127.0.0.1:8001 会出现hello word
# 优化写法(优化路由写法)
from tornado.web import Application
from tornado.ioloop import IOLoop # 循环模型对象(事件循环)
from tornado.web import RequestHandler # 和django中的request一样(请求对象) 里面封装了常用的请求方法GET POST PUT DELETE
class IndexHandler(RequestHandler):
def get(self, *args, **kwargs):
self.write('测试')
def make_app(): # 优化写法
return Application([
('/', IndexHandler), # 绑定路由信息
])
if __name__ == '__main__':
app = make_app()
app.listen(port=8001)
print('Starting Web Server: http://127.0.0.1:8001')
IOLoop.current().start()
#通过命令行参数启动
from tornado.web import Application
from tornado.ioloop import IOLoop # 循环模型对象(事件循环)
from tornado.web import RequestHandler # 和django中的request一样(请求对象) 里面封装了常用的请求方法GET POST PUT DELETE
import tornado.options
class IndexHandler(RequestHandler):
def get(self, *args, **kwargs):
self.write('测试')
def make_app():
return Application([
('/', IndexHandler), # 绑定路由信息
], default_host=tornado.options.options.host)
if __name__ == '__main__':
tornado.options.define('port',
default=8001, # 定义命令行参数
type=int,
help='bind socket port')
tornado.options.define('host',
default='127.0.0.1:8001',
type=str,
help='设置host name')
tornado.options.parse_command_line() # 解析命令行参数
app = make_app()
app.listen(tornado.options.options.port)
print(f'Starting Web Server: http://{tornado.options.options.host}:{tornado.options.options.port}')
IOLoop.current().start()
获取请求参数
class IndexHandler(RequestHandler):
def get(self, *args, **kwargs):
# 1.读取单个参数
wd = self.get_argument('wd') # 读取单个参数
# 2.读取多个(同名)参数,返回列表
title = self.get_arguments('title') # 获取多个title参数(参数名相同的时候) --> 返回一个列表
# print(title) # /?wd=查询参数&title=12135131&title=588
# 3. 同上一样的效果
wd2 = self.get_query_argument('wd') # 获取单个
wd3 = self.get_query_arguments('title') # 获取多个同名参数
总结:
self.get_argument() / get_arguments() 可以获取任何请求方式的请求参数
带's'的是获取所有同名参数
self.get_query_argument() / get_query_argments() 获得get请求的查询参数
返回一个列表,获取get请求方式的keyword对应的一组值,如果不存在,则为空列表
获取get请求方式的keyword对应的值,如果不存在,则为空字符串
self.get_body_argument() / self.get_body_arguments() 获取post表单参数
self.request.arguments.get()
根据key去查找参数,如果不存在返回None(post get,参数都在里面)
常用于判断参数是否存在
获取post /put/ json参数
name = self.request.body.decode()
names = json.loads(name)
获取headers参数
s = self.request.headers
print(s.get('Host')) # 示例:获取host
返回响应
self.write(html) # 返回响应数据
self.set_status(status_code) # 状态码
self.set_cookie(name, value)
self.redirect('/') # 重定向
self.set_header(name, value) # 设置请求头
-----------------------------------------------------------------------------
class SearchHandler(RequestHandler):
def get(self):
self.write(json.dumps({'code': 500})) # 返回json对象
self.set_header('Content-Type', 'application/json;charset=utf-8') # 更改数据类型
self.set_status(201) # 设置状态码
self.set_cookie('key', 'value') # 设置cookie
def make_app():
return Application([
('/', IndexHandler),
('/search/', SearchHandler), # 注意前方需要/
])
获取其他的参数
self.request.method # 请求方法
self.request.path # 请求路径
self.request.host # ip
... self对象中很多
获取路径参数
class OrderHandler(RequestHandler):
def get(self, order): # 必须多一个参数
self.write(json.dumps({'code': int(order)}))
def make_app():
return Application([
('/', IndexHandler), # 绑定路由信息
('/search/', SearchHandler),
(r'/order/(\d+)/', OrderHandler), # 正则匹配
])
图片上传与查看
class OrderHandler(RequestHandler):
def post(self, *args, **kwargs):
file = self.request.files.get('image')
filename = str(file[0].get('filename'))
image_body = file[0]
size = int(self.request.headers.get('Content-Length'))
print('图片大小:kb', size / 1000) # 获取图片大小
image_type = ''.join(filename.split('.')[1]) # 后缀
image_name = ''.join(filename.split('.')[0]) # 文件名
with open('ceshi.' + image_type, 'wb') as f:
f.write(dict(image_body).get('body'))
def get(self):
with open('ceshi.jpg', 'rb') as f:
self.write(f.read())
self.set_header('Content-Type', 'image/jpeg')
def make_app():
return Application([
('/order/', OrderHandler),
])
文件下载
class OrderHandler(RequestHandler):
def get(self):
buf_size = 4096
filename = 'ceshi.jpg' # self.get_argument('filename', None)
if not filename:
self.write({"error": "文件名称为空"})
return
# 设置传输的文件类型,有很多例如png/pdf等等 取决于不同场景,这边我用octet-stream
self.set_header('Content-Type', 'application/octet-stream')
path = './' + filename
with open(path, 'rb') as f:
while True:
data = f.read(buf_size)
if not data:
break
self.write(data)
filename = parse.quote(filename)
self.set_header('Content-Disposition', 'attachment; filename=' + filename)
self.finish()
中间件AOP【切入点】
from tornado.web import Application
from tornado.ioloop import IOLoop # 循环模型对象(事件循环)
from tornado.web import RequestHandler # 和django中的request一样(请求对象) 里面封装了常用的请求方法GET POST PUT DELETE
class IndexHandler(RequestHandler):
def get(self, *args, **kwargs):
self.write({'get': 'get'})
def post(self, *args, **kwargs):
self.write({'post': 'post'})
def initialize(self):
# 所有的请求方法,都会进行初始化操作
print('-----initialize------')
def prepare(self): # 【建议】(验证参数,权限,读缓存)
# 预处理(初始化之后,调用行为方法之前 注:先初始化(initialize),后预处理(prepare), GET/POST 常用于缓存,权限验证等)
print('-----prepare-----')
def on_finish(self): # 【主要用于文件关闭 数据库关闭】
# 所有方法执行完后执行的方法(请求处理完成后,释放资源的方法,在行为方法完后调用)
print('------on_finish-------')
def make_app():
return Application([
('/', IndexHandler),
])
if __name__ == '__main__':
app = make_app()
app.listen(port=8001)
print(f'Starting Web Server: http://127.0.0.1:8001')
IOLoop.current().start()
API接口设计
用户的 crud操作接口
import uuid as uuid
from tornado.web import Application
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler
from tornado.options import options, define, parse_command_line
class LoginHandler(RequestHandler):
'''登陆'''
users = [ # 模拟数据库
{'id': 1,
'name': 'disen',
'pwd': '123456',
'last_login_device': 'Android 5.1 OnePlus5'}
]
def get(self, *args, **kwargs):
pass
def post(self, *args, **kwargs):
content_type = self.request.headers.get('Content-Type')
if content_type == 'application/json':
json_str = json.loads(self.request.body)
if json_str.get('name') == self.users[0].get('name') and json_str.get('pwd') == self.users[0].get('pwd'):
uid = uuid.uuid4().hex
response = {'code': 'OK', 'mes': '登陆成功', 'token': uid}
else:
response = {'code': 'NO', 'mes': '账号或密码错误'}
else:
response = {'code': 'NO', 'mes': '请求头须为json'}
self.write(response)
self.set_header('Content-Type', 'application/json')
def put(self, *args, **kwargs):
pass
def delete(self, *args, **kwargs):
pass
def make_app():
return Application(
handlers=[
('/login/', LoginHandler),
],
default_host=options.h)
if __name__ == '__main__':
define('p', default=8001, type=int, help='绑定的port端口')
define('h', default='localhost', type=str, help='绑定的ip')
parse_command_line() # 解析命令行参数
app = make_app()
app.listen(options.p)
print(f'Starting Web Server: http://127.0.0.1:8001')
IOLoop.current().start()
解决跨域
import tornado.web
class BaseHandler(tornado.web.RequestHandler):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.host = ""
self.port = ""
# 在初始化方法中添加设置跨域的方法
self.set_default_headers()
# 处理OPTIONS域检请求
def post(self):
self.set_status(204)
self.finish()
# 设置跨域的具体方法
def set_default_headers(self):
super().set_default_headers()
# 设置允许的请求头
self.set_header("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS")
self.set_header("X-XSS-Protecion","1")
self.set_header("Content-Security-Policy","default-src 'self'")
self.set_header("Access-Control-Allow-Credentials","true")
# 设置一些自己定义的请求头
self.set_header("Access-Control-Allow-Headers",
"Content-Type,Access-Control-Allow-Headers,X-Auth-Token,Y-Auth-Token"
)
self.set_header("Content-Type","application/json; charset=UTF-8")
# 设置允许本地调试的域名通过!
self.set_header("Access-Control-Allow-Origin",self.request.headers.get("Origin","http://localhost:9090"))
# 如果后面的域名设置为 * ,表示允许所有的域名通过
# self.set_header("Access-Control-Allow-Origin","*")
# self.set_header("Access-Control-Allow-Origin",self.request.headers.get("Origin","*"))
# 如果前端发起请求需要加上 示例: mode:'cors'
let options = {
method: 'post',
body: JSON.stringify({
'name':'disen',
'pwd':'123456'
})
headers: {
'Content-Type': 'application/json',
},
mode: 'cors'
};
fetch('http://......./login/, options)
.then(response=>response.json())
.then(data=>{$('result').innerHTML = data.msg;
})
}
注:项目结构设计(不能一个脚本写到最后,不善于维护)
结构大致如下:
file
---> app
---> __init__.py # 在app的init中,创建tronado.web.Application类对象,并且设置初始化的参数
---> views
---> __init__.py
---> models
----> __init__.py
---> static
---> templates
---> utils
---> manage.py
SQLAlchemy–>ORM使用
Tronado中没提供orm框架,可以使用SQLAlchemy框架
参考文章
https://www.cnblogs.com/lsdb/p/9835894.html
配置sqlalchemy
pip install sqlalchemy -i https://mirrors.aliyun.com # 装依赖
注: mac/linux配置pip安装源,在~/.pip/pip.conf, 配置内容
[global]
index-url = 'https://mirrors.aliyun.com/pypi/simple
[install]
trusted-host = mirrors.aliyun.com
如果在windows系统,在用户目录的.pip子目录,配置pip.ini,内容如上
序列化查询对象
method1
1.
for r in session.query(User).all():
row2dict = lambda r: {c.name: str(getattr(r, c.name)) for c in r.__table__.columns}
print(row2dict(r))
2.
def row2dict(row):
d = {}
for column in row.__table__.columns:
d[column.name] = str(getattr(row, column.name))
return d
for r in session.query(User).all():
print(row2dict(r))
表转orm模型
pip install sqlacodegen # 实际是一个exe程序,需要找到他才能进行表转模型且在当前虚拟环境
转全部表
sqlacodegen --outfile=main.py postgresql+psycopg2://dbuser:zhimakaimen@192.168.1.50:5432/game
sqlacodegen --outfile=导出的文件名 数据库类型+连接数据库的引擎://账号:密码@IP:端口/数据库名
转指定表
sqlacodegen --outfile=main.py postgresql+psycopg2://dbuser:zhimakaimen@192.168.1.50:5432/game --tables user
参数同上... --tables 表名
1.0(配置)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 创建引擎
engine = create_engine('mysql+mysqldb://账号:密码@ip:端口/数据库?charset=utf8')
# mysqlclient引擎
# engine = create_engine('mysql+pymysql://账号:密码@ip:端口/数据库?charset=utf8')
# pymysql引擎
# engine = create_engine('sqlite:///db.db', echo=False, connect_args={'check_same_thread': False})
# sqlite引擎
# 生成数据库连接的类
DbSession = sessionmaker(bind=engine)
# 创建会话类
session = DbSession()
# 使用线程隔离的session
# engine = create_engine('sqlite:///db.db', echo=False, connect_args={'check_same_thread': False})
# DbSession = sessionmaker(bind=engine)
# session = scoped_session(DbSession)
# Base = declarative_base(bind=engine)
# 生成所有模型类的父类
# Base = declarative_base(bind=engine)
在web中或者其他多线程环境中
engine = create_engine('mysql+pymysql://root:密码@ip:3306/polls?charset=utf8',
pool_size=20, # 连接池
max_overflow=10, # 连接池允许溢出最大值
pool_recycle=3600, # 每个连接存在最大的时长/秒
pool_pre_ping=True, # 每次新建连接发送心跳
echo=False)
session = scoped_session(sessionmaker(bind=engine) # 套一层scoped这个,保证每个线程的会话对象是独立的;
Base = declarative_base(bind=engine)
2.0 orm类
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Float
from util.conn import Base
class Article(Base):
__tablename__ = 'article1' # 必须声明数据表的名
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(50), nullable=False)
price = Column(Float, nullable=False)
def __str__(self):
return "Article(title:{},price:{})".format(self.title, self.price)
# Base.metadata.create_all() # 创建所有表
# Base.metadata.drop_all() # 删除所有表
crud操作
# 增加数据 --> 查第一个数据
# article = Article(title='add', price=13)
# session.add(article)
# session.commit() # 提交事务
# 批量插入
1. objects = [
User(name="u1"),
User(name="u2"),
User(name="u3")]
session.bulk_save_objects(objects)
2. objects = [User(name="u1"), User(name="u2"), User(name="u3")]
session.add_all(objects)
session.commit()
# 修改
# user = session.query(Article).first() # 修改第一条数据
# user.title = '第一个标题'
# session.commit()
res = session.query(Article).filter(Article.id==1).first() # 修改指定数据
res.price = 100
session.commit()
# 批量修改
session.query(Note).filter(Note.id==1).update({Note.title:"title-edit",
Note.body:"body-edit"})
session.commit()
# 查全部
# article = session.query(Article).all()
# data = [{'id': i.id, 'title': i.title, 'price': i.price} for i in article]
# print(data)
# 根据主键查
res = session.query(Article).get(2)
# 只查某一个字段(优化性能)
# article = session.query(Article, 'title').all()
# for i in article:
# print(i.title)
# 删除
res = session.query(Article).filter(Article.id==1)
res.delete()
session.commit()
# 聚合函数(统计数量)
# res = session.query(func.count(Article.id)).first()
# print(res)
# 过滤后的数量
# res = session.query(Article).filter(Article.title=='add').count()
# 分页查询(limit, offset, slice)
res = session.query(Arctcle).limit(10).all() -- > 只查10条数据
res = session.query(Arctcle).limit(10).offset(20).all() --> 只查10 - 20条数据
res = session.query(Arctcle).slice(10, 20).all() --> 切片查询
# 某个字段的平均值
# res = session.query(func.avg(Article.price)).first()
# 最小值
# res = session.query(func.min(Article.price)).first()
# 最大值
# res = session.query(func.max(Article.price)).first()
# 求和
# res = session.query(func.sum(Article.price)).first()
#日期查询
from datetime import datetime
from sqlalchemy import extract
res = session.query(cls).filter(
extract('year', cls.create_time) == datetime.today().year,
extract('month', cls.create_time) == datetime.today().month,
extract('day', cls.create_time) == datetime.today().day)\
.filter(cls.user == user_id).filter(cls.match_id == match_id)\
.filter(cls.is_free == 1).first()
#过滤
# eq
# result = session.query(Article).filter(Article.title =='title0').all()
# not eq mysql <>
# result = session.query(Article).filter(Article.title !='title0').all()
# like 模糊查询
# title 包含 title
# result = session.query(Article).filter(Article.title.like('%title%')).all()
# in 在..里面 Pyhont关键字 _ 私有的(下划线加前面)
# in[1,4] 查询的是id 为1,4 不是范围
# result = session.query(Article).filter(Article.title.in_(['title0','title4'])).all()
# not in 取反
# result = session.query(Article).filter(~Article.title.in_(['title0','title4'])).all()
# result = session.query(Article).filter(Article.title.notin_(['title0','title4'])).all()
# Null(未开辟空间),空(已开辟空间)
# result = session.query(Article).filter(Article.title == None).all()
# result = session.query(Article).filter(Article.title.is_(None)).all()
# result = session.query(Article).filter(Article.title.isnot(None)).all()
# result = session.query(Article).filter(Article.title !=None).all()
# and 和,且 推荐->第一种简单
# result = session.query(Article).filter(Article.title == 'title0', Article.price == 23).all()
# result = session.query(Article).filter(and_(Article.title == 'title0', Article.price == 23)).all()
# result = session.query(Article).filter(Article.title == 'title0').filter(Article.price == 23).all()
# or 或 只满足一个条件就可以
result = session.query(Article).filter(or_(Article.title == 'title0', Article.price == 17, Article.title == 'title2')).all()
# 数据去重
from sqlalchemy import distinct
session.query(distinct(BudgetOrganizational.center_name)).all()
或
session.query(BudgetOrganizational.center_name).group_by(BudgetOrganizational.center_name).all()
# 日期查询的三种方式
方法一注意时间格式:xxxx-xx-xx
方法二没有‘day’
方法三的时间格式同方法一
1、result = Jobs.query.filter(Jobs.create_time < '2017-07-10').all()
2、result = Jobs.query.filter(and_(
extract('year', Jobs.create_time) == 2017,
extract('month', Jobs.create_time) == 7
)).all()
3、result = Jobs.query.filter(Jobs.create_time.between('1990-01-01', '2018-01-01'))
#排序(逆序)
res = session.query(Article).order_by(Article.id.desc()).all()
外键排序
class User_VipTick(Base):
'''用户背包'''
from sqlalchemy.orm import backref
__tablename__ = 'user_viptick'
id = Column(Integer, primary_key=True, autoincrement=True)
user = Column(Integer, ForeignKey('user.id'))
vipticket = Column(Integer, ForeignKey('vipticket.id'))
create_time = Column(DateTime, default=datetime.now)
# 加他
dp = relationship("User", backref=backref('user', order_by=create_time.desc()))
属性详解
Column常用参数:
default:默认值。
nullable:是否可空。
primary_key:是否为主键。
unique:是否唯一。
autoincrement:是否自动增加。
onupdate:更新的时候执行的函数。
name:该属性在数据库中的字段映射
sqlalchemy常用的数据类型:
Integer:整形。
Float:浮点类型。
Boolean:传递True/False进去。
DECIMAL:定点类型。
enum:枚举类型。
Date:传递datetime.date()进去。
DateTime:传递datetime.datetime()进去。
Time:传递
datetime.time()进去。
String:字符类型,使用时需要指定长度,区别于Text类型。
Text:文本类型。
LONGTEXT:长文本类型。
外键查询
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Float, func
from sqlalchemy.orm import relationship
from util.conn import Base, session
class Student(Base):
__tablename__ = 'student'
student_id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255))
age = Column(String(255), default='0')
sex = Column(String(255), default='男')
class Score(Base):
__tablename__ = 'score'
score_id = Column(Integer, primary_key=True, autoincrement=True)
score = Column(String(255), default='0')
student_id = Column(ForeignKey('student.student_id'))
dp = relationship("Student", backref='student') # backref 绑定双向关系 理解为: 一查多的时候 对象.student
# -->join查询
res3 = session.query(Score.score, Score.student_id).join(Student, Score.student_id == Student.student_id).all()
for i in res3:
print(i)
# -->正向查询
res0 = session.query(Score).filter(Score.score == 100).all()
print([{'name': i.dp.name} for i in res0])
# -->逆向查询
res = session.query(Student).filter_by(name='李白').first()
for i in res.student:
print(i.score)
简单的实例
import json
from sqlalchemy import Column, Integer, String, Text, ForeignKey, Float, func
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 创建引擎
engine = create_engine('mysql+mysqldb://root:Admin123.@IP:3306/study?charset=utf8')
# engine = create_engine('mysql+pymysql://root:Admin123.@IP:3306/polls?charset=utf8')
# 生成数据库连接的类
DbSession = sessionmaker(bind=engine)
# 创建会话类
session = DbSession()
# 生成所有模型类的父类
Base = declarative_base(bind=engine)
class Student(Base):
__tablename__ = 'student'
student_id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(255))
age = Column(String(255), default='0')
sex = Column(String(255), default='男')
class Score(Base):
__tablename__ = 'score'
score_id = Column(Integer, primary_key=True, autoincrement=True)
score = Column(String(255), default='0')
student_id = Column(ForeignKey('student.student_id'))
dp = relationship("Student", backref='student') # backref 绑定双向关系 理解为: 一查多的时候 对象.student
from tornado.web import Application
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler
from tornado.options import options, define, parse_command_line
class Login(RequestHandler):
'''测试跨域'''
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_default_headers()
def options(self):
# 处理OPTIONS域检请求
self.set_status(204)
self.finish()
def set_default_headers(self):
super().set_default_headers()
self.set_header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
self.set_header("X-XSS-Protecion", "1")
self.set_header("Content-Security-Policy", "default-src 'self'")
self.set_header("Access-Control-Allow-Credentials", "true")
self.set_header("Access-Control-Allow-Headers",
"Content-Type,Access-Control-Allow-Headers,X-Auth-Token,token",
)
self.set_header("Content-Type", "application/json; charset=UTF-8")
self.set_header("Access-Control-Allow-Origin", "*")
def get(self):
user = session.query(Student).all()
user = [{'user_id': i.student_id, 'name': i.name, 'age': i.age} for i in user]
self.write({'code': 200, 'mes': '查询成功', 'results': user})
self.set_status(200)
def post(self, *args, **kwargs):
token = self.request.headers
username = self.request.body
username = json.loads(username).get('username')
res = session.query(Student).filter_by(name=username).first()
if res:
response = {'code': 200, 'mes': '登陆成功'}
else:
response = {'code': 500, 'mes': '账号或者密码错误'}
self.write(response)
self.set_header('ContType', 'application/json;charset=utf-8')
self.set_status(200)
app = Application([ # 注册路由(里面是一个列表,列表中是一个元祖('路由', Tornado程序))
('/', Login)
])
app.listen(port=8001) # 绑定端口
print('http://127.0.0.1:8001')
IOLoop.current().start() # 获取事件循环对象,启动这个服务