12 项目优化

数据库查询

重构 session 的使用

  • ReuestHandler 的 prepare 和 on_finish 使用

class AuthBaseHandler(tornado.web.RequestHandler, SessionMixin):
	def get_current_user(self):
		return self.session.get('tudo_user_info')

	def prepare(self):
		self.db_session = DBSession()
		self.orm = HandlerDRM(self.db_session)

	def on_finish(self):
		self.db_session.close()
  • db session 的独立
self.db_session = DBSession()
  • 使用 ORM 工具类

相关代码

/room 的相关改进

  • user 的获取
  • 数据库的使用

服务器维护

作业

改进项目,使得 handler 里面的数据库查询支持更好,上传支持多进程的服务

code

├── tudo
│   ├── alembic
│   │   ├── env.py
│   │   ├── README
│   │   ├── script.py.mako
│   │   └── versions
│   │       ├── b9faebdbef40_add_post_model.py
│   │       ├── d868e3279547_create_users_table.py
│   │       ├── f7cb9fac4444_add_like_model.py
│   ├── alembic.ini
│   ├── app.py
│   ├── deploy
│   │   ├── nginx.conf
│   │   ├── supervisord.conf
│   │   ├── tudo_nginx
│   │   └── tudo_super.conf
│   ├── handlers
│   │   ├── auth.py
│   │   ├── chat.py
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── service.py
│   ├── models
│   │   ├── account.py
│   │   ├── db.py
│   │   ├── __init__.py
│   ├── static
│   │   ├── css
│   │   │   ├── bootstrap.css
│   │   │   ├── bootstrap.css.map
│   │   │   ├── bootstrap-grid.css
│   │   │   ├── bootstrap-grid.css.map
│   │   │   ├── bootstrap-grid.min.css
│   │   │   ├── bootstrap-grid.min.css.map
│   │   │   ├── bootstrap.min.css
│   │   │   ├── bootstrap.min.css.map
│   │   │   ├── bootstrap-reboot.css
│   │   │   ├── bootstrap-reboot.css.map
│   │   │   ├── bootstrap-reboot.min.css
│   │   │   └── bootstrap-reboot.min.css.map
│   │   ├── font-awesome-4.7.0
│   │   │   ├── css
│   │   │   │   ├── font-awesome.css
│   │   │   │   └── font-awesome.min.css
│   │   │   └── fonts
│   │   │       ├── FontAwesome.otf
│   │   │       ├── fontawesome-webfont.eot
│   │   │       ├── fontawesome-webfont.svg
│   │   │       ├── fontawesome-webfont.ttf
│   │   │       ├── fontawesome-webfont.woff
│   │   │       └── fontawesome-webfont.woff2
│   │   ├── images
│   │   │   ├── 1.jpg
│   │   │   └── error.jpg
│   │   ├── js
│   │   │   ├── bootstrap.bundle.js
│   │   │   ├── bootstrap.bundle.js.map
│   │   │   ├── bootstrap.bundle.min.js
│   │   │   ├── bootstrap.bundle.min.js.map
│   │   │   ├── bootstrap.js
│   │   │   ├── bootstrap.js.map
│   │   │   ├── bootstrap.min.js
│   │   │   ├── bootstrap.min.js.map
│   │   │   ├── chat.js
│   │   │   ├── jquery-3.3.1.slim.min.js
│   │   │   └── popper.min.js
│   │   └── uploads
│   │       ├── 0370e9cf7a9c46e495a7a21e74ec21ef.jpg
│   │       └── thumbs
│   │           ├── 0370e9cf7a9c46e495a7a21e74ec21ef_200x200.jpg
│   ├── templates
│   │   ├── base.html
│   │   ├── error.html
│   │   ├── explore.html
│   │   ├── index.html
│   │   ├── login.html
│   │   ├── message.html
│   │   ├── post.html
│   │   ├── profile.html
│   │   ├── room.html
│   │   ├── signup.html
│   │   └── upload.html
│   └── utils
│       ├── account.py
│       ├── photo.py
│       └── __pycache__
│           ├── account.cpython-35.pyc
│           └── photo.cpython-35.pyc



auth.py

