51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1826|回复: 1
打印 上一主题 下一主题

[原创] 如何用Python写一个在线聊天室?

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2022-11-16 16:11:43 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
本帖最后由 草帽路飞UU 于 2022-11-16 16:16 编辑

今天从头开始做一个在线聊天网站,网上各种各样的聊天工具已经很多了,为啥还要做这么一个聊天工具呢,无他,兴趣耳!

  今天先完成第一部分,搭建起聊天网站的整体框架。


  整体技术


  ·flask 框架

  · flask_login 的使用


  · jquery 简单应用


  搭建权限框架


  还是使用 Flask 来搭建后台应用,使用 flask-login 扩展来处理用户登陆鉴权逻辑。首先定义登陆表单。

  class LoginForm(FlaskForm):


      username = StringField('Username', validators=[DataRequired(), ])


      password = PasswordField('Password', validators=[DataRequired()])


      remember_me = BooleanField('Keep me logged in')


      submit = SubmitField('Log in')




  一个简单的登陆表单,不做过多解释。


  接下来定义数据库结构。


  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 问题。


  下面就可以定义用户登陆表单:


  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() 函数,登陆。用户密码的使用,也留到后面再做处理。


  其中 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-head
er">



              <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 页面,主要使用了 Ajax 来处理文字交互。首先来看看主体页面,在 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.notallow=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 和自己训练的聊天机器人,来实现真正的在线聊天室。






本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

  • TA的每日心情
    开心
    2024-10-4 10:34
  • 签到天数: 1208 天

    连续签到: 1 天

    [LV.10]测试总司令

    2#
    发表于 2023-10-26 13:49:52 | 只看该作者
    聊五毛钱的
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-22 03:26 , Processed in 0.066410 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表