Tornado实战-用户登录与注册

需要模块

pip install redis
pip install packet

实现代码

app.py

import tornado.ioloop   #开启循环,让服务一直等待请求的到来
import tornado.web      #web服务基本功能都封装在此模块中
import tornado.options  #从命令行中读取设置
from tornado.options import define,options  #导入包

from handlers import main,auth

define('port',default='8000',help='Listening port',type=int) #定义如何接受传进来的东西


class Application(tornado.web.Application):  #引入Application类,重写方法,这样做的好处在于可以自定义,添加另一些功能
    def __init__(self):
        handlers = [
            (r'/',main.IndexHandler),
            (r'/explore',main.ExploreHandler),
            (r'/post/(?P<post_id>[0-9]+)',main.PostHandler), #命名组写法,使用关键字,路由与handler方法不一定顺序一致
            (r'/upload',main.UploadHandler),
            (r'/login',auth.LoginHandler),
            (r'/logout',auth.LogoutHandler),
            (r'/register',auth.RegisterHandler)
        ]
        settings = dict(
            debug = True, #调试模式,修改后自动重启服务,不需要自动重启,生产情况下切勿开启,安全性
            template_path='templates', #模板文件目录,想要Tornado能够正确的找到html文件,需要在 Application 中指定文件的位置
            static_path='static',  #静态文件目录,可用于用于访问js,css,图片之类的添加此配置之后,tornado就能自己找到静态文件
            login_url='/login', #没有登录则跳转至此
            cookie_secret='1q2w3e4r',  # 加密cookie的字符串
            pycket={  #固定写法packet,用于保存用户登录信息
                'engine': 'redis',
                'storage': {
                    'host': 'localhost',
                    'port': 6379,
                    'db_sessions': 5,
                    'db_notifications': 11,
                    'max_connections': 2 ** 33,
                },
                'cookie': {
                    'expires_days': 38,
                    'max_age': 100
                }
            }
        )

        super(Application,self).__init__(handlers,**settings) #用super方法将父类的init方法重新执行一遍,然后将handlers和settings传进去,完成初始化


app = Application() #实例化


if __name__ == '__main__':   #当.py文件被直接运行时,代码块将被运行;当.py文件以模块形式被导入时,代码块不被运行。

    tornado.options.parse_command_line()
    app.listen(options.port)  ##如果一个与define语句中同名的设置在命令行中被给出,那么它将成为全局的options的一个属性 即 options.port 相当于define的url的port
    print("Server start on port {}".format(str(options.port)))  #提示服务启动占用端口
    tornado.ioloop.IOLoop.current().start()   #执行ioloop

main.py

import tornado.web
import os
from pycket.session import SessionMixin
from utils import photo


class AuthBaseHandler(tornado.web.RequestHandler,SessionMixin):
    def get_current_user(self): #重写get_current_user()方法
        return self.session.get('user_info',None) #session是一种会话状态,跟数据库的session可能不一样

#添加装饰器,装饰需要验证的请求
class IndexHandler(AuthBaseHandler):
    """
     Home page for user,photo feeds 主页----所关注的用户图片流
    """
    @tornado.web.authenticated   #@tornado.web.authenticated装饰器包裹get方法时,表示这个方法只有在用户合法时才会调用,authenticated装饰器会调用get_current_user()方法获取current_user的值,若值为False,则重定向到登录url装饰器判断有没有登录,如果没有则跳转到配置的路由下去,但是要在app.py里面设置login_url
    def get(self,*args,**kwargs):
        self.render('index.html')


class ExploreHandler(AuthBaseHandler):
    """
    Explore page,photo of other users 发现页-----发现或最近上传的图片页面
    """
    @tornado.web.authenticated
    def get(self,*args,**kwargs):
        # image_urls = get_images("./static/uploads")  #打开指定路径下的文件,或者static/uploads
        os.chdir('static')  # 用于改变当前工作目录到指定的路径
        image_urls = photo.get_images("uploads/thumbs")
        os.chdir("..")
        self.render('explore.html',image_urls=image_urls)

class PostHandler(AuthBaseHandler):
    """
    Single photo page and maybe  单个图片详情页面
    """
    @tornado.web.authenticated
    def get(self,post_id):
        print(post_id)
        self.render('post.html',post_id = post_id)   #根据正则输入的内容,接收到,打开相应的图片


class UploadHandler(AuthBaseHandler):  #上传文件
    @tornado.web.authenticated
    def get(self,*args,**kwargs):
        self.render('upload.html')

    def post(self,*args,**kwargs):
        file_imgs = self.request.files.get('newImg',None)  #获取上传文件数据,返回文件列表

        for file_img in file_imgs: #可能同一个上传的文件会有多个文件,所以要用for循环去迭代它
            # filename 文件的实际名字,body 文件的数据实体;content_type 文件的类型。 这三个对象属性可以像字典一样支持关键字索引
            save_to = 'static/uploads/{}'.format(file_img['filename'])
            #以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
            with open(save_to,'wb') as f: #二进制
                f.write(file_img['body'])
            photo.make_thumb(save_to) #同时生成缩略图

        self.redirect('/explore')