from .main import AuthBaseHandler
import tornado.web


class LoginHandler(AuthBaseHandler):
   """
   登录
   """

   def get(self, *args, **kwargs):
      if self.current_user:  # 如果已经登录 访问/login自动跳转到/
         self.redirect('/')
      nextname = self.get_argument('next', '/')  # 注意是/  没有next (从logout跳转过来)就跳转到首页
      self.render('login.html', nextname=nextname, error=None)

   def post(self, *args, **kwargs):
      username = self.get_argument('username')
      password = self.get_argument('password')
      nextname = self.get_argument('next', '/')
      passed = self.orm.authenticate(username, password)  # 登录认证

      if passed:
         self.session.set('tudo_user_info', username)
         # 保存最后登录时间
         self.orm.username = username
         self.orm.save_last_login()
         self.redirect(nextname)
      else:
         self.render('login.html', nextname=nextname, error='用户名或密码错误')


class LogoutHandler(AuthBaseHandler):
   """
   登出 注销用户
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      self.session.set('tudo_user_info', '')
      self.redirect('/login')  # 直接跳转到 login  没有next 默认是/


class SignupHandler(AuthBaseHandler):
   """
   注册
   """

   def get(self, *args, **kwargs):
      self.render('signup.html', msg='')

   def post(self, *args, **kwargs):
      username = self.get_argument('username', 'no')
      email = self.get_argument('email', 'no')
      password1 = self.get_argument('password1', 'no')
      password2 = self.get_argument('password2', 'no')

      if username and password1 and password2:
         if password1 != password2:
            self.render('signup.html', msg='两次输入的密码不一致')
         else:
            ret = self.orm.register(username, password2, email)  # 注册函数
            # 注册成功
            if ret['msg'] == 'ok':
               self.session.set('tudo_user_info', username)  # 注册成功后直接设置session(自动登录)
               self.redirect('/')  # 注册成功后自动访问首页
            else:
               self.render('signup.html', msg=ret['msg'])
      else:
         self.render('signup.html', msg={'sign failed'})

chat.py

import tornado.web
import tornado.websocket
import tornado.httpclient
import tornado.escape
from tornado.ioloop import IOLoop
from pycket.session import SessionMixin
from .main import AuthBaseHandler
import uuid


def make_chat(msg, img_url=None):
   """生成一个用来格式化message.html的dict"""
   chat = {
      "id": str(uuid.uuid4()),
      "body": msg,
      "img_url": img_url  # 注意 展示数据库中的url  传给message.html 的 img的url 是 thumb_url
   }
   return chat


class RoomHandler(AuthBaseHandler):
   """
   聊天室页面
   """

   def get(self, *args, **kwargs):
      self.render('room.html', messages=ChatSocketHandler.cache)


class ChatSocketHandler(tornado.websocket.WebSocketHandler, SessionMixin):
   """
   处理响应websocket连接
   """
   waiters = set()  # 等待接受信息的用户
   cache = []  # 存放历史信息的列表
   cache_size = 200  # 消息列表的大小

   def get_current_user(self):
      return self.session.get('tudo_user_info')

   def open(self, *args, **kwargs):
      """新的websocket连接打开时 自动调用此函数"""
      print('new connection:%s' % self)
      ChatSocketHandler.waiters.add(self)

   def on_close(self):
      """websocketa连接关闭时 自动调用此函数"""
      print('close connection: %s' % self)
      ChatSocketHandler.waiters.remove(self)

   @classmethod
   def update_cache(cls, message):
      """# 更新历史消息列表 加入新的消息"""
      cls.cache.append(message)
      if len(cls.cache) > cls.cache_size:
         cls.cache = cls.cache[-cls.cache_size:]

   @classmethod
   def send_updates(cls, chat):
      """# 向所有在线用户发送消息"""
      for waiter in cls.waiters:
         waiter.write_message(chat)

   def on_message(self, message):
      """websocket服务端接收到消息 自动调用此函数"""
      print('got message %s' % message)  # got message {"body":"2"}
      parsed = tornado.escape.json_decode(message)  # {"body":"2"}
      # 用户发送的消息是一个链接
      if parsed['body'] and parsed["body"].startswith("http://"):
         url = parsed['body']
         client = tornado.httpclient.AsyncHTTPClient()  # 异步客户端
         # http://127.0.0.1:8000/async?url=http://source.unsplash.com/random
         save_api_url = "http://127.0.0.1:8000/async?url={}&username={}&from=room".\
            format(url, self.current_user)
         # 非阻塞调用内部接口(/async)  client.fetch(http://127.0.0.1:8000/async?url=http://source.unsplash.com/random)
         IOLoop.current().spawn_callback(client.fetch, save_api_url)

         chat = make_chat("user[{}],url({}) is processing".format(self.current_user, url))
         chat["html"] = tornado.escape.to_basestring(
            self.render_string("message.html", message=chat))
         self.write_message(chat)  # 消息仅发送给自己
      else:
         chat = make_chat("user[{}],say: {}".format(self.current_user, parsed['body']))

         chat["html"] = tornado.escape.to_basestring(
            self.render_string("message.html", message=chat))
         ChatSocketHandler.update_cache(chat)  # 更新历史消息列表
         ChatSocketHandler.send_updates(chat)  # 向所有在线用户发送消息

