从头搭建一个在线聊天室(四)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

转载声明:转载请注明出处,本技术博客是本人原创文章

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 从头搭建一个在线聊天室(四)

微信公众号:**萝卜大杂烩** 关注可了解更多的原创内容。问题或建议,请公众号留言或加本人微信; **如果你觉得文章对你有帮助,欢迎加微信交流**

今天继续完善我们的在线聊天室

TODO

  • 定时清理过期消息
  • 禁言功能
  • 踢人功能
  • 对接聊天机器人
  • 禁言功能

    对接聊天机器人

    清理过期消息

    由于我们需要定时清理 redis 中保存的聊天记录,那么就需要一个定时任务。flask 有一个完善的插件 flask-apscheduler,但是简单试用了下,限制还是挺多的。所以,我这里选择自己实现一个简单的定时器功能。
    创建一个 tasks.py 文件
    首先定义定时器类

    
     1from threading import Timer
     2
     3
     4class Scheduler(object):
     5    def __init__(self, sleep_time, func, mytime=None):
     6        self.sleep_time = sleep_time
     7        self.func = func
     8        self._t = None
     9        self.mytime = mytime
    10
    11    def start(self):
    12        if self._t is None:
    13            self._t = Timer(self.sleep_time, self._run)
    14            self._t.start()
    15        else:
    16            raise Exception("this timer is already running")
    17
    18    def _run(self):
    19        if self.mytime is not None:
    20            self.func(self.mytime)
    21        else:
    22            self.func()
    23        self._t = Timer(self.sleep_time, self._run)
    24        self._t.start()
    25
    26    def stop(self):
    27        if self._t is not None:
    28            self._t.cancel()
    29            self._t = None
    30
    31    @staticmethod
    32    def init_app(app):
    33        pass
    

    使用线程中的 Timer 来调用真正的函数,通过 sleep time 的方式达到定时调用的效果。

    然后编写需要定时调用的函数,即清理数据的函数。

    
     1def keep_msg(mytime=None):
     2    if mytime is not None:
     3        expare_time = mytime
     4    else:
     5        expare_time = 604800
     6    msg_list = r.keys("msg-*")
     7    for msg in msg_list:
     8        _ = r.zrange(msg, 0, 0)
     9        for i in _:
    10            score = r.zscore(msg, i)
    11            if time.time() - score  expare_time:
    12                r.zrem(msg, i)
    

    比较简单,判断 redis 中的 score 是否处于过期时间,是,则删除。

    接下来注册函数到我们的 flask 应用当中。
    init.py 中填入如下代码:

    
     1from .tasks import Scheduler, keep_msg
     2
     3
     4sch = Scheduler(86400, keep_msg)  # 每间隔一天执行
     5
     6
     7def create_app(config_name):
     8    ...
     9    sch.init_app(app)
    10    ...
    11    return app
    

    最后还要注意的是,由于我们前面是使用 socketio 来启动应用的,因为 socketio 是异步 io,而我们的 scheduler 是阻塞运行的,所以需要在 socketid 中创建子线程来启动。

    修改 manage.py 如下:

    
     1import os
     2from app import create_app, socketio, sch
     3
     4
     5app = create_app(os.getenv('FLASK_CONFIG') or 'default')
     6
     7
     8if __name__ == '__main__':
     9    my = sch.start
    10    socketio.start_background_task(target=my)  # 启动一个子线程
    11    socketio.run(app, debug=True)
    

    这样,一个简单的定时任务就做好了。

    禁言功能

    正所谓“林子大了,什么鸟都有”,当聊天室人数很多的时候,经常会出现一些不和谐的话语,那么禁言功能就很有必要了。

    首先在 views 中创建一个新的函数

    
     1@main.route('/chat/block/roomuser/', methods=['GET', 'POST'])
     2@login_required
     3def block_roomuser():
     4    rname = request.args.get('rname', "")
     5    new_b_user = request.args.get('b_user', "")
     6    b_time = request.args.get('b_time', "")
     7    if b_time is "":
     8        r.set('b_user-' + new_b_user, new_b_user, ex=None)
     9    else:
    10        r.set('b_user-' + new_b_user, new_b_user, ex=b_time)
    11    return redirect(url_for('main.room_user_list', rname=rname))
    

    从前端获取到对应的聊天室名字、需要禁言的用户和禁言时间,然后根据禁言时间,把用户添加到 redis 中。

    再来看看禁言功能的入口函数

    
     1@main.route('/chat/roomuser/list', methods=['GET', 'POST'])
     2@login_required
     3def room_user_list():
     4    rname = request.args.get('rname', "")
     5    ulist = r.zrange("chat-" + rname, 0, -1)
     6    b_user = r.keys('b_user-*')
     7    b_user_list = []
     8    for b in b_user:
     9        b_user_list.append(r.get(b))
    10    return render_template('roomuser_list.html', ulist=ulist, rname=rname, b_user=b_user_list)
    

    从 redis 对应的有序集合中取出正处于禁言状态的用户,把这些用户传递到模板供渲染使用。

    对应的 roomuser_list.html 代码为:

    
     1div class="container"
     2    div class="page-header"
     3        h1Hello, 这里是聊天室  rname }} 所有的用户哦!/h1
     4    /div
     5     for user in ulist %}
     6    p
     7        button class="btn btn-primary" user }}/button
     8         if user in b_user %}
     9        span class="label label-default"禁言中。。。/span
    10         endif %}
    11    /p
    12    p
    13        div class="btn-group"
    14            button type="button" class="btn btn-warning"禁言/button
    15            button type="button" class="btn btn-warning dropdown-toggle dropdown-toggle-split" data-toggle="dropdown"
    16                span class="caret"/span
    17            /button
    18            div class="dropdown-menu"
    19                lia href=" url_for('main.block_roomuser', rname=rname, b_user=user, b_time=300)}}"5 Mins/a/li
    20                lia href=" url_for('main.block_roomuser', rname=rname, b_user=user, b_time=600)}}"10 Mins/a/li
    21                lia href=" url_for('main.block_roomuser', rname=rname, b_user=user, b_time=18000)}}"5 Hours/a/li
    22                lia href=" url_for('main.block_roomuser', rname=rname, b_user=user)}}"永久禁言/a/li
    23            /div
    24        /div
    25        a href=" url_for('main.block_roomuser', rname=rname, b_user=user, b_time=1)}}" class="btn btn-info" role="button"解禁/a
    26        a href=" url_for('main.kick_roomuser', rname=rname, del_user=user) }}" class="btn btn-danger" role="button"踢出/a
    27    /p
    28     endfor %}
    29/div
    

    方便起见,直接使用 bootstrap 框架渲染页面。同时这里取了个巧,在“解禁”的时候,只是传入 b_time 为1,这样1秒之后,用户就自动从 redis 中过期了,也就成功解禁了。

    最后,再来处理聊天室的消息,禁言的用户,当然不能再发消息啦。

    在 chat 函数中,添加代码:

    
     1@main.route('/chat/', methods=['GET', 'POST'])
     2def chat():
     3    ...
     4    b_user = r.keys('b_user-*')
     5    b_user_list = []
     6    for b in b_user:
     7        b_user_list.append(r.get(b))
     8    ...
     9    if current_user.is_authenticated:
    10        return render_template('chat.html', rname=rname, user_list=ulist, msg_list=msg_list,
    11                               b_user_list=b_user_list)
    

    把处于禁言的用户取出,传递给模板。

    在 send_chat 函数中添加代码:

    
    1@main.route('/api/sendchat/info', methods=['GET', 'POST'])
    2def send_chat(info):
    3    ...
    4    b_user = r.exists('b_user-%s' % current_user.username)
    5    if b_user:
    6        data = json.dumps({'code': 201, 'msg': 'Your are under block now!'})
    7        return data
    8    ...
    

    如果用户处于禁言状态,直接返回 json 消息。

    修改 chat.html 中的 javascript 函数 sendToServer,增加代码如下:

    
     1var jsondata = JSON.parse(myObj);
     2               if ( jsondata.code == 201 || jsondata.code == 403) {
     3                   var htmlData3 =   'div class="msg_item fn-clear"'
     4                   + '   div class="uface"img src=" url_for('static', filename='chat/images/duck.jpg')}}" width="40" height="40"  alt=""//div'
     5                   + '   div class="item_right"'
     6                   + '     div class="msg"' + "自动回复: " + jsondata.msg + '/div'
     7                   + '     div class="name_time"' + '小黄鸭' + ' · ' + myTime +'/div'
     8                   + '   /div'
     9                   + '/div';
    10               $("#message_box").append(htmlData3);
    11               $('#message_box').scrollTop($("#message_box")[0].scrollHeight + 20);
    12               }
    

    判断返回的 json 中 code 值如果是 201 或 403,则由小黄鸭自动回复消息。

    最后的效果如下:

    从头搭建一个在线聊天室(四)

    踢人

    如果在聊天室中,这个人真的让人忍无可忍,那么踢人就是最好的办法了。
    其实实现思想和逻辑都和禁言相类似,这里直接给出部分代码

    新增函数 kick_roomuser

    
    1@main.route('/chat/kick/roomuser/', methods=['GET', 'POST'])
    2@login_required
    3def kick_roomuser():
    4    rname = request.args.get("rname", "")
    5    del_user = request.args.get("del_user", "")
    6    r.zrem("chat-" + rname, del_user)
    7    return redirect(url_for('main.room_user_list', rname=rname))
    

    修改 send_chat 函数

    
     1@main.route('/api/sendchat/info', methods=['GET', 'POST'])
     2def send_chat(info):
     3    ...
     4    if current_user.is_authenticated:
     5        rname = request.form.get("rname", "")
     6        ulist = r.zrange("chat-" + rname, 0, -1)
     7        if current_user.username in ulist:
     8            body = {"username": current_user.username, "msg": info}
     9            r.zadd("msg-" + rname, json.dumps(body), time.time())
    10            socket_send(info, current_user.username)
    11            data = json.dumps({'code': 200, 'msg': info})
    12            return data
    13        else:
    14            data = json.dumps({'code': 403, 'msg': 'You are not in this room'})
    15            return data
    16    else:
    17        data = json.dumps({'code': 202, 'msg': info})
    18        return data
    

    最后效果如下

    从头搭建一个在线聊天室(四)

    对接聊天机器人

    当前,如果用户没有登陆,是无法和其他人聊天的。那么一个友好的聊天机器人就非常有必要了。我们可以使用免费的图灵聊天机器人,当然也可以自己训练一个。以前我也写过一篇关于如何训练聊天机器人,感兴趣的小伙伴儿可以戳这里()。

    在这里直接复用以前部署的 API 了,只需要增加几行代码即可
    修改 send_chat 函数

    
    1@main.route('/api/sendchat/info', methods=['GET', 'POST'])
    2def send_chat(info):
    3    ...
    4    else:
    5        base_url = 'http://luobodazahui.top:8889/api/chat/'
    6        chat_text = requests.get(base_url + info).text
    7        return chat_text
    

    在函数中调用聊天机器人的 API 地址,将返回的内容传递给前端即可。

    最终的效果如下:

    从头搭建一个在线聊天室(四)

    华丽丽的分割线

    到今天为止,从头搭建在线聊天室系列就告一段落了,如果大家认为项目还可以,欢迎到 GitHub 上给个 star,同时也欢迎 fork,后面再有任何的优化或者功能增强,都会直接提交到 GitHub 上,谢谢大家的阅读!

    GitHub:https://github.com/zhouwei713/online-chat

    猜泥稀饭:

    从头系列:

    原文始发于微信公众号(萝卜大杂烩):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    转载声明:转载请注明出处,本技术博客是本人原创文章

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 从头搭建一个在线聊天室(四)


     上一篇
    Python 面试题大全系列(三) Python 面试题大全系列(三)
    微信公众号:**萝卜大杂烩** 关注可了解更多的原创内容。问题或建议,请公众号留言或加本人微信; **如果你觉得文章对你有帮助,欢迎加微信交流** 今天继续分享 Python 相关的面试题,你准备好了嘛! 综合篇(一),网络编程 1
    2021-04-06
    下一篇 
    Python 面试题大全系列(一) Python 面试题大全系列(一)
    微信公众号:**萝卜大杂烩** 关注可了解更多的原创内容。问题或建议,请公众号留言或加本人微信; **如果你觉得文章对你有帮助,欢迎加微信交流** 从今天开始,陆续更新一些 Python 相关的面试题,在学习的路上,与君共勉,我的文
    2021-04-06