你們跟着我,齊步一塊兒走!javascript
文章比較長,還請耐心閱讀css
先提供項目源碼,歡迎 start 和 forkhtml
仍是使用 Flask 來搭建後臺應用,使用 flask-login 擴展來處理用戶登錄鑑權邏輯。 首先定義登錄表單java
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), ])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Keep me logged in')
submit = SubmitField('Log in')
複製代碼
一個簡單的登錄表單,不作過多解釋python
接下來定義數據庫結構jquery
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
password = db.Column(db.String(64))
複製代碼
當前,咱們只須要一個 user 用戶表,只包含三個字段的簡單表。用戶密碼也只是簡單的保存了明文,後面再處理用戶密碼的 hash 問題。git
下面就能夠定義用戶登錄表單github
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'login'
app = Flask(__name__)
login_manager.init_app(app)
app.config['SECRET_KEY'] = 'hardtohard'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user:
login_user(user)
return redirect(url_for('chat'))
return render_template('login.html', form=form)
複製代碼
這裏定義了,只檢查用戶名是否存在,若是存在,就執行 login_user() 函數,登錄。用戶密碼的使用,也留到後面再作處理。web
其中 load_user,是回調函數,將獲取到的 user 對象存儲到瀏覽器的 session 中,而後在調用 login_user 函數時,就會調用 load_user 來把真正須要登錄的用戶設置到 session 中。當登錄成功後,就會跳轉到 chat 函數所對應的頁面。
chat 函數比較簡單,只是展現一個網頁
@app.route('/chat', methods=['GET', 'POST'])
@login_required
def chat():
return render_template('chat.html')
複製代碼
使用 login_required 裝飾器,保證該函數只容許登錄的用戶訪問。
增長些初始化函數
@app.route('/adddb', methods=['GET', 'POST'])
def addbd():
db.create_all()
return "OK"
@app.route('/deldb', methods=['GET', 'POST'])
def delbd():
db.drop_all()
return "OK"
@app.route('/adduser/<user>', methods=['GET', 'POST'])
def adduser(user):
user = User(username=user, password='admin')
db.session.add(user)
db.session.commit()
return "OK"
複製代碼
增長了初始化數據庫和新增用戶的函數。
首先處理登錄頁面,在 login.html 中添加
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</div> {% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, Welcome!</h1>
</div>
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
複製代碼
使用擴展庫 flask_bootstrap 來快速構建頁面。
下面重點來看看 chat 頁面。 首先來看看主體頁面,在 chat.html 中填入代碼
{% extends 'bootstrap/base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Kung Fu Realm{%endblock %}
{% block head %}
<head>
<meta charset="utf-8">
<title>Hi Hi 聊天室</title>
<link rel="shortcut icon" href="{{ url_for('static',filename='chat/images/hihi.jpg')}}">
<link rel="icon" href="{{ url_for('static',filename='chat/images/hihi.jpg')}}" type="image/x-icon">
<link type="text/css" rel="stylesheet" href="/static/chat/css/style.css">
<script type="text/javascript" src="{{ url_for('static', filename='chat/js/jquery.min.js') }}"></script>
</head>
{% endblock %}
{% block content %}
<body>
<div class="chatbox">
<div class="chat_top fn-clear">
<div class="uinfo fn-clear" style="float: left;"><div class="uface"><h1 style="color: #7777">ROOM: 聊天室123哈哈哈</h1></div></div>
<div class="uinfo fn-clear">
{% if current_user.is_authenticated %}
<div class="uface"><img src="{{ url_for('static', filename='chat/images/hi.jpg') }}" width="40" height="40" alt=""/></div>
{% else %}
<div class="uface"><img src="{{ url_for('static', filename='chat/images/hi.jpg')}}" width="40" height="40" alt=""/></div>
{% endif %}
<div class="uname">
小HI<i class="fontico down"></i>
<ul class="managerbox">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('login') }}"><i class="fontico lock"></i>退出登錄</a></li>
{% else %}
<li><a href="{{ url_for('logout') }}"><i class="fontico logout"></i>登陸</a></li>
{% endif %}
</ul>
</div>
</div>
</div>
<div class="chat_message fn-clear">
<div class="chat_left">
<div class="message_box" id="message_box">
<div class="msg_item fn-clear">
<div class="uface"><img src="{{ url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40" alt=""/></div>
<div class="item_right">
<div class="msg own"><img src="{{ url_for('static', filename='chat/images/hihi.jpg')}}" width="400" height="400" alt=""/></div>
<div class="name_time">小黃鴨 </div>
</div>
</div>
{% if current_user.is_authenticated %}
<div class="msg_item fn-clear">
<div class="uface"><img src="{{ url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40" alt=""/></div>
<div class="item_right">
<div class="msg">Welcome to Hihi Chat Room. 歡迎來到 Hihi 聊天室。 </div>
<div class="name_time">小黃鴨 </div>
</div>
</div>
{% else %}
<div class="msg_item fn-clear">
<div class="uface"><img src="{{ url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40" alt=""/></div>
<div class="item_right">
<div class="msg">您尚未登錄,先和小黃鴨聊聊吧。 </div>
<div class="name_time">小黃鴨 </div>
</div>
</div>
{% endif %}
</div>
<div class="write_box">
{% if current_user.is_authenticated %}
<textarea id="message" name="message" class="write_area" placeholder="說點啥吧..."></textarea>
{% else %}
<textarea id="message_not" name="message" class="write_area" placeholder="說點啥吧..."></textarea>
{% endif %}
<input type="hidden" name="fromname" id="fromname" value="你" />
<input type="hidden" name="to_uid" id="to_uid" value="0">
<div class="facebox fn-clear">
<div class="expression"></div>
<div class="chat_type" id="chat_type">羣聊</div>
{% if current_user.is_authenticated %}
<button name="login" class="sub_but" id="sub_but_login">提 交</button>
{% else %}
<button name="logout" class="sub_but" id="sub_but">提 交</button>
{% endif %}
</div>
</div>
</div>
</div>
</div>
複製代碼
總體效果以下,是否是挺少女系的。
當用戶在點擊「提交」按鈕後,調用 JS 函數
/*用戶登錄的用戶點擊提交按鈕發送消息按鈕*/
$('#sub_but_login').click(function(event){
sendMessageLogin(event, fromname, to_uid, to_uname);
});
複製代碼
爲了後面便於擴展,將未登陸的用戶特別區分開來,後面也許一樣容許未登錄用戶訪問該頁面,可是隻能同機器人小黃鴨聊天
/*用戶未登錄的用戶點擊提交按鈕發送消息按鈕*/
$('#sub_but').click(function(event){
sendMessage(event, fromname, to_uid, to_uname);
});
複製代碼
在來看函數 sendMessageLogin
function sendMessageLogin(event, from_name, to_uid, to_uname){
var msg = $("#message").val();
var myDate = new Date();
var myTime = myDate.toLocaleTimeString();
var itTime = myDate.toLocaleString();
//var iTime = myDate.toDateString();
var htmlData = '<div class="msg_item fn-clear">'
+ ' <div class="uface">{% if current_user.is_authenticated %}<img src="{{ url_for('static', filename='chat/images/hi.jpg') }}" width="40" height="40" alt=""/>{% endif %}</div>'
+ ' <div class="item_right">'
+ ' <div class="msg own">' + msg + '</div>'
+ ' <div class="name_time">' + from_name + ' · ' + itTime +'</div>'
+ ' </div>'
+ '</div>';
$("#message_box").append(htmlData);
$('#message_box').scrollTop($("#message_box")[0].scrollHeight + 20);
$("#message").val('');
setTimeout(function(){sendToServer(from_name, msg)}, 1000); //延時調用
}
複製代碼
接收幾個參數,而後將當前會話消息追加到 HTML 頁面中,而且調用真正的後臺 API 函數 sendToServer
function sendToServer(name, msg){
var xmlhttp = new XMLHttpRequest();
var myDate = new Date();
//var myTime = myDate.toLocaleTimeString();
var myTime = myDate.toLocaleString();
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
myObj = xmlhttp.responseText;
var htmlData2 = '<div class="msg_item fn-clear">'
+ ' <div class="uface"><img src="{{ url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40" alt=""/></div>'
+ ' <div class="item_right">'
+ ' <div class="msg">' + myObj + '</div>'
+ ' <div class="name_time">' + '小黃鴨' + ' · ' + myTime +'</div>'
+ ' </div>'
+ '</div>';
$("#message_box").append(htmlData2);
$('#message_box').scrollTop($("#message_box")[0].scrollHeight + 20);
}
}
xmlhttp.open("GET", "/api/sendchat/" + msg, true);
xmlhttp.send();
};
複製代碼
sendToServer 函數調用後臺 API,並把返回接收到的消息回寫到 HTML 頁面中。
而目前的後臺 API 也比較簡單,直接返回用戶輸入的消息
@app.route('/api/sendchat/<info>', methods=['GET', 'POST'])
@login_required
def send_chat(info):
return info
複製代碼
這樣,一個總體的聊天室架子就搭建好了,接下來咱們再接入 redis,來實現聊天功能。
我這裏使用 redis 來做爲後端數據存儲工具。你們若是有本身的 redis 服務器固然是最好了,若是沒有的話,推薦下在線的 redis 免費應用 redislabs,你們能夠自行體驗下,redislabs.com/
下面鏈接到 redis 服務器並打開鏈接池
pool = redis.ConnectionPool(host='redis-12143.c8.us-east-1-3.ec2.cloud.redislabs.com', port=12143,
decode_responses=True, password='pkAWNdYWfbLLfNOfxTJinm9SO1')
r = redis.Redis(connection_pool=pool)
複製代碼
redis 中數據結構及用法以下: chat-{ChatRoomName},聊天室及加入的用戶,zset 類型 msg-{ChatRoomName},每一個聊天室對應的消息,zset 類型
當前結構比較簡單,暫時只定義了兩個域,分別用來存儲聊天室和消息。
在前面的代碼中,chat 視圖函數僅僅是返回了一個 HTML 頁面,並無任何功能邏輯,如今要完善下。最新的代碼以下:
@app.route('/chat', methods=['GET', 'POST'])
@login_required
def chat():
rname = request.args.get('rname', "")
ulist = r.zrange("chat-" + rname, 0, -1)
messages = r.zrange("msg-" + rname, 0, -1, withscores=True)
msg_list = []
for i in messages:
msg_list.append([json.loads(i[0]), time.strftime("%Y/%m/%d %p%H:%M:%S", time.localtime(i[1]))])
return render_template('chat.html', rname=rname, user_list=ulist, msg_list=msg_list)
複製代碼
其中 rname 是其餘函數傳值過來的,咱們後面再說。 r.zrange() 函數就是從 redis 中取出對應聊天室的用戶列表和歷史聊天記錄,最後就是把相關的信息返回到模板中。
在 chat 視圖中,咱們傳入了一個 rname 字段,這個字段就是當建立或者加入聊天室時,須要傳遞過來的。
@app.route('/createroom', methods=["GET", 'POST'])
@login_required
def create_room():
rname = request.form.get('chatroomname', '')
if r.exists("chat-" + rname) is False:
r.zadd("chat-" + rname, current_user.username, 1)
return redirect(url_for('chat', rname=rname))
else:
return redirect(url_for('chat_room_list'))
複製代碼
判斷聊天室名稱是否存在,若是不存在,則將當前用戶在 redis 中建立並跳轉至 chat 函數;不然跳轉至聊天室列表頁面。
@app.route('/joinroom', methods=["GET", 'POST'])
@login_required
def join_chat_room():
rname = request.args.get('rname', '')
if rname is None:
return redirect(url_for('chat_room_list'))
r.zadd("chat-" + rname, current_user.username, time.time())
return redirect(url_for('chat', rname=rname))
複製代碼
這裏是從前端獲取到聊天室名稱(rname),並將當前用戶名加入到對應的聊天室中。
到這裏,redis 中的聊天室就處理完成了,下面再來看看其餘的一些輔助功能。
既然有加入聊天室的功能,那麼就要提供一個列表供用戶選擇聊天室。
後臺邏輯代碼:
@app.route('/roomlist', methods=["GET", 'POST'])
@login_required
def chat_room_list():
roomlist_tmp = r.keys(pattern='chat-*')
roomlist = []
for i in roomlist_tmp:
i_str = str(i, encoding='utf-8')
istr_list = i_str.split('-', 1)
roomlist.append(istr_list[1])
return render_template('chatroomlist.html', roomlist=roomlist)
複製代碼
比較簡單,到 redis 中拿到全部以「chat-」開頭的 key 值,而後處理成列表返回到前端便可。
前臺頁面代碼:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</div> {% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, {{ current_user.username }}!</h1>
</div>
<div class="page-header">
{% for i in roomlist %}
<p>{{ i }} <a href="{{ url_for('join_chat_room', rname=i) }}" class="btn btn-default" role="button">Join This Room</a></p>
{% endfor %}
</div>
<form action="{{ url_for('create_room') }}" method="POST" class="comment-form">
<div class="form-group comment-form-author">
<label for="chatroomname">Chat Room Name <span class="required">*</span></label>
<input class="form-control" id="chatroomname" name="chatroomname" type="text" value="" size="30" aria-required='true' />
</div>
<div class="form-group comment-form-comment">
<label for="description">Chat Room Description <span class="required">*</span></label>
<textarea class="form-control" id="description" name="description" cols="45" rows="6"></textarea>
</div>
<button name="submit" type="submit" id="submit" class="btn btn-primary" value="Submit Comment">Create Room</button>
</form>
</div>
{% endblock %}
複製代碼
就是循環渲染列表數據,和一個建立聊天室的表單。
當用戶退出登錄時,咱們當前也但願該用戶同時退出聊天室,因此修改 logout 函數以下:
@app.route('/logout')
@login_required
def logout():
rname = request.args.get("rname", "")
r.zrem("chat-" + rname, current_user.username)
logout_user()
return redirect(url_for('login'))
複製代碼
從前端拿到聊天室的名字,並在 redis 的對應 zset 中刪除當前用戶。
爲了聊天室的美觀,不一樣用戶須要擁有不一樣的頭像,這裏仍是使用 gravatar 這個免費的頭像服務。 在 User 模型中添加代碼:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
password = db.Column(db.String(64))
avatar_hash = db.Column(db.String(32))
def gravatar(self, name=None, size=100, default='identicon', rating='g'):
if request.is_secure:
url = 'https://secure.gravatar.com/avatar'
else:
url = 'http://www.gravatar.com/avatar'
if name is not None:
email = name + "@hihichat.com"
else:
email = self.username + "@hihichat.com"
myhash = self.avatar_hash or hashlib.md5(email.encode('utf-8')).hexdigest()
return '{url}/{hash}?s={size}&d={default}&r={rating}'.format(url=url, hash=myhash, size=size,
default=default, rating=rating)
複製代碼
gravatar 函數,對於登錄的用戶,使用其註冊的郵箱來生成頭像,對於未登陸用戶,這裏就使用一個固定的郵箱來處理。
下面就開始編寫最主要的消息推送邏輯。 我採用的技術是 websocket,這樣節省了使用 Ajax 輪詢帶來的額外開銷。並且 flask 框架也有很好的 websocket 相關的擴展庫供咱們使用,即 flask-sokcetio。
首先安裝好 flask_socketio 模塊,而後引入並初始化
from flask_socketio import SocketIO, emit
socketio = SocketIO()
app = Flask(__name__)
socketio.init_app(app)
複製代碼
編寫一個 socket 發送消息的函數
def socket_send(data, user):
emit("response", {"code": '200', "msg": data, "username": user}, broadcast=True, namespace='/testnamespace')
socketio.on_event('request_for_response', socket_send, namespace='/testnamespace')
複製代碼
其中 request_for_response,response 和 testnamespace 都須要和前端代碼相對應。request_for_response 是用來接收前端傳遞到後端的消息,response 是後端傳遞消息到前端時的標識,而 namespace 則相似於做用域的概念,相互傳遞的消息都僅僅做用在 testnamespace 這個 namespace 中。
前端 JavaScript 代碼:
//websocket
var websocket_url = 'ws://' + document.domain + ':' + location.port + '/testnamespace';
var socket = io.connect(websocket_url);
//發送消息到後端
socket.emit('request_for_response',{'param':'{{rname}}'});
//監聽回覆的消息
socket.on('response',function(data){
var myDate = new Date();
var myTime = myDate.toLocaleString();
var msg = data.msg;
var username = data.username;
var currentuser = '{{ current_user.username }}';
console.log(currentuser);
if ( currentuser == username )
{
username = '你';
};
var hash = md5(username + "@hihichat.com");
var htmlData2 =
'<div class="msg_item fn-clear">'
+ ' <div class="uface"><img src="http://www.gravatar.com/avatar/' + hash + '?s=40&d=identicon&r=g" width="40" height="40" alt=""/></div>'
+ ' <div class="item_right">'
+ ' <div class="msg">' + msg + '</div>'
+ ' <div class="name_time">' + username + ' · ' + myTime +'</div>'
+ ' </div>'
+ '</div>';
$("#message_box").append(htmlData2);
$('#message_box').scrollTop($("#message_box")[0].scrollHeight + 20);
});
複製代碼
關於更多的 websocket 用法,你們能夠自行查找相關資料,這裏就不作過多介紹了。
最後,編寫接收聊天內容的 API
@app.route('/api/sendchat/<info>', methods=['GET', 'POST'])
@login_required
def send_chat(info):
rname = request.form.get("rname", "")
body = {"username": current_user.username, "msg": info}
r.zadd("msg-" + rname, json.dumps(body), time.time())
socket_send(info, current_user.username)
return info
複製代碼
將接收到的聊天內容插入到對應的 redis 中(msg-*),而後調用 websocket 函數,廣播剛剛收到的消息到全部已經鏈接的 socket 客戶端。
登錄頁面:
index 頁面:
聊天室列表頁面:
聊天室頁面:
到此爲止,其實咱們已經完成了一個簡單的聊天室功能。可是呢,該程序還有不少功能須要優化,好比程序代碼結構(當前全部後臺邏輯代碼都在一個文件中),聊天室控制(禁言,踢人等),以及非登錄用戶的處理,還要比較有意思的聊天機器人等待。
華麗麗的分割線
這裏開始,就是一些進階的功能,完善咱們的聊天室。
隨着咱們項目功能愈來愈多,把全部的邏輯代碼都寫在一個文件裏已經不太合適了,下面就經過 flask 的工廠模式,把項目代碼拆分開。
首先來看下拆分後的項目結構:
main 中主要存放後臺邏輯代碼。 static 中存放 js,css 以及用到的圖片等。 templates 中存放 HTML 模板。 models.py 中是數據庫模型。 config.py 中是一些公共的配置信息。 manage.py 中是項目的啓動信息。
下面咱們分別來看看各個模塊對應的代碼
在 config.py 中,填入代碼:
import os
import redis
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = 'hardtohard'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'chat.sqlite3')
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
pass
class TestingConfig(Config):
pass
class ProductionConfig(Config):
pass
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
複製代碼
在 app/_init_.py 中填入代碼:
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_bootstrap import Bootstrap
from flask_socketio import SocketIO
from config import config
login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'main.login'
db = SQLAlchemy()
bootstrap = Bootstrap()
socketio = SocketIO()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
socketio.init_app(app)
login_manager.init_app(app)
db.init_app(app)
bootstrap.init_app(app)
# 註冊藍本
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
複製代碼
create_app 函數就是程序的工廠函數,它接受一個配置名的參數。
藍本和程序相似,也能夠定義路由。不一樣的是,在藍本中定義的路由處於休眠狀態,直到藍本註冊到程序上後,路由才真正成爲程序的一部分。
在 main/_init_.py 中建立藍本
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, forms
複製代碼
經過實例化一個 Blueprint 類對象能夠建立藍本。這個構造函數有兩個必須指定的參數: 藍本的名字和藍本所在的包或模塊。和程序同樣,大多數狀況下第二個參數使用 Python 的 _name_ 變量便可。
對於視圖函數,須要導入相關的包,同時因爲使用了藍本,原來用來裝飾路由的 app.route 都要修改成 main.route,url_for 函數也須要增長 main 做用域,修改後的部分代碼以下:
from flask import render_template, redirect, url_for, request
from flask_login import login_required, login_user, logout_user, current_user
from . import main
from .. import db
from .forms import LoginForm
from ..models import User
from config import config
import time
import json
from ..socket_conn import socket_send
pool = redis.ConnectionPool(host='redis-12143.c8.us-east-1-3.ec2.cloud.redislabs.com', port=12143,
decode_responses=True, password='pkAWNdYWfbLLfNOfxTJinm9SO16eSJFx')
r = redis.Redis(connection_pool=pool)
@main.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user:
login_user(user)
return redirect(url_for('main.index'))
return render_template('login.html', form=form)
@main.route('/createroom', methods=["GET", 'POST'])
@login_required
def create_room():
rname = request.form.get('chatroomname', '')
if r.exists("chat-" + rname) is False:
r.zadd("chat-" + rname, current_user.username, 1)
return redirect(url_for('main.chat', rname=rname))
else:
return redirect(url_for('main.chat_room_list'))
複製代碼
在 models.py 的同級目錄下建立 socket_conn.py 文件,添加代碼以下:
from . import socketio
from flask_socketio import emit
@socketio.on('request_for_response', namespace='/testnamespace')
def socket_send(data, user):
emit("response", {"code": '200', "msg": data, "username": user}, broadcast=True, namespace='/testnamespace')
複製代碼
該函數供視圖函數調用,廣播 socket 消息。
將原來的表單代碼和數據庫模型代碼分別拷貝到這兩個文件中 forms.py
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired
from flask_wtf import FlaskForm
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), ])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Keep me logged in')
submit = SubmitField('Log in')
複製代碼
models.py
from . import db
from flask_login import UserMixin
from flask import request
import hashlib
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
password = db.Column(db.String(64))
avatar_hash = db.Column(db.String(32))
def gravatar(self, name=None, size=100, default='identicon', rating='g'):
if request.is_secure:
url = 'https://secure.gravatar.com/avatar'
else:
url = 'http://www.gravatar.com/avatar'
if name is not None:
email = name + "@hihichat.com"
else:
email = self.username + "@hihichat.com"
myhash = self.avatar_hash or hashlib.md5(email.encode('utf-8')).hexdigest()
return '{url}/{hash}?s={size}&d={default}&r={rating}'.format(url=url, hash=myhash, size=size,
default=default, rating=rating)
複製代碼
把 HTML 模板裏的 url_for() 函數都增長 main.,再放置到 templates 下面便可。
頂級文件夾中的 manage.py 文件用於啓動程序。
import os
from app import create_app, socketio
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
if __name__ == '__main__':
socketio.run(app, debug=True)
複製代碼
仍是使用 socketio.run 的方式啓動應用。
至此,代碼拆分完畢。
之前咱們都是使用瀏覽器 URL 直接新增用戶的,即函數 adduser,如今咱們作一個簡單的頁面,來規範這個操做。
定義表單
class CreateUserForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired(), EqualTo('password2',
message='Password must match.')])
password2 = PasswordField('Confirm password', validators=[DataRequired()])
submit = SubmitField('Create User')
def validate_username(self, field):
if User.query.filter_by(username=field.data).first():
raise ValidationError('Username already in use.')
複製代碼
定義了一個函數,來校驗用戶名是否重複。
修改原來的視圖函數 adduser
@main.route('/adduser', methods=['GET', 'POST'])
@login_required
def adduser():
form = CreateUserForm()
if form.validate_on_submit():
user = User(username=form.username.data, password=form.password.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('adduser.html', form=form)
複製代碼
還要再修改下 User 模型,由於當前保存的是明文密碼,修改爲使用 hash 存儲。
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
複製代碼
分別設置密碼的只讀權限,以及 hash 計算和驗證功能。
接下來編寫 HTML 模板
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('main.logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('main.login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</div> {% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, New 一個 User 吧!</h1>
</div>
{{ wtf.quick_form(form) }}
</div>
{% endblock %}
複製代碼
至此,一個簡單的新增用戶功能就行了。固然,咱們還能夠增長刪除用戶,重置密碼等功能,這些的具體實現,均可以在 GitHub 的代碼中看到,就再也不贅述了。
咱們其實並不但願全部人都可以建立聊天室,那麼就要作一個簡單的控制功能。 首先定義一個 permission 表,用來存儲建立聊天室等權限,再定義一個用戶和權限的關聯關係表
class Permission(db.Model):
id = db.Column(db.Integer, primary_key=True)
permission_name = db.Column(db.String(64), unique=True, index=True)
class RelUserPermission(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
permission_id = db.Column(db.Integer)
複製代碼
而後咱們還須要一個增長權限的表,以及一個用戶列表頁面 在 forms.py 中添加
class EditUserForm(FlaskForm):
permission = SelectMultipleField('Permission', coerce=int)
submit = SubmitField('Submit')
def __init__(self, user, *args, **kwargs):
super(EditUserForm, self).__init__(*args, **kwargs)
self.permission.choices = [(per.id, per.permission_name)
for per in Permission.query.order_by(Permission.permission_name).all()]
self.user = user
複製代碼
定義了一個初始化函數,會獲取到 Permission 表中的 name,id 等信息
接下來編寫視圖函數
@main.route('/listuser/', methods=['GET', 'POST'])
@login_required
def listuser():
user_list = User.query.all()
return render_template('listuser.html', user_list=user_list)
@main.route('/addper/', methods=['GET', 'POST'])
@login_required
def addper():
form = CreatePerForm()
if form.validate_on_submit():
per = Permission(permission_name=form.permissionname.data)
db.session.add(per)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('addper.html', form=form)
@main.route('/edituser/<int:id>/', methods=['GET', 'POST'])
@login_required
def edituser(id):
user = User.query.filter_by(id=id).first()
form = EditUserForm(user=user)
if form.validate_on_submit():
for p in form.permission.data:
rup = RelUserPermission(user_id=id, permission_id=p)
db.session.add(rup)
db.session.commit()
return redirect(url_for('main.index'))
return render_template('edituser.html', form=form)
複製代碼
三個函數,分別是展現用戶列表,增長權限,以及爲用戶添加權限。
而後再修改下 chat_room_list 函數,使得沒有權限的用戶不能展現建立聊天室的表單。
@main.route('/roomlist/', methods=["GET", 'POST'])
@login_required
def chat_room_list():
roomlist_tmp = r.keys(pattern='chat-*')
roomlist = []
can_create = False
create_room_id = Permission.query.filter_by(permission_name='createroom').first().id
rel_user_id = RelUserPermission.query.filter_by(user_id=current_user.id).first()
rel_permission = RelUserPermission.query.filter_by(user_id=current_user.id).first()
if rel_permission and rel_user_id and create_room_id:
rel_permission_id = rel_permission.permission_id
if rel_permission_id == create_room_id:
can_create = True
for i in roomlist_tmp:
i_str = str(i)
istr_list = i_str.split('-', 1)
roomlist.append(istr_list[1])
return render_template('chatroomlist.html', roomlist=roomlist, can_create=can_create)
複製代碼
這裏主要是判斷用戶是否擁有 createroom 權限,其實還有一種更加簡便,可是稍微有些繞的鑑權方式,能夠在文末的連接中找到,你們也能夠嘗試下。
最後處理 HTML 表單
對於聊天室列表頁面:
{% if can_create %}
<form action="{{ url_for('main.create_room') }}" method="POST" class="comment-form">
<div class="form-group comment-form-author">
<label for="chatroomname">Chat Room Name <span class="required">*</span></label>
<input class="form-control" id="chatroomname" name="chatroomname" type="text" value="" size="30" aria-required='true' />
</div>
<div class="form-group comment-form-comment">
<label for="description">Chat Room Description <span class="required">*</span></label>
<textarea class="form-control" id="description" name="description" cols="45" rows="6"></textarea>
</div>
<button name="submit" type="submit" id="submit" class="btn btn-primary" value="Submit Comment">Create Room</button>
</form>
{% endif %}
複製代碼
對於用戶列表頁面:
{% extends "bootstrap/base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block navbar %}
<div class="navbar navbar-inverse" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Flasky</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('main.logout') }}">Logout</a></li>
{% else %}
<li><a href="{{ url_for('main.login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</div>
</div> {% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h1>Hello, 這裏是全部的用戶哦!</h1>
</div>
{% for user in user_list %}
<a href="{{ url_for('main.edityouser', id=user.id) }}" class="btn btn-default" role="button">{{ user.username }}</a>
{% endfor %}
</div>
{% endblock %}
複製代碼
這裏爲了方便起見,當點擊用戶時,就會跳轉至編輯用戶權限的頁面。
如今,沒有權限的用戶,就不能看到建立聊天室的表單嘍!
當前的登錄,只要用戶名是正確的,不會驗證密碼,直接登錄成功,如今來處理下密碼校驗功能。其實也簡單,咱們在 User 模型中新增了一個函數 verify_password,只要登錄的時候,調用該函數來驗證密碼便可。
@main.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
return redirect(url_for('main.index'))
return render_template('login.html', form=form)
複製代碼
ok,密碼錯誤的你,已經無法再登錄了。
1.去掉 chat_room_list,join_chat_room,send_chat 和 chat 視圖函數的登錄裝飾器 @login_required 2.修改 chat_room_list,判斷當前用戶是否已經登錄
@main.route('/roomlist/', methods=["GET", 'POST'])
def chat_room_list():
roomlist_tmp = r.keys(pattern='chat-*')
roomlist = []
can_create = False
create_room = Permission.query.filter_by(permission_name='createroom').first()
if current_user.is_authenticated: # 判斷用戶是否登錄
rel_user_id = RelUserPermission.query.filter_by(user_id=current_user.id).first()
rel_permission = RelUserPermission.query.filter_by(user_id=current_user.id).first()
if rel_permission and rel_user_id and create_room:
rel_permission_id = rel_permission.permission_id
create_room_id = create_room.id
if rel_permission_id == create_room_id:
can_create = True
for i in roomlist_tmp:
i_str = str(i)
istr_list = i_str.split('-', 1)
roomlist.append(istr_list[1])
return render_template('chatroomlist.html', roomlist=roomlist, can_create=can_create)
複製代碼
3.導航欄增長 room list 入口
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="{{ url_for('main.chat_room_list') }}">Room List</a></li>
</ul>
複製代碼
4.chat 視圖函數增長判斷邏輯
@main.route('/chat/', methods=['GET', 'POST'])
def chat():
rname = request.args.get('rname', "")
ulist = r.zrange("chat-" + rname, 0, -1)
messages = r.zrange("msg-" + rname, 0, -1, withscores=True)
msg_list = []
for i in messages:
msg_list.append([json.loads(i[0]), time.strftime("%Y/%m/%d %p%H:%M:%S", time.localtime(i[1]))])
if current_user.is_authenticated:
return render_template('chat.html', rname=rname, user_list=ulist, msg_list=msg_list)
else:
email = "youke" + "@hihichat.com"
hash = hashlib.md5(email.encode('utf-8')).hexdigest()
gravatar_url = 'http://www.gravatar.com/avatar/' + hash + '?s=40&d=identicon&r=g'
return render_template('chat.html', rname=rname, user_list=ulist,
msg_list=msg_list, g=gravatar_url)
複製代碼
5.修改 send_chat 視圖
@main.route('/api/sendchat/<info>', methods=['GET', 'POST'])
def send_chat(info):
if current_user.is_authenticated:
rname = request.form.get("rname", "")
body = {"username": current_user.username, "msg": info}
r.zadd("msg-" + rname, json.dumps(body), time.time())
socket_send(info, current_user.username)
return info
else:
return info
複製代碼
當前對於未登錄的用戶(遊客),直接回復遊客發送的消息。
因爲咱們須要定時清理 redis 中保存的聊天記錄,那麼就須要一個定時任務。flask 有一個完善的插件 flask-apscheduler,可是簡單實驗了下,限制仍是挺多的,因此,我這裏選擇本身實現一個簡單的定時器功能。 建立一個 tasks.py 文件 首先定義定時器類
from threading import Timer
class Scheduler(object):
def __init__(self, sleep_time, func, mytime=None):
self.sleep_time = sleep_time
self.func = func
self._t = None
self.mytime = mytime
def start(self):
if self._t is None:
self._t = Timer(self.sleep_time, self._run)
self._t.start()
else:
raise Exception("this timer is already running")
def _run(self):
if self.mytime is not None:
self.func(self.mytime)
else:
self.func()
self._t = Timer(self.sleep_time, self._run)
self._t.start()
def stop(self):
if self._t is not None:
self._t.cancel()
self._t = None
@staticmethod
def init_app(app):
pass
複製代碼
使用線程中的 Timer 來調用真正的函數,經過 sleep time 的方式達到定時調用的效果。
而後編寫須要定時調用的函數,即清理數據的函數。
def keep_msg(mytime=None):
if mytime is not None:
expare_time = mytime
else:
expare_time = 604800
msg_list = r.keys("msg-*")
for msg in msg_list:
_ = r.zrange(msg, 0, 0)
for i in _:
score = r.zscore(msg, i)
if time.time() - score > expare_time:
r.zrem(msg, i)
複製代碼
比較簡單,判斷 redis 中的 score 是否處於過時時間,是,則刪除。
接下來註冊函數到咱們的 flask 應用當中。 在 _init_.py 中填入以下代碼:
from .tasks import Scheduler, keep_msg
sch = Scheduler(86400, keep_msg) # 每間隔一天執行
def create_app(config_name):
...
sch.init_app(app)
...
return app
複製代碼
最後還要注意的是,因爲咱們前面是使用 socketio 來啓動的應用,由於 socketio 是異步 io,而咱們的 scheduler 是阻塞運行的,因此須要在 socketio 中建立子線程來啓動。 修改 manage.py 以下:
import os
from app import create_app, socketio, sch
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
if __name__ == '__main__':
my = sch.start
socketio.start_background_task(target=my) # 啓動一個子線程
socketio.run(app, debug=True)
複製代碼
這樣,一個簡單的定時任務就作好了。
正所謂「林子大了,什麼鳥都有」,當聊天室人數不少的時候,常常會出現一些不和諧的話語和人,那麼禁言功能就頗有必要了。
首先在 views 中建立一個新的函數
@main.route('/chat/block/roomuser/', methods=['GET', 'POST'])
@login_required
def block_roomuser():
rname = request.args.get('rname', "")
new_b_user = request.args.get('b_user', "")
b_time = request.args.get('b_time', "")
if b_time is "":
r.set('b_user-' + new_b_user, new_b_user, ex=None)
else:
r.set('b_user-' + new_b_user, new_b_user, ex=b_time)
return redirect(url_for('main.room_user_list', rname=rname))
複製代碼
從前端獲取到對應的聊天室名字、須要禁言的用戶和禁言時間,而後根據禁言時間,把用戶添加到 redis 中。
再來看看禁言功能的入口函數
@main.route('/chat/roomuser/list', methods=['GET', 'POST'])
@login_required
def room_user_list():
rname = request.args.get('rname', "")
ulist = r.zrange("chat-" + rname, 0, -1)
b_user = r.keys('b_user-*')
b_user_list = []
for b in b_user:
b_user_list.append(r.get(b))
return render_template('roomuser_list.html', ulist=ulist, rname=rname, b_user=b_user_list)
複製代碼
從 redis 對應的有序集合中取出正處於禁言狀態的用戶,把這些用戶傳遞到模板供渲染使用。
對應的 roomuser_list.html 代碼爲:
<div class="container">
<div class="page-header">
<h1>Hello, 這裏是聊天室 {{ rname }} 全部的用戶哦!</h1>
</div>
{% for user in ulist %}
<p>
<button class="btn btn-primary">{{ user }}</button>
{% if user in b_user %}
<span class="label label-default">禁言中。。。</span>
{% endif %}
</p>
<p>
<div class="btn-group">
<button type="button" class="btn btn-warning">禁言</button>
<button type="button" class="btn btn-warning dropdown-toggle dropdown-toggle-split" data-toggle="dropdown">
<span class="caret"></span>
</button>
<div class="dropdown-menu">
<li><a href="{{ url_for('main.block_roomuser', rname=rname, b_user=user, b_time=300)}}">5 Mins</a></li>
<li><a href="{{ url_for('main.block_roomuser', rname=rname, b_user=user, b_time=600)}}">10 Mins</a></li>
<li><a href="{{ url_for('main.block_roomuser', rname=rname, b_user=user, b_time=18000)}}">5 Hours</a></li>
<li><a href="{{ url_for('main.block_roomuser', rname=rname, b_user=user)}}">永久禁言</a></li>
</div>
</div>
<a href="{{ url_for('main.block_roomuser', rname=rname, b_user=user, b_time=1)}}" class="btn btn-info" role="button">解禁</a>
<a href="{{ url_for('main.kick_roomuser', rname=rname, del_user=user) }}" class="btn btn-danger" role="button">踢出</a>
</p>
{% endfor %}
</div>
複製代碼
方便起見,直接使用 bootstrap 框架渲染頁面。同時這裏取了個巧,在「解禁」的時候,只是傳入 b_time 爲1,這樣1秒以後,用戶就自動從 redis 中過時了,也就成功解禁了。
最後,再來處理聊天室的消息,禁言的用戶,固然不能再發消息啦。
在 chat 函數中,添加代碼:
@main.route('/chat/', methods=['GET', 'POST'])
def chat():
...
b_user = r.keys('b_user-*')
b_user_list = []
for b in b_user:
b_user_list.append(r.get(b))
...
if current_user.is_authenticated:
return render_template('chat.html', rname=rname, user_list=ulist, msg_list=msg_list,
b_user_list=b_user_list)
複製代碼
把處於禁言的用戶取出,傳遞給模板。
在 send_chat 函數中添加代碼:
@main.route('/api/sendchat/<info>', methods=['GET', 'POST'])
def send_chat(info):
...
b_user = r.exists('b_user-%s' % current_user.username)
if b_user:
data = json.dumps({'code': 201, 'msg': 'Your are under block now!'})
return data
...
複製代碼
若是用戶處於禁言狀態,直接返回 json 消息。
修改 chat.html 中的 javascript 函數 sendToServer,增長代碼以下:
var jsondata = JSON.parse(myObj);
if ( jsondata.code == 201 || jsondata.code == 403) {
var htmlData3 = '<div class="msg_item fn-clear">'
+ ' <div class="uface"><img src="{{ url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40" alt=""/></div>'
+ ' <div class="item_right">'
+ ' <div class="msg">' + "自動回覆: " + jsondata.msg + '</div>'
+ ' <div class="name_time">' + '小黃鴨' + ' · ' + myTime +'</div>'
+ ' </div>'
+ '</div>';
$("#message_box").append(htmlData3);
$('#message_box').scrollTop($("#message_box")[0].scrollHeight + 20);
}
複製代碼
判斷返回的 json 中 code 值若是是 201 或 403,則由小黃鴨自動回覆消息。
最後的效果以下:
若是在聊天室中,這我的真的讓人忍無可忍,那麼踢人就是最好的辦法了。 其實實現思想和邏輯都和禁言相相似,這裏直接給出部分代碼
新增函數 kick_roomuser
@main.route('/chat/kick/roomuser/', methods=['GET', 'POST'])
@login_required
def kick_roomuser():
rname = request.args.get("rname", "")
del_user = request.args.get("del_user", "")
r.zrem("chat-" + rname, del_user)
return redirect(url_for('main.room_user_list', rname=rname))
複製代碼
修改 send_chat 函數
@main.route('/api/sendchat/<info>', methods=['GET', 'POST'])
def send_chat(info):
...
if current_user.is_authenticated:
rname = request.form.get("rname", "")
ulist = r.zrange("chat-" + rname, 0, -1)
if current_user.username in ulist:
body = {"username": current_user.username, "msg": info}
r.zadd("msg-" + rname, json.dumps(body), time.time())
socket_send(info, current_user.username)
data = json.dumps({'code': 200, 'msg': info})
return data
else:
data = json.dumps({'code': 403, 'msg': 'You are not in this room'})
return data
else:
data = json.dumps({'code': 202, 'msg': info})
return data
複製代碼
最後效果以下
當前,若是用戶沒有登錄,是沒法和其餘人聊天的。那麼一個友好的聊天機器人就很是有必要了。咱們可使用免費的圖靈聊天機器人,固然也能夠本身訓練一個。之前我也寫過一篇關於如何訓練聊天機器人,感興趣的小夥伴兒能夠到個人公衆號裏查看(蘿蔔大雜燴)。
在這裏也直接複用之前部署的 API 了,只須要增長几行代碼便可 修改 send_chat 函數
@main.route('/api/sendchat/<info>', methods=['GET', 'POST'])
def send_chat(info):
...
else:
base_url = 'http://luobodazahui.top:8889/api/chat/'
chat_text = requests.get(base_url + info).text
return chat_text
複製代碼
在函數中調用聊天機器人的 API 地址,將返回的內容傳遞給前端便可。
最終的效果以下:
最後的最後,咱們再來看看如何部署上線呢,畢竟沒有部署到公網的 web 服務,都是啥啥啥
首先,你得有一個公網服務器,雲主機之類的。 而後就是一頓折騰,各類安裝了。個人雲主機是 CentOS 7.5,下面的一切操做都默認是這個操做系統了。
在 CentOS 7.5 上安裝 docker
curl -fsSL https://get.docker.com
複製代碼
安裝 redis
docker pull redis
複製代碼
啓動 redis
docker run --name=myredis -p 6379:6379 -v /home/redis-data:/data -d redis redis-server --appendonly yes
複製代碼
在本機鏈接 redis,測試一下
docker exec -it 491f1051715a redis-cli
複製代碼
安裝 python3 源碼安裝 安裝編譯軟件
yum -y groupinstall "Development tools"
yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel
yum install libffi-devel -y
複製代碼
下載源碼包並解壓
wget https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tar.xz
tar Jxvf Python-3.7.1.tar.xz
複製代碼
編譯安裝
cd Python-3.7.1
./configure --prefix=/usr/local/python3
make && make install
複製代碼
建立軟鏈接
ln -s /usr/local/python3/bin/python3 /usr/local/bin/python3
ln -s /usr/local/python3/bin/pip3 /usr/local/bin/pip3
複製代碼
驗證
python3 -V
pip3 -V
複製代碼
安裝 chatterbot
因爲使用 pip 直接安裝 chatterbot,一直報錯,因此我這裏採用 conda 來安裝。
首先到 conda 官網下載好安裝腳本,而後一鍵安裝便可。
sh Miniconda3-latest-Linux-x86_64.sh
複製代碼
以後就可使用 conda 的 pip 命令來安裝 chatterbot 了。
pip install --upgrade chatterbot
pip install chatterbot_corpus
複製代碼
安裝 gevent 和 gunicorn
pip install gevent
pip install gunicorn
複製代碼
以上,全部的安裝準備工做基本完成了!
下面就很簡單了呀
首先編寫 gunicorn 啓動腳本
debug = True
loglevel = 'debug'
bind = '0.0.0.0:5000'
logfile = '/home/mychat/online_chat/log/debug.log'
workers = 1
worker_class = 'eventlet'
reload = True
複製代碼
使用 gunicorn 啓動 flask_socketio,貌似還不能很好的啓動多進程,這部分,留待之後再研究。
而後再寫一個程序啓動腳本
/root/miniconda3/bin/gunicorn -D -c /home/mychat/online_chat/gunicorn manage:app
複製代碼
最後,運行 run.sh 腳本,而後使用 ps -ef|grep python 來查看是否有進程存在
如上圖所示,說明咱們的程序已經啓動成功了,如今讓咱們來訪問 http://www.{hostip}:5000,不出意外的話,已經能夠正常訪問了。
也許有小夥伴會問,怎麼沒有用 Nginx 啊,這個本文就不詳寫了,後面咱們再一塊兒看看如何使用 Nginx 代理,綁定域名,使用 https 等。
最後,再給出一個示例網站吧,http://106.13.2.228/
小破雲主機,你們輕拍!
最後,再安利下公衆號,「蘿蔔大雜燴」,歡迎關注!
全文完!