main.py

import tornado.web
from models.db import DBSession
from pycket.session import SessionMixin
from utils.account import HandlerDRM
from utils.photo import ImageSave


class AuthBaseHandler(tornado.web.RequestHandler, SessionMixin):
   def get_current_user(self):
      return self.session.get('tudo_user_info')

   def prepare(self):
      self.db_session = DBSession()
      self.orm = HandlerDRM(self.db_session, self.current_user)

   def on_finish(self):
      self.db_session.close()


class IndexHandler(AuthBaseHandler):
   """
   首页
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      post_list = self.orm.get_post_for_user()
      self.render('index.html', post_list=post_list)


class ExploreHandler(AuthBaseHandler):
   """
   发现页
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      post_list = self.orm.get_post_all()
      self.render('explore.html', post_list=post_list)


class PostHandler(AuthBaseHandler):
   """
   详情页
   """

   @tornado.web.authenticated
   def get(self, post_id):
      post = self.orm.get_post(post_id)
      if not post:
         self.write('post id is not exists')
      else:
         like_count = self.orm.get_like_count(post)
         self.render('post.html', post=post, like_count=like_count)


class UploadHandler(AuthBaseHandler):
   """
      接受图片上传
      """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      self.render('upload.html')

   def post(self, *args, **kwargs):
      # 提取表单中‘name’为‘newimg’的文件元数据   获取上传文件信息
      img_files = self.request.files.get('newimg')

      if img_files:
         post_id = 0
         for img in img_files:
            # 保存的图片的目录 名字
            image_saver = ImageSave(self.settings['static_path'], img['filename'])
            # 保存图片
            image_saver.save_image(img['body'])

            # 生成缩略图  ./ static / uploads /thumbs/ 701728_200x200.jpg
            image_saver.make_thumbs()

            # 保存图片的用户 大图地址 缩略图地址  把url存到数据库
            post = self.orm.add_post_for_user(image_saver.image_url, image_saver.thumb_url)
            post_id = post.id

         self.redirect('post/{}'.format(post_id))
      else:
         self.write({'msg': 'empty form data'})


class ProfileHandler(AuthBaseHandler):
   """
   显示用户上传的图片 和 喜欢的图片列表
   """

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      username = self.get_argument('name', None)  # url传参
      if not username:  # name=空
         username = self.current_user

      self.orm.username = username
      user = self.orm.get_user()
      if user:
         like_post_list = self.orm.get_like_post(user)
         self.render('profile.html', user=user, like_post_list=like_post_list)
      else:
         self.render('error.html')

service.py

from datetime import datetime
import tornado.web
import tornado.httpclient
import tornado.gen
import tornado.escape

from .main import AuthBaseHandler
from utils.photo import ImageSave
from .chat import ChatSocketHandler, make_chat


