头条在线消息推送实现
需求
在头条的Flask应用中,用户关注后需要推送消息,通过消息队列告知IM服务为用户进行推送
在Socket.IO 框架中可以选择使用以下两种方式作为消息中间件:
- 使用Redis
mgr = socketio.RedisManager('redis://')
sio = socketio.Server(client_manager=mgr)
- 使用RabbitMQ
pip install kombu
mgr = socketio.KombuManager('amqp://')
sio = socketio.Server(client_manager=mgr)
实现
因为要给指定的用户推送消息,所以需要用到用户的身份,用户在客户端携带JWT连接SocketIO服务器,我们在服务器端对jwt token进行验证,对于验证出用户身份(user_id)的客户端,将其添加到名为用户id的room房间中,方便按照user_id进行推送。
socketio服务端编写
在toutiao-backend/im/main.py中补充添加搜寻包的路径,方便使用utils中的jwt_utils模块
import eventlet
eventlet.monkey_patch()
import eventlet.wsgi
import sys
import os
# 补充搜索包路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'common'))
if len(sys.argv) < 2:
print('Usage: python main.py [port]')
exit(1)
port = int(sys.argv[1])
from server import app
import chat
import notify
SERVER_ADDRESS = ('', port)
sock = eventlet.listen(SERVER_ADDRESS)
eventlet.wsgi.server(sock, app)
在toutiao-backend/im/server.py文件中补充消息队列rabbitmq的配置信息和jwt使用的秘钥
import socketio
RABBITMQ = 'amqp://python:rabbitmqpwd@localhost:5672/toutiao'
JWT_SECRET = 'TPmi4aLWRbyVq8zu9v82dWYW17/z+UvRnYTt4P6fAXA'
mgr = socketio.KombuManager(RABBITMQ)
sio = socketio.Server(async_mode='eventlet', client_manager=mgr)
app = socketio.Middleware(sio)
在toutiao-backend/im目录中新建notify.py
from server import sio, JWT_SECRET
from werkzeug.wrappers import Request
from utils.jwt_util import verify_jwt
def check_jwt_token(environ):
"""
检验jwt token
:param environ:
:return:
"""
request = Request(environ)
token = request.args.get('token')
if token:
payload = verify_jwt(token, JWT_SECRET)
if payload:
user_id = payload.get('user_id')
return user_id
return None
@sio.on('connect')
def on_connect(sid, environ):
"""
与客户端建立连接后执行
"""
# 检验连接客户端的jwt token
user_id = check_jwt_token(environ)
print('user_id={}'.format(user_id))
# 若检验出user_id,将此客户端添加到user_id的room中
if user_id:
sio.enter_room(sid, str(user_id))
@sio.on('disconnect')
def on_disconnect(sid):
"""
与客户端断开连接时执行
"""
# 客户端离线时将客户端从所有房间中移除
rooms = sio.rooms(sid)
for room in rooms:
sio.leave_room(sid, room)
flask web服务端编写
在toutiao-backend/toutiao/__init__.py
中 添加sio对象的创建
import socketio
def create_app(config, enable_config_file=False):
"""
创建应用
:param config: 配置信息对象
:param enable_config_file: 是否允许运行环境中的配置文件覆盖已加载的配置信息
:return: 应用
"""
...
# socket.io
app.sio = socketio.KombuManager(app.config['RABBITMQ'], write_only=True)
...
在toutiao-backend/toutiao/resources/user/following.py 用户关注接口视图中添加发送事件消息
class FollowingListResource(Resource):
"""
关注用户
"""
method_decorators = {
'post': [login_required],
'get': [login_required],
}
def post(self):
"""
关注用户
"""
# 关注用户的数据库保存
...
# 发送关注通知
_user = cache_user.UserProfileCache(g.user_id).get()
_data = {
'user_id': g.user_id,
'user_name': _user['name'],
'user_photo': _user['photo'],
'timestamp': int(time.time())
}
current_app.sio.emit('following notify', data=_data, room=str(target))
return {'target': target}, 201
效果
壬戌之秋,七月既望,苏子与客泛舟游于赤壁之下。清风徐来,水波不兴。举酒属客,诵明月之诗,歌窈窕之章。少焉,月出于东山之上,徘徊于斗牛之间。白露横江,水光接天。纵一苇之所如,凌万顷之茫然。浩浩乎如冯虚御风,而不知其所止;飘飘乎如遗世独立,羽化而登仙。 于是饮酒乐甚,扣舷而歌之。歌曰:“桂棹兮兰桨,击空明兮溯流光。渺渺兮予怀,望美人兮天一方。”客有吹洞箫者,倚歌而和之。其声呜呜然,如怨如慕,如泣如诉,余音袅袅,不绝如缕。舞幽壑之潜蛟,泣孤舟之嫠妇。 苏子愀然,正襟危坐而问客曰:“何为其然也?”客曰:“月明星稀,乌鹊南飞,此非曹孟德之诗乎?西望夏口,东望武昌,山川相缪,郁乎苍苍,此非孟德之困于周郎者乎?方其破荆州,下江陵,顺流而东也,舳舻千里,旌旗蔽空,酾酒临江,横槊赋诗,固一世之雄也,而今安在哉?况吾与子渔樵于江渚之上,侣鱼虾而友麋鹿,驾一叶之扁舟,举匏樽以相属。寄蜉蝣于天地,渺沧海之一粟。哀吾生之须臾,羡长江之无穷。挟飞仙以遨游,抱明月而长终。知不可乎骤得,托遗响于悲风。” 苏子曰:“客亦知夫水与月乎?逝者如斯,而未尝往也;盈虚者如彼,而卒莫消长也。盖将自其变者而观之,则天地曾不能以一瞬;自其不变者而观之,则物与我皆无尽也,而又何羡乎!且夫天地之间,物各有主,苟非吾之所有,虽一毫而莫取。惟江上之清风,与山间之明月,耳得之而为声,目遇之而成色,取之无禁,用之不竭,是造物者之无尽藏也,而吾与子之所共适。” 客喜而笑,洗盏更酌。肴核既尽,杯盘狼籍。相与枕藉乎舟中,不知东方之既白。