auth.py

import tornado.web
from utils.account import authenticate
from .main import AuthBaseHandler



class RegisterHandler(AuthBaseHandler):
    def get(self, *args, **kwargs):
        print('register')
        self.render('register.html')

    def post(self, *args, **kwargs):
        print('registerpost')

        username = self.get_argument('username','')
        password1 = self.get_argument('password1','')
        password2 = self.get_argument('password2','')

        if username and password1 and (password1 == password2):
            pass
        else:
            self.write({'msg':'register fail'})


class LoginHandler(AuthBaseHandler):
    def get(self,*args,**kwargs):
        if self.current_user: #若用户已登录
            self.redirect('/') #那么直接跳转到主页
        else:
            nextname = self.get_argument('next','') #将原来的路由赋值给nextname
            self.render('login.html',nextname = nextname) #否则去登录界面

    def post(self,*args,**kwargs):
        username = self.get_argument('username',None)
        password = self.get_argument('password',None)

        passed = authenticate(username,password)

        if passed:
            self.session.set('user_info',username) #将前面设置的cookie设置为username,保存用户登录信息
            next_url = self.get_argument('next', '')  # 获取之前页面的路由

            if next_url:
                self.redirect(next_url) #跳转主页路由
            else:
                self.redirect('/')
        else:
            self.write({'msg':'login fail'}) #不通过,有问题

class LogoutHandler(AuthBaseHandler):
    def get(self, *args, **kwargs):
        #self.session.set('user_info','') #将用户的cookie清除
        self.session.delete('user_info')
        self.redirect('/login')

db.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker



HOSTNAME = '127.0.0.1'
PORT = '3306'  #注意这个不是本地端口是指远程数据库端口,因为pycharm已经先SSH连接到本地了
DATABASE = 'my_db'
USERNAME = 'root'
PASSWORD = '1q2w3e4r'

db_url = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(
    USERNAME,
    PASSWORD,
    HOSTNAME,
    PORT,
    DATABASE
)


#连接数据库
engine = create_engine(db_url)

Base = declarative_base(bind = engine)  #这个基类是维系类和数据表关系的目录。

#在对表数据进行增删改查之前,先需要建立会话,建立会话之后才能进行操作,就类似于文件要打开之后才能对文件内容操作。
Session = sessionmaker(engine)
session = Session()

users.py

from datetime import datetime
from sqlalchemy import Column,Integer,String,DateTime
from .db import Base

class User(Base):

    __tablename__ = 'users'
    id = Column(Integer,primary_key=True,autoincrement=True)
    name = Column(String(50),unique=True,nullable=False)
    password = Column(String(50),nullable=False)
    last_login = Column(DateTime,default=datetime.now)

    def __repr__(self):
        return '<User #{}:{}>'.format(self.id,self.name)

account.py

import hashlib

def hash(text):
    text = hashlib.md5(text.encode()).hexdigest() #给密码加密,用hashlib来算法加密,utf8不加的话就是默认utf8

    return text

USER_DATA = {
    'name':'user',
    'password':hash('1q2w3e4r')
}

def authenticate(username,password):#用户密码匹配判断函数
    if username and password:
        hash_pwd = hash(password)
        if username == USER_DATA['name'] and hash_pwd == USER_DATA['password']: #是否与保存的一致
            return True

    return False

index.html

{% extends 'base.html' %} #继承base.html

{% block title %} index page {% end %}

{% block content %}
index content
current_user:{{current_user}} <!--获取当前用户 -->
{% for num in range(1,5) %}
<a href="/post/{{num}}">
    <img src="{{static_url('images/{}.jpg'.format(num))}}" /> <!-- 使用此方法时,Tornado 会自动地给静态文件添加版本号,如果版本号更改了,浏览器会自动的缓存新的静态文件-->
</a>

{% end %}
{% end %}
image.png

![image.png](https://upload-images.jianshu.io/upload_images/9286065-aa9c90ddb040bc5c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,159评论 22 257
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,719评论 0 10
  • jHipster - 微服务搭建 CC_简书[https://www.jianshu.com/u/be0d56c4...
    quanjj阅读 794评论 0 2
  • 西部多天堑,陡壁云间断。 成昆斗士似飞仙,左撷霓彩,右饮雪中霰。 悬身一线天方好,不畏金沙险。 索桥日夜冬夏,换得...
    冰熙舍人阅读 643评论 2 12
  • 如此静的天空,是不是活着的都进入了冬眠 我靠着僵硬的墙站着,无奈地沉默 嗅着空气中的夜的味道 仿佛见着一个人的路径...
    木客的雨子阅读 609评论 0 1