class URLSaveHandler(AuthBaseHandler):
   """保存指定url的图片 同步方法"""

   @tornado.web.authenticated
   def get(self, *args, **kwargs):
      url = self.get_argument('url', None)
      response = self.fetch_image(url)  # 获取指定url的图片
      if not response.body:  # 数据被封装在响应对象的body属性中
         self.write('empty data')
         return

      image_saver = ImageSave(self.settings['static_path'], 'x.jpg')
      image_saver.save_image(response.body)  # body 就是图片数据 保存图片
      image_saver.make_thumbs()  # 做缩略图
      # 添加到数据库,拿到 post 实例
      post = self.orm.add_post_for_user(image_saver.image_url, image_saver.thumb_url)

      print("-- {} -end fetch:#{}".format(datetime.now(), post.id))

      self.redirect('/post/{}'.format(post.id))  # 跳转到 post 页面

   def fetch_image(self, url):
      """获取指定url的图片"""
      client = tornado.httpclient.HTTPClient()  # 获取同步操作对象
      print("-- {} -going to fetch:{}".format(datetime.now(), url))
      response = client.fetch(url)  # 获取url对应的内容  得到响应对象
      return response


class AsyncURLSaveHandler(AuthBaseHandler):
   """保存指定url的图片 异步方法"""

   # @tornado.web.authenticated
   @tornado.gen.coroutine
   def get(self, *args, **kwargs):
      url = self.get_argument('url', None)
      print(url)
      response = yield self.fetch_image(url)  # 获取指定url的图片
      if not response.body:  # 数据被封装在响应对象的body属性中
         self.write('empty data')
         return

      image_saver = ImageSave(self.settings['static_path'], 'x.jpg')
      image_saver.save_image(response.body)  # 保存图片
      image_saver.make_thumbs()  # 缩略图

      # http://127.0.0.1: 8000/async?url=http://source.unsplash.com/random&username=2&from=room
      username = self.get_argument("username", None)  # user可以在url中获取
      is_from_room = self.get_argument('from',"")=='room'

      if username and is_from_room:
         self.orm.username = username
         post = self.orm.add_post_for_user(image_saver.image_url, image_saver.thumb_url)  # 添加到数据库
         print("-- {} -end fetch:#{}".format(datetime.now(), post.id))

         # 注意 展示数据库中的url  传给message.html 的 img的url 是 thumb_url
         chat = make_chat("username[{}] post:127.0.0.1:8000/{}".\
                      format(username, post.id), image_saver.thumb_url)
         chat["html"] = tornado.escape.to_basestring(
            self.render_string("message.html", message=chat))
         ChatSocketHandler.update_cache(chat)  # 将抓取到的图片更新到历史消息列表
         ChatSocketHandler.send_updates(chat)  # 向所有在线用户发送图片消息

      else:
         self.write('username error')

   @tornado.gen.coroutine
   def fetch_image(self, url):
      """获取指定url的图片"""
      client = tornado.httpclient.AsyncHTTPClient()  # 获取异步操作对象
      print("-- {} -going to fetch:{}".format(datetime.now(), url))
      # yield tornado.gen.sleep(6)
      response = yield client.fetch(url)  # 获取url对应的内容  得到响应对象
      return response

models/account.py

from sqlalchemy import (Column, Integer, String, DateTime, ForeignKey)
from sqlalchemy import exists
from sqlalchemy.orm import relationship
from sqlalchemy.sql import exists

from .db import Base

from datetime import datetime

# session = DBSession()

class Users(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)
   created = Column(DateTime, default=datetime.now)
   email = Column(String(50))
   last_login = Column(DateTime)

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

   # 增加用户到数据库中
   @classmethod
   def add_user(cls, username, password, db_session, email=''):
      user = Users(name=username, password=password, email=email, last_login=datetime.now())
      db_session.add(user)
      db_session.commit()

   # 查询用户username对应的密码
   @classmethod
   def get_passwd(cls, username, db_session):
      user = db_session.query(Users).filter_by(name=username).first()
      # 如果用户存在 返回密码
      if user:
         return user.password
      # 用户不存在 返回空
      else:
         return ''

   # 判断用户是否存在于数据库中
   @classmethod
   def is_exists(cls, username, db_session):
      ret = db_session.query(exists().where(Users.name == username)).scalar()
      return ret


class Posts(Base):
   """
   用户图片信息
   """
   __tablename__ = 'posts'
   id = Column(Integer, primary_key=True, autoincrement=True)
   image_url = Column(String(100))  # 大图路径
   thumb_url = Column(String(100))  # 缩略图路径
   user_id = Column(Integer, ForeignKey('users.id'))
   # Post 有user属性 存放Users对象 , Users有posts属性 存放Posts对象(多对一)
   user = relationship('Users', backref='posts', uselist=False, cascade='all')


class Like(Base):
   """
   记录用户喜欢的图片信息
   """
   __tablename__ = 'likes'
   user_id = Column(Integer, ForeignKey('users.id'), nullable=False, primary_key=True)
   post_id = Column(Integer, ForeignKey('posts.id'), nullable=False, primary_key=True)


if __name__ == '__main__':
   Base.metadata.create_all()

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'
DATABASE = 'tudo'
USERNAME = 'admin'
PASSWORD = 'Root110qwe'

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

engin = create_engine(db_uri)

DBSession = sessionmaker(engin)

Base = declarative_base(engin)

if __name__ == '__main__':
   connection = engin.connect()
   result = connection.execute('select 1')
   print(result.fetchone())

chat.js

// 浏览器在页面加载完成后调用此方法
$(document).ready(function () {
    // 创建空console对象,避免JS报错  //兼容Firefox/IE使用console.log
    if (!window.console)
        window.console = {};
    if (!window.console.log)
        window.console.log = function () {
        };

    $("#messageform").on("submit", function () {  // 点击提交时执行
        newMessage($(this)); // 发送新消息给服务器
        return false;
    });
    $("#messageform").on("keypress", function (e) {  // 回车提交时执行
        if (e.keyCode == 13) {
            newMessage($(this)); // 发送新消息给服务器
            return false;
        }
    });
    $("#message").select(); // 选中输入框
    updater.start();   // 开始 WebSocket 连接服务器 获取信息 展示在页面上
});

// 发送新消息给服务器
function newMessage(form) {
    var message = form.formToDict(); // 将提取的数据转化成字典 {body: "2"}
    updater.socket.send(JSON.stringify(message)); // 向服务器发送json形式的新的消息 {"body":"2"}
    $("input[name='body']").val("").select(); //清空并选中输入框
}

// 将提取的数据转化成字典
jQuery.fn.formToDict = function () {
    var fields = this.serializeArray(); // [{name: "body", value: "2"}]
    var json = {};
    for (var i = 0; i < fields.length; i++) {
        json[fields[i].name] = fields[i].value; // json["body"]="2"
    }
    if (json.next)
        delete json.next;
    return json;  // {body: "2"}
};

var updater = {
    socket: null,

    start: function () {
        var url = "ws://" + location.host + "/ws"; //ws://127.0.0.1:8000/ws
        updater.socket = new WebSocket(url);  // 初始化 WebSocket  客户端与服务器建立连接

        // 收到服务器数据event.data后的回调函数
        updater.socket.onmessage = function (event) {
            updater.showMessage(JSON.parse(event.data));//信息展示在页面上
        }
    },

    showMessage: function (message) {
        var existing = $("#m" + message.id);
        if (existing.length > 0)
            return;
        var node = $(message.html);
        // node.hide();
        $("#inbox").append(node);  // 添加消息 DIV 到页面
        // node.slideDown();
    }
};

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{{ static_url('css/bootstrap.css') }}">
    <link rel="stylesheet" href="{{ static_url('font-awesome-4.7.0/css/font-awesome.css') }}">
    <title>{% block title %}base{% end %}</title>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="#">
            <i class="fa fa-camera"></i>
            Tudo 图片
        </a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="/">首页<span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/explore">发现</a>
                </li>
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                        用户中心
                    </a>
                    <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                      <a class="dropdown-item" href="/profile">个人信息</a>
                      <a class="dropdown-item" href="#">收藏</a>
                      <div class="dropdown-divider"></div>
                      <a class="dropdown-item" href="#">Something else here</a>
                    </div>
                </li>
                <li class="nav-item">
                    <a class="nav-link disabled" href="/logout">{{ current_user }}登出</a>
                </li>
            </ul>
            <a class="btn btn-info" href="/upload">
                <i class="fa fa-upload"></i>
                上传
            </a>
        </div>
    </nav>

    <div class="container">
        {% block content %}
            base
        {% end %}
    </div>
    <script src="{{ static_url('js/jquery-3.3.1.slim.min.js') }}"></script>
    <script src="{{ static_url('js/popper.min.js') }}"></script>
    <script src="{{ static_url('js/bootstrap.js') }}"></script>
    {% block extra_scripts %}{% end %}
</body>
</html>

error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <img src="{{ static_url('images/error.jpg') }}">
    <h3>您访问的页面不存在</h3>
</body>
</html>

explore.html

{% extends 'base.html' %}

{% block title %}
    explore page
{% end %}

{% block content %}
     {% for post in post_list %}
        <a href="post/{{ post.id }}">
            <img src="{{ static_url(post.thumb_url) }}">
        </a>
    {% end %}
{% end %}

index.html

{% extends 'base.html' %}

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

{% block content %}
    <div class="row">
        <div class="col col-md-8">

            {% for post in post_list %}
                <a href="post/{{ post.id }}">
                    <img src="{{ static_url(post.image_url) }}" class="img-fluid" width="666">
                </a>
            {% end %}
        </div>
        <div class="d-none d-sm-none d-md-block col-md-4">
            4col
        </div>
    </div>
{% end %}

login.html

{% extends 'base.html' %}

{% block title %}
login page
{% end %}

{% block content %}

    <div class="row justify-content-center">
        <div class="col-sm-6">
            {% if error %}
                {{ error }}
            {% end %}
            <form action="/login?next={{ nextname }}" method="post" enctype="multipart/form-data">
                <div class="form-group">
                    Username
                    <input autofocus="" class="form-control" id="id_username" maxlength="254" name="username" type="text"
                           required="">
                </div>

                <div class="form-group">
                    Password
                    <input class="form-control" id="id_password" name="password" type="password" required="">
                </div>
                <button class="btn btn-outline-primary btn-lg btn-block">登录</button>
                <br>
                <div>
                    还没有账号 需要<a href="/signup">注册</a>一个
                </div>
            </form>
        </div>
</div>
{% end %}

message.html

<div class="message" id="m{{ message["id"] }}">
    {% module linkify(message["body"]) %}
    <br>
    {% if message["img_url"] %}
        <img src="{{ static_url(message["img_url"]) }}">
    {% end %}
</div>

post.html

{% extends 'base.html' %}

{% block title %}
    post page
{% end %}

{% block content %}

    <div class="row">
        <div class="col-sm-8">
            <img src="{{ static_url(post.image_url)}}" width="560">
        </div>
        <div class="col-sm-4">
            upload by {{ post.user.name }}<br>
            {% if like_count %}
                <i class="fa fa-heart text-danger"></i> {{ like_count }}
            {% end %}
        </div>
    </div>
{% end %}

profile.html

{% extends 'base.html' %}

{% block title %}
    profile page
{% end %}

{% block content %}
    <div class="card">
       <div class="card-header"><h3>{{ user.name }}用户上传</h3></div>
        <div class="row">
            {% for post in user.posts %}
                <div class="col-6 col-sm-3">
                    <a href="/post/{{ post.id }}">
                        <img src="{{ static_url(post.thumb_url) }}" class="img-fluid">
                    </a>
                </div>
            {% end %}
        </div>
    </div>
    <div class="card">
        <div class="card-header"><h3>用户喜欢</h3></div>
        <div class="row">
            {% for post in like_post_list %}
                <div class="col-6 col-sm-3">
                    <a href="/post/{{ post.id }}">
                        <img src="{{ static_url(post.thumb_url) }}" class="img-fluid">
                    </a>
                </div>
        {% end%}
        </div>
    </div>
{% end %}

room.html

{% extends 'base.html' %}

{% block title %}
    room page
{% end %}

{% block content %}
    <div id="body">
      <div id="inbox">
        {% for message in messages %}
          {% include "message.html" %}
        {% end %}
      </div>
      <div id="input">
        <form action="#" method="post" id="messageform">
          <table>
            <tr>
              <td><input name="body" id="message" style="width:500px"></td>
              <td style="padding-left:5px">
                <input type="submit" value="提交">
                <input type="hidden" name="next" value="{{ request.path }}">
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
{% end %}

{% block extra_scripts %}
    <script src="{{ static_url('js/chat.js') }}" type="text/javascript"></script>

{% end %}

signup.html

{% extends 'base.html' %}

{% block title %}
     signup page
{% end %}

{% block content %}

    <div class="row justify-content-center">
        <div class="col-sm-6">
            {% if msg %}
                {{ msg }}
            {% end %}

            <form action="/signup" enctype="multipart/form-data" method="post">
                <div class="form-group">
                    Username
                    <input autofocus="" class="form-control" id="id_username" maxlength="150" name="username" type="text" required="">
                </div>
                <div class="form-group">
                    Email
                    <input class="form-control" id="id_email" name="email" type="email" required="">
                </div>
                <div class="form-group">
                    Password
                    <input class="form-control" id="id_password1" name="password1" type="password" required="">
                </div>
                <div class="form-group">
                    Password confirmation
                    <input class="form-control" id="id_password2" name="password2" type="password" required="">
                </div>
                <button class="btn btn-outline-primary btn-lg btn-block">立即注册</button>
                <br>
                <div class="text-center help-text">
                    已有账号请 <a href="/login">登录</a>
                </div>
            </form>
        </div>
</div>
{% end %}

upload.html

{% extends base.html %}

{% block title %}
    upload
{% end %}

{% block content %}
    <form action="/upload" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <input type="file" name="newimg" class="form-control-file" id="exampleFormControlFile1">
            <br>
            <input type="submit">
        </div>
    </form>
{% end %}

utils/account.py

import hashlib
from models.account import Users, Posts, Like
from datetime import datetime


class HandlerDRM:
   """和RequestHandler配合使用的数据库连接操作工具类 """

   def __init__(self, db_session, username):
      """
      初始化参数
      :param db_session: 由RequestHandler来调用 初始化 传入 关闭 的session
      :param username: pycket记录的用户名
      """

      self.db_session = db_session
      self.username = username

   def hashed(self, password):
      return hashlib.md5(password.encode('utf8')).hexdigest()

   def authenticate(self, username, password):
      """
      登录认证
      :param username:
      :param password:
      :return:
      """
      if username and password:
         # 获取数据库中username对应的密码
         user_passwd = Users.get_passwd(username, self.db_session)
         # 如果用户存在 密码匹配
         if user_passwd and user_passwd == self.hashed(password):
            return True
      return False

   def register(self, username, password, email):
      """
      注册
      :param username:
      :param password:
      :param email:
      :return:
      """
      # 查看用户是否存在于数据库中
      if Users.is_exists(username, self.db_session):
         return {'msg': '用户已存在'}
      hash_passwd = self.hashed(password)
      # 添加用户到数据库
      Users.add_user(username, hash_passwd, self.db_session, email)
      return {'msg': 'ok'}

   def add_post_for_user(self, image_url, thumb_url):
      """
      保存用户上传的图片信息  图片和特定的用户建立关系(这张图片是由这个用户传上来的)
      :param image_url:
      :param thumb_url:
      :return:
      """
      user = self.get_user()
      post = Posts(image_url=image_url, thumb_url=thumb_url, user=user)
      self.db_session.add(post)
      self.db_session.commit()
      return post

   def get_post_for_user(self):
      """
      获取用户上传的图片信息
      :return:
      """
      user = self.get_user()
      if user:
         return user.posts
      else:
         return []

   def get_post_all(self):
      """
      获取所有图片信息(降序)
      :return:
      """
      post_list = self.db_session.query(Posts).order_by(Posts.id.desc()).all()
      return post_list

   def get_post(self, post_id):
      """
      获取用户的特定图片
      :param post_id:
      :return:
      """
      post = self.db_session.query(Posts).filter_by(id=post_id).first()
      return post

   def get_user(self):
      """
      查询用户user
      :return:
      """
      user = self.db_session.query(Users).filter_by(name=self.username).first()
      return user

   def get_like_post(self, user):
      """
      查询用户user喜欢的图片的post对象
      :param user:  Users的实例
      :return:  Posts的实例
      """

      # 去除用户自己上传的图片
      posts = self.db_session.query(Posts).filter(Like.user_id == user.id, Posts.id == Like.post_id,
                                       Posts.user_id != user.id).all()
      return posts

   def get_like_count(self, post):
      """
      获取喜欢的(♥)数量
      :param post:
      :return:
      """
      like_count = self.db_session.query(Like).filter_by(post_id=post.id).count()
      return like_count

   def save_last_login(self):
      """
      # 保存用户username最后登录时间
      :return:
      """
      t = datetime.now()
      print("user {} login at {}".format(self.username, t))
      self.db_session.query(Users).filter_by(name=self.username).update({Users.last_login: t})
      self.db_session.commit()

photo.py

import os
import glob
from PIL import Image
import uuid


class ImageSave(object):
   """
   保存图片
   """
   upload_dir = 'uploads'
   thumb_dir = 'thumbs'
   size = (200, 200)

   def __init__(self, static_path, name):
      self.static_path = static_path  # static
      self.oldname = name  # 701728.jpg
      self.newname = self.gen_new_name()

   def gen_new_name(self):
      """
      生成随机唯一的字符串(文件名)
      :return:
      """
      _, ext = os.path.splitext(self.oldname)
      return uuid.uuid4().hex + ext

   # 图片的url: uploads / 1202983.jpg
   @property
   def image_url(self):
      return os.path.join(self.upload_dir, self.newname)

   # 图片上传后的保存路径 static / uploads / 1202983.jpg
   @property
   def upload_path(self):
      return os.path.join(self.static_path, self.image_url)

   # 缩略图的url:uploads / thumbs / 1202983_200x200.jpg
   @property
   def thumb_url(self):
      filename, ext = os.path.splitext(self.newname)
      thumb_name = '{}_{}x{}{}'.format(filename, *self.size, ext)
      return os.path.join(self.upload_dir, self.thumb_dir, thumb_name)

   # 保存图片
   def save_image(self, content):
      with open(self.upload_path, 'wb') as f:
         f.write(content)

   # 生成缩略图
   def make_thumbs(self):
      im = Image.open(self.upload_path)
      im.thumbnail(self.size)
      # static / uploads / thumbs / 1202983.jpg
      save_thumb_to_ = os.path.join(self.static_path, self.thumb_url)
      im.save(save_thumb_to_, 'JPEG')

app.py

import tornado.web
import tornado.options
import tornado.ioloop
from tornado.options import define, options

from handlers import main,auth,chat,service

define(name='port', default='8000', type=int, help='run port')


class Application(tornado.web.Application):
   def __init__(self):
      handlers = [
         (r'/', main.IndexHandler),
         (r'/explore', main.ExploreHandler),
         (r'/post/(?P<post_id>[0-9]+)', main.PostHandler),
         (r'/upload', main.UploadHandler),
         (r'/profile', main.ProfileHandler),
         (r'/login', auth.LoginHandler),
         (r'/logout', auth.LogoutHandler),
         (r'/signup', auth.SignupHandler),
         (r'/room', chat.RoomHandler),
         (r'/ws', chat.ChatSocketHandler),
         (r'/save', service.URLSaveHandler),
         (r'/async', service.AsyncURLSaveHandler),
      ]
      settings = dict(
         debug=True,
         template_path='templates',
         static_path='static',
         login_url='/login',
         cookie_secret='bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=',
         pycket={
            'engine': 'redis',
            'storage': {
               'host': 'localhost',
               'port': 6379,
               # 'password': '',
               'db_sessions': 5,  # redis db index
               'db_notifications': 11,
               'max_connections': 2 ** 30,
            },
            'cookies': {
               'expires_days': 30,
            },
         }
      )

      super(Application, self).__init__(handlers, **settings)


application = Application()

if __name__ == '__main__':
   tornado.options.parse_command_line()
   application.listen(options.port)
   print("Server start on port {}".format(str(options.port)))
   tornado.ioloop.IOLoop.current().start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值