网页即时通讯项目

设计思路

项目名称:网页即时通讯
项目描述:实现通信系统,使用户能够通过浏览器进行用户的登录,进行即时聊天
开发环境:Centos7.6 -vim,g++,makefile,git
使用技术:MVC框架,MYSQL,mongoose,JsonCpp,Vue.js,jQuery ajax
框架设计:使用MVC框架
M-数据管理模块:用户数据的管理
V-前端页面模块:前端页面的展示
C-业务逻辑模块:实现网络通信,进行系统的业务处理
在这里插入图片描述

详细设计

一. 数据管理模块

MYSQL数据库是一个C/S框架的数据库

注意事项 :
MySQL数据库对语句中大小写不敏感
库名, 表名, 表中字段不能使用关键字
每条语句最后都要以英文分号结尾

1.MySQL数据库

特点:数据库对语句中的大小写不敏感,库名,表名,表中字段不能使用关键字,每条语句都要以分号结尾

库的操作:

建库:
create database if not exists db_name;
如果db_name 不存在则创建

删库:
drop database db_name;
删除db_name数据库

show database;
显示所有数据库
use database;
选择要操作的数据库

表的操作:

建表:
create table if not exists db_name(字段信息);

删表:
drop table tb_name;
删除tb_name表

show tables;
显示数据库所有表
desc tb_name;
描述tb_name表的字段属性信息

数据的操作

增:insert tbname value(fields_value...);
删:delete from tbname where condition..;
查:select *from tbname;
	select fields_name... from tbname;
改:update tbname set fields_name = fields_value where condition;

2.用户表的设计

ID,用户名,密码,状态

create table if not exists tb_user(
	id int primary key auto_increment,
	name varchar(32) not null unique comment '用户名',
	passwd varchar(32) not null nuique comment'密码',
	status varchar(8) not null comment'状态信息-offline/online'
);

3. db.sql文件

创建一个db.sql,将SQL语句写入其中
打开数据库时,mysql -uroot -p <db.sql导入,自动执行

create database if not exists im_system;
use im_system;
create table if not exists tb_user(
	id int primary key auto_increment,
	name varchar(32) not null unique comment '用户名',
	passwd varchar(32) not null nuique comment'密码',
	status varchar(8) not null comment'状态信息-offline/online'
);

4.MySQL提供的接口

在项目中,我们要通过数据管理模块完成对数据库的访问,我们要自己实现MYSQL客户端,通过MySQL提供的接口进行客户端搭建
1.初始化mysql句柄

MYSQL* mysql_init(MYSQL* mysql)
参数通常为null,表明动态分配一块空间进行初始化

2.连接mysql服务器

MYSQL* mysql_real_connect(MYSQL* mysql,
const char *host,const char *user,const char *passwd,
const char *db,unsigned int post,
const char *unix_socket,unsigned long client_flag)
mysql: 初始化完成的句柄
host: 要连接的mysql服务器IP
user: mysql数据库用户名
passwd: 数据库访问密码
db: 默认要选择使用的数据库名称
port: mysql服务端口, 0默认表示3306端口
unix_socket: 指定socket或管道, 通常为NULL
client_flag: 一些选项操作标志位, 通常置0
返回值: 返回句柄的空间首地址, 出错返回NULL

3.设置客户端字符编码集

int mysql_set_character_set(MYSQL *mysql,const char* csname)
mysql:mysql句柄
csname:utf-8
返回值:成功返回0,失败返回非0

4.选择要使用的数据库

int mysql_select_db(MYSQL* mysql,const char* db)
mysql:mysql句柄
db:数据库名称
返回值:成功返回0,失败返回非0

5.表以及表中数据的各项操作

int mysql_query(MYSQL* mysql,const char* stmt_str)
mysql: mysql句柄
stmt_str: 要执行的sql语句
返回值: 成功返回0, 失败返回非0

增删改三种操作只要执行语句成功即可,但查询不一样,在语句成功之后还要返回获取结果

6.从远程服务器获取结果集

1.将查询结果获取到本地
MYSQL_RES* mysql_store_result(MYSQL *mysql)
2.获取查询结果中的条数
uint64_t mysql_num_rows(MYSQL_RES *result)
3.获取查询结果中的列数
unsigned int mysql_num_fields(MYSQL_RES *result)
4.遍历结果集,每次获取一条数据
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
5.释放结果集
void mysql_free_result(MYSQL_RES *result)

MYSQL_ROW 是一个二级指针char**
MYSQL_ROW row表示一行数据
row[0]表示第0列数据,row[1]表示第1列数据

7.关闭数据库释放资源

void mysql_close(MYSQL *mysql)

8.获取mysql接口调用失败的原因

const char* mysql_error(MYSQL *mysql)

Json数据类型

在数据管理模块,我们要考虑数据对象作为参数进行传递,我们可能要组织很多的数据对象,但参数过多会降低效率,且结果过于复杂
因此使用Json数据系列化方式
我们需要安装Jsoncpp库:Jsoncpp库可以将多个数据对象序列化为json格式的字符串,或者将json格式的字符串反序列化得到各个数据对象

Json::Value 中间的数据交换类
Json::Reader 实现反序列化,将json格式字符串解析得到Json::Value 对象
Json::Writer 实现序列化,将json::Value对象中的数据组织成json字符串
在这里插入图片描述
提供一个例子可以展示接口和对应数据格式的转换

#include<iostream>
#include<jsoncpp/json/json.h>
#include<string>
using namespace std;

int main()
{
	Json::Value value;
	value["name"]="花城";
	value["age"] = 19;
	value["score"]=95;

	Json::StyledWriter writer;
	string str = writer.write(value);
	cout<<str<<endl;

	Json::Value value1;
	Json::Reader reader;
	reader.parse(str,value1);

	cout<<value1["name"].asString()<<endl;
	cout<<value1["age"].asInt()<<endl;
	cout<<value1["score"].asFloat()<<endl;
	return 0;
}

6.代码实现业务管理模块

#include <cstdio>
#include <iostream>
#include <mysql/mysql.h>
#include <string>
#include <jsoncpp/json/json.h>
#include <mutex>

using namespace std;

namespace im {
#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PASS ""
#define MYSQL_DB   "im_system"
#define ONLINE     "online"
#define OFFLINE    "offline"

  class  TableUser {
    public:
      TableUser() :_mysql(NULL) {
        //完成数据库操作的初始化
        _mysql = mysql_init(NULL);
        if(_mysql == NULL) {
          printf("init mysql instance failed!\n");
          exit(-1);
        }
        //连接服务器
        if(mysql_real_connect(_mysql, MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB,0,NULL,0)==NULL) {
          printf("connect mysql server failed!\n"); 
          mysql_close(_mysql);
          exit(-1);
        }
        //设置字符集
        if(mysql_set_character_set(_mysql, "utf8") != 0) {
          printf("set client character failed:%s\n", mysql_error(_mysql));
          mysql_close(_mysql);
          exit(-1);
        }
        //选择数据库
        //mysql_select_db(_mysql, MYSQL_DB);
      }
      

      ~TableUser() {
        //完成数据库句柄的销毁
        if(_mysql)
          mysql_close(_mysql);
      }

      
      bool Insert(const string& name, const string& passwd) {
#define INSERT_USER "insert tb_user value(null, '%s', MD5('%s'), '%s');"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, INSERT_USER, name.c_str(), passwd.c_str(), OFFLINE);
        return QuerySql(tmp_sql);
      }


      bool Delete(const string& name) {
#define DELETE_USER "delete from tb_user where name='%s';"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, DELETE_USER, name.c_str());
        return QuerySql(tmp_sql);
      }

      //更新状态
      bool UpdateStatus(const string& name, const string& status) {
#define UPDATE_USER_STATUS "update tb_user set status='%s' where name='%s';"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, UPDATE_USER_STATUS, status.c_str(), name.c_str());
        return QuerySql(tmp_sql);
      } 

      //更新密码
      bool UpdatePasswd(const string& name, const string& passwd) {
#define UPDATE_USER_PASSWD "update tb_user set passwd=MD5('%s') where name='%s';"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, UPDATE_USER_PASSWD, passwd.c_str(), name.c_str());
        return QuerySql(tmp_sql);
      }

      //获取单个用户信息
      bool SelectOne(const string& name, Json::Value* user) {
#define SELECT_ONE_USER "select id, passwd, status from tb_user where name='%s';"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, SELECT_ONE_USER, name.c_str());
        _mutex.lock();
        if(QuerySql(tmp_sql) == false) {
          _mutex.unlock();
          return false;
        }
        //保存结果集
        MYSQL_RES* res = mysql_store_result(_mysql);
        _mutex.unlock();
        if(res == NULL) {
          printf("select one user store result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        //获取结果集条数
        int num_row = mysql_num_rows(res);
        if (num_row != 1) {
          printf("one user result count error!\n");
          mysql_free_result(res);
          return false;
        }
        
        for(int i = 0; i < num_row; i++) {
          MYSQL_ROW row = mysql_fetch_row(res);
          (*user)["id"] = stoi(row[0]);
          (*user)["name"] = name.c_str();
          (*user)["passwd"] = row[1];
          (*user)["status"] = row[2];
        }
        mysql_free_result(res);
        return true;
      }

      //获取所有用户信息
      bool SelectAll(Json::Value* users) {
#define SELECT_ALL_USER "select id, name, passwd, status from tb_user;"
        _mutex.lock();
        if(QuerySql(SELECT_ALL_USER) == false) {
          _mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql);
        _mutex.unlock();
        if(res == NULL) {
          printf("select one user store result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int num_row = mysql_num_rows(res);
        for(int i = 0; i < num_row; i++) {
          MYSQL_ROW row = mysql_fetch_row(res);
          Json::Value user;
          user["id"] = stoi(row[0]);
          user["name"] = row[1];
          user["passwd"] = row[2];
          user["status"] = row[3];
          users->append(user);
        }
        mysql_free_result(res);
        return true;
      }

      //验证用户登录信息
      bool VerifyUser(const string& name, const string& passwd) {
#define VERIFY_USER "select * from tb_user where name='%s' and passwd=MD5('%s');"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, VERIFY_USER, name.c_str(), passwd.c_str());
        _mutex.lock();
        if(QuerySql(tmp_sql) == false) {
          _mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql);
        _mutex.unlock();
        if(res == NULL) {
          printf("verify user store result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int num_row = mysql_num_rows(res);
        if(num_row != 1) {
          printf("verify user faled!\n");
          mysql_free_result(res);
          return false;
        }
        mysql_free_result(res);
        return true;
      } 

      //查询用户是否存在
      bool Exists(const string& name) {
#define EXISTS_USER "select * from tb_user where name='%s';"
        char tmp_sql[4096] = {0};
        sprintf(tmp_sql, EXISTS_USER, name.c_str());
        _mutex.lock();
        if(QuerySql(tmp_sql) == false) {
          _mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql);
        _mutex.unlock();
        if(res == NULL) {
          printf("exists user store result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int num_row = mysql_num_rows(res);
        if(num_row != 1) {
          printf("have no user!\n");
          mysql_free_result(res);
          return false;
        }
        mysql_free_result(res);
        return true;
      }

    private:
      //执行sql语句
      bool QuerySql(const string& sql) {
        if(mysql_query(_mysql, sql.c_str()) != 0) {
          printf("query sql:[%s] failed:%s\n", sql.c_str(), mysql_error(_mysql));
          return false;
        }
         return true;
      }

    private:
      MYSQL* _mysql;
      mutex _mutex;
  };
}

二. 业务逻辑模块

实现网络通信模块,对各个请求进行相应的业务处理模块

1. websocket协议

http是一种简单的请求-响应协议,客户端发送请求,服务器端针对请求进行相应

在实现网页即时通讯中,每个用户发送一条信息后,如果使用http协议,其他用户需要间隔几秒就去服务器获取当前的聊天信息,繁琐复杂。因此使用websocket协议实现聊天信息的消息推送,使用mongoose库搭建http服务器

在这里插入图片描述

2.mongoose库搭建http服务器

搭建流程如下:

  1. 定义操作句柄 struct mg_mgr mgr;
  2. 初始化操作句柄 void mg_mgr_init();
  3. 创建http的监听连接–创建一个socket,定义为http协议通信,对于请求的数据进行http协议格式解析
    struct mg_connection mg_http_listen(struct mg_mgr,const char *url,mg_event _handler_t fn, void *fn_data);
    static void fn(struct mg_connection *c,int ev,void *ev_data,void *fn_data)
  4. 开始轮询监听(等待所有连接,有数据到来进行处理)void mg_mgr_poll(struct mg_mgr *mgr,int ms);

在这里插入图片描述
针对监听得到的不同数据,使用不同的处理函数进行处理

3.业务处理接口设计

实现业务处理模块:注册,登录,聊天,静态页面的请求
静态页面,注册,登录功能都是通过HTTP协议实现传输
聊天通过websocket协议进行传输

1.一个用户,首先注册一个用户名,然后登录,登录成功之后获取聊天页面,
2.进入聊天页面之后,建立一个websocket通信用于聊天信息的传输

接口设计如下:
在这里插入图片描述
登录成功后,就会让客户端请求聊天页面,进行聊天,在聊天中我们需要知道是谁在发消息,所以需要进行客户端的状态维护
使用 Cookie+Seission进行状态管理
用户登陆成功后,服务端为客户端创建session会话,记录用户名以及对应的状态信息,生成session_id,通过Cookie将id+name返回给客户端
Set-Cookie: SESSION ID = 23211111123;NAME=“huacheng”;path = /
浏览器客户端下次请求的时候,就会将这些信息自动发送给服务器,服务器收到后就可以分辨信息是谁发送的
在这里插入图片描述

4.网页即时通信功能流程

1.客户端浏览器请求注册页面(静态网页请求)-------返回页面文件
2填写注册的用户信息,点击注册提交〈(提交用户名和密码到服务器)--------检测用户名是否重复,注册用户-添加到数据库
3.客户端浏览器请求登录页面(静态网页请求)-----------返回页面文件
4.界面中填写登录信息,点击登录提交(提交用户名和密码到服务器)--------检测登录信息5.登录成功,请求聊天界面(静态网页请求)-----------返回页面文件
6.建立websocket聊天通信的通道(http协议切换)-----协议切换确认
7.使用wensocket协议进行聊天通信,发送消息---------将聊天信息广播给其他的用户
数据管理模块∶用户信息数据库表的设计+代码中用户数据访问类的实现(用户的增删改查+用户验证+是否存在)
业务处理模块:搭建http服务器,设计通信接口-明确什么样的请求对应哪些功能业务–(静态网页+注册+登录+协议切换+聊天消息)前端界面模块:使用html+css+js设计前端展示界面(注册+登录界面+聊天界面)

5.代码实现业务逻辑模块
struct session {
    uint64_t session_id;
    string name;
    string status;
    double login_time;
    double last_atime;
    struct mg_connection* conn;
  };

  class IM {
    public:
      ~IM() {
        mg_mgr_free(&_mgr);
      }

      static bool Init(const string &port = "9000") {
        _tb_user = new TableUser();
        mg_mgr_init(&_mgr);
        string addr = "0.0.0.0:";
        addr += port;
        _lst_http = mg_http_listen(&_mgr, addr.c_str(), callback, &_mgr);
        if(_lst_http == NULL) {
          cout << "http listen failed!\n";
          return false;
        }
        return true;
      }

      static bool Run() {
        while(1)
          mg_mgr_poll(&_mgr, 1000);
        return true;
      }

    private:
      //分割字符串
      static int Split(const string& str, const string& sep, vector<string>* vec) {
        //string::find(s, pos); 从pos位置开始找s分隔符, 返回所在位置
        //string::substr(pos, n); 从pos位置开始截取n长度数据
        int count = 0;
        size_t pos = 0, idx = 0;
        while(1) {
          //从字符串str的idx位置开始找sep分隔符
          pos = str.find(sep, idx);
          if(pos == string::npos) {
            break;
          }
          vec->push_back(str.substr(idx, pos - idx));
          idx = pos + sep.size();
          count++;
        }
        //处理最后一个字符串
        if(idx < str.size()) {
          vec->push_back(str.substr(idx));
          count++;
        }
        return count;
      }

      //SESSION_ID=351263317318; NAME=zhangsan; path=/
      static bool GetCookie(const string& cookie,const string& key, string* val) {
        vector<string> vec;
        int count = Split(cookie, "; ", &vec);
        for(auto s : vec) {
          vector<string> arry_cookie;
          Split(s, "=", &arry_cookie);
          if(arry_cookie[0] == key) {
            *val = arry_cookie[1];
            return true;
          }
        }
        return true;
      }

      static void CreateSession(struct session* s,struct mg_connection* c, const string& name) {
        s->name = name;
        s->session_id = (uint64_t)(mg_time() * 1000000);
        s->login_time = mg_time();
        s->last_atime = mg_time();
        s->conn = c;
        return ;
      }

      static void DeleteSession(struct mg_connection* c) {
        auto it = _list.begin();
        for(; it != _list.end(); it++) {
          if(it->conn == c) {
            cout << "delete session: " << it->name << endl;
            _list.erase(it);
            return ;
          }
        }
        return ;
      }

      static struct session* GetSessionByConn(struct mg_connection* c) {
        auto it = _list.begin();
        for(; it != _list.end(); it++) {
          if(it->conn == c) {
            return &(*it);
          }
        }
        return NULL;
      }

      static struct session* GetSessionByName(const string& name) {
        auto it = _list.begin();
        for(; it != _list.end(); it++) {
          if(it->name == name) {
            return &(*it);
          }
        }
        return NULL;
      }

      //注册函数
      static bool reg(struct mg_connection* c, struct mg_http_message* hm) {
        int status = 200;
        string header = "Content-Type: application/json\r\n";
        //从正文中获取提交的用户信息 -- json格式的字符串
       string body;
       body.assign(hm->body.ptr, hm->body.len);
        //解析得到用户名和密码
       Json::Value user;
       Json::Reader reader;
       bool ret = reader.parse(body, user);
       if(ret == false) {
         //解析出错
         status = 400;
         mg_http_reply(c, status, header.c_str(), "{\"reason\":\"请求格式错误\"}");
         return false;
       }
        //判断用户名是否存在
       ret = _tb_user->Exists(user["name"].asString());
       if(ret == true) {
         status = 400;
         mg_http_reply(c, status, header.c_str(), "{\"reason\":\"用户名已被占用\"}");
         return false;
       }
        //不存在, 则将信息插入数据库
        ret = _tb_user->Insert(user["name"].asString(), user["passwd"].asString());
        if(ret == false) {
          status = 500;
          mg_http_reply(c, status, header.c_str(), "{\"reason\":\"数据库插入错误\"}");
          return false;
        }

        //成功返回
        mg_http_reply(c, status, header.c_str(), "{\"reason\":\"注册成功\"}");
        return true;
      }

      //登录函数
      static bool login(struct mg_connection* c, struct mg_http_message* hm) {
        int rsp_status = 200;;
        string rsp_body = "{\"reason\":\"注册成功\"}";
        string rsp_header = "Content-Type: application/json\r\n"; 
        string req_body;
        req_body.assign(hm->body.ptr, hm->body.len);

        Json::Value user;
        Json::Reader reader;
        bool ret = reader.parse(req_body, user);
        if(ret == false) {
          //解析失败
          rsp_status = 400;
          rsp_body = "{\"reason\":\"请求格式错误\"}";
          mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
          return false;
        }
        //验证用户信息
        ret = _tb_user->VerifyUser(user["name"].asString(), user["passwd"].asString());
        if(ret == false) {
          rsp_status = 403;
          rsp_body = "{\"reason\":\"用户名或密码错误\"}";
          mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
          return false;
        }

        //登录成功
        //修改用户状态
        ret = _tb_user->UpdateStatus(user["name"].asString(), ONLINE);
        if(ret == false) {
          rsp_status = 500;
          rsp_body = "{\"reason\":\"修改用户状态错误\"}";
          mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
          return false;
        }
        //创建session 
        struct session s;
        CreateSession(&s, c, user["name"].asString());
        _list.push_back(s);
        stringstream cookie;
        cookie << "Set-Cookie: SESSION_ID=" << s.session_id << ";  path=/\r\n";
        cookie << "Set-Cookie: NAME=" << s.name << "; path=/\r\n";
        rsp_header += cookie.str();
        mg_http_reply(c, rsp_status, rsp_header.c_str(), rsp_body.c_str());
        return true;
      }


      //广播消息
      static void Broadcase(const string& msg) {
        struct mg_connection* c;
        for(c = _mgr.conns; c != NULL; c = c->next) {
          if(c->is_websocket) {
            mg_ws_send(c, msg.c_str(), msg.size(), WEBSOCKET_OP_TEXT);
          }
        }
        return ;
      }

      //回调函数
      static void callback(struct mg_connection* c, int ev, void* ev_data, void* fn_data) {
        struct mg_http_message* hm = (struct mg_http_message*)ev_data;
        struct mg_ws_message* wm = (struct mg_ws_message*)ev_data;
        switch(ev) {
          case MG_EV_HTTP_MSG: //表示当前的请求是一个http请求
            if(mg_http_match_uri(hm, "/reg")) {
              //注册的提交表单数据请求
              reg(c, hm);
            }
            else if(mg_http_match_uri(hm, "/login")) {
              //登陆的提交表单数据请求
              login(c, hm);
            }
            else if(mg_http_match_uri(hm, "/websocket")) {
              //websocket握手请求
              //在建立websocket聊天通道的时候, 应该检测客户端是否已经登录
              struct mg_str* cookie_str = mg_http_get_header(hm, "Cookie"); 
              if(cookie_str == NULL) {
                //未登录用户
                string body = R"({"reason":"未登录"})";
                string header = "Cotent-Type: application/json\r\n";
                mg_http_reply(c, 403, header.c_str(), body.c_str());  
                return ;
              }

              string tmp;
              tmp.assign(cookie_str->ptr, cookie_str->len);
              string name;
              GetCookie(tmp, "NAME", &name);
              string msg = name + " 加入聊天室...大家欢迎";
              Broadcase(msg);
              mg_ws_upgrade(c, hm, NULL);
            }
            else {
              //静态页面请求
              //除了登录界面, 还应该检测Cookie, 判断是否登录成功
              //如果没有检测到session, 则应该跳转到登录页面
              if(hm->uri.ptr != "/login.html") {
                //获取一下cookie, 根据name查找session, 没有就意味着没有登录
                //但是这里存在问题: login.html依赖的其他的都静态资源(图片, css代码)
                //在没有登录成功的状态下, 就获取不到这些资源
              }
              struct mg_http_serve_opts opts = {.root_dir = "./web_root"};
              mg_http_serve_dir(c, hm, &opts);
            }
            break;
          case MG_EV_WS_MSG: //表示当前请求是一个websocket请求
            {
              string msg;
              msg.assign(wm->data.ptr, wm->data.len);
              Broadcase(msg);
            }
            break;
          case MG_EV_CLOSE:
            {
              struct session* ss = GetSessionByConn(c);
              if(ss != NULL) {
                string msg = ss->name + " 退出聊天室...";
                Broadcase(msg);
                _tb_user->UpdateStatus(ss->name, OFFLINE);
                DeleteSession(c);
              }
            }
            break;
          default:
            break;
        }
        return ;
      }

    private:
      string _addr; //监听地址信息
      static TableUser* _tb_user;
      static struct mg_mgr _mgr; //句柄
      static struct mg_connection* _lst_http; //监听连接
      static list<struct session> _list;
  };

  //初始化静态成员
  TableUser* IM::_tb_user = NULL;
  struct mg_mgr IM::_mgr;
  struct mg_connection* IM::_lst_http = NULL;
  list<struct session> IM::_list;
}

三. 前端页面模块

本模块使用一个博客页面的模板进行改写,使用了html+css+js进行编写

1.Vue.js库的使用

Vue.js类似于一个js库,使用起来非常方便,使用Vue首先要加入Vue链接

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

定义Vue对象

<script>
	var app = new Vue({
		el:'#app',
		data:{
			author:'hlmz',
			tag_list:[],
			blog_list:[],
			},
		methods:{}
	})
</script>

el中的数据要和容器id一致才能使用vue中定义的数据和方法,data就是定义的变量,methods就是定义的方法
在一个html容器标签中加入id,id内容与vue对象el元素相同,才可以在对应的HTML容器中直接访问vue对象的数据

2.jQuery ajax的使用

使用ajax,首先要加入ajax链接

    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>

使用方法如下:

$.ajax({
	url: "/blog",  //请求中的path路径
	type: "get",   //请求中的请求方法
	context: this, //设置请求成功后回调函数中的this指针--当前赋值的this是vue对象
	success: function (result) {
		this.blog_list = result; //成功后将响应的正文的json串赋值	
	}
})

3.登录页面 login.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>网页IM</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        html,
        body {
            height: 100%;
        }

        @font-face {
            font-family: 'neo';
            src: url("font/NEOTERICc.ttf");
        }

        input:focus {
            outline: none;
        }

        .form input {
            width: 300px;
            height: 30px;
            font-size: 18px;
            background: none;
            border: none;
            border-bottom: 1px solid #fff;
            color: #fff;
            margin-bottom: 20px;
        }

            .form input::placeholder {
                color: rgba(255,255,255,0.8);
                font-size: 18px;
                font-family: "neo";
            }

        .confirm {
            height: 0;
            overflow: hidden;
            transition: .25s;
        }

        .btn {
            width: 140px;
            height: 40px;
            border: 1px solid #fff;
            background: none;
            font-size: 20px;
            color: #fff;
            cursor: pointer;
            margin-top: 25px;
            font-family: "neo";
            transition: .25s;
        }

            .btn:hover {
                background: rgba(255,255,255,.25);
            }

        #login_wrap {
            width: 980px;
            min-height: 500px;
            border-radius: 10px;
            font-family: "neo";
            overflow: hidden;
            box-shadow: 0px 0px 120px rgba(0, 0, 0, 0.25);
            position: fixed;
            top: 50%;
            right: 50%;
            margin-top: -250px;
            margin-right: -490px;
        }

        #login {
            width: 50%;
            height: 100%;
            min-height: 500px;
            background: linear-gradient(45deg, #9a444e, #e06267);
            position: relative;
            float: right;
        }

            #login #status {
                width: 90px;
                height: 35px;
                margin: 40px auto;
                color: #fff;
                font-size: 30px;
                font-weight: 600;
                position: relative;
                overflow: hidden;
            }

                #login #status i {
                    font-style: normal;
                    position: absolute;
                    transition: .5s
                }

            #login span {
                text-align: center;
                position: absolute;
                left: 50%;
                margin-left: -150px;
                top: 52%;
                margin-top: -140px;
            }

                #login span a {
                    text-decoration: none;
                    color: #fff;
                    display: block;
                    margin-top: 80px;
                    font-size: 18px;
                }

        #bg {
            background: linear-gradient(45deg, #211136, #bf5853);
            height: 100%;
        }
        /*绘图*/
        #login_img {
            width: 50%;
            min-height: 500px;
            background: linear-gradient(45deg, #221334, #6c3049);
            float: left;
            position: relative;
        }

            #login_img span {
                position: absolute;
                display: block;
            }

            #login_img .circle {
                width: 200px;
                height: 200px;
                border-radius: 50%;
                background: linear-gradient(45deg, #df5555, #ef907a);
                top: 70px;
                left: 50%;
                margin-left: -100px;
                overflow: hidden;
            }

                #login_img .circle span {
                    width: 150px;
                    height: 40px;
                    border-radius: 50px;
                    position: absolute;
                }

                    #login_img .circle span:nth-child(1) {
                        top: 30px;
                        left: -38px;
                        background: #c55c59;
                    }

                    #login_img .circle span:nth-child(2) {
                        bottom: 20px;
                        right: -35px;
                        background: #934555;
                    }

            #login_img .star span {
                background: radial-gradient(#fff 10%,#fff 20%,rgba(72, 34, 64, 0));
                border-radius: 50%;
                box-shadow: 0 0 7px #fff;
            }

                #login_img .star span:nth-child(1) {
                    width: 15px;
                    height: 15px;
                    top: 50px;
                    left: 30px;
                }

                #login_img .star span:nth-child(2) {
                    width: 10px;
                    height: 10px;
                    left: 360px;
                    top: 80px;
                }

                #login_img .star span:nth-child(3) {
                    width: 5px;
                    height: 5px;
                    top: 400px;
                    left: 80px;
                }

                #login_img .star span:nth-child(4) {
                    width: 8px;
                    height: 8px;
                    top: 240px;
                    left: 60px;
                }

                #login_img .star span:nth-child(5) {
                    width: 4px;
                    height: 4px;
                    top: 20px;
                    left: 200px;
                }

                #login_img .star span:nth-child(6) {
                    width: 4px;
                    height: 4px;
                    top: 460px;
                    left: 410px;
                }

                #login_img .star span:nth-child(7) {
                    width: 6px;
                    height: 6px;
                    top: 250px;
                    left: 350px;
                }

            #login_img .fly_star span {
                width: 90px;
                height: 3px;
                background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0.67), rgba(255,255,255,0));
                background: -o-linear-gradient(left, rgba(255, 255, 255, 0.67), rgba(255,255,255,0));
                background: linear-gradient(to right, rgba(255, 255, 255, 0.67), rgba(255,255,255,0));
                transform: rotate(-45deg);
            }

                #login_img .fly_star span:nth-child(1) {
                    top: 60px;
                    left: 80px;
                }

                #login_img .fly_star span:nth-child(2) {
                    top: 210px;
                    left: 332px;
                    opacity: 0.6;
                }

            #login_img p {
                text-align: center;
                color: #af4b55;
                font-weight: 600;
                margin-top: 365px;
                font-size: 25px;
            }

                #login_img p i {
                    font-style: normal;
                    margin-right: 45px;
                }

                    #login_img p i:nth-last-child(1) {
                        margin-right: 0;
                    }
        /*提示*/
        #hint {
            width: 100%;
            line-height: 70px;
            background: linear-gradient(-90deg, #9b494d, #bf5853);
            text-align: center;
            font-size: 25px;
            color: #fff;
            box-shadow: 0 0 20px #733544;
            display: none;
            opacity: 0;
            transition: .5s;
            position: absolute;
            top: 0;
            z-index: 999;
        }
        /* 响应式 */
        @media screen and (max-width:1000px ) {
            #login_img {
                display: none;
            }

            #login_wrap {
                width: 490px;
                margin-right: -245px;
            }

            #login {
                width: 100%;
            }
        }

        @media screen and (max-width:560px ) {
            #login_wrap {
                width: 330px;
                margin-right: -165px;
            }

            #login span {
                margin-left: -125px;
            }

            .form input {
                width: 250px;
            }

            .btn {
                width: 113px;
            }
        }

        @media screen and (max-width:345px ) {
            #login_wrap {
                width: 290px;
                margin-right: -145px;
            }
        }
    </style>
</head>


<body>
    <div id="bg">
        <div id="app">
            <div id="login_wrap">
                <div id="login">
                    <!-- 登录注册切换动画 -->
                    <div id="status">
                        <i style="top: 0" v-show="show_flag=='login'">Login</i>
                        <i style="top: 0" v-show="show_flag=='sigin'">Sigin</i>
                    </div>
                    <span>
                        <form action="post">
                            <p class="form">
                                <input type="text" id="user" placeholder="用户名" v-model="username">
                            </p>
                            <p class="form">
                                <input type="password" id="passwd" placeholder="密码" v-model="password">
                            </p>
                            <p class="form" v-show="show_flag=='sigin'">
                                <input type="password" id="passwd" placeholder="确认密码" v-model="confirm_password">
                            </p>
                            <input type="button" value="登录" class="btn" style="margin-right: 20px;" v-on:click="login()">
                            <input type="button" value="注册" class="btn" id="btn" v-on:click="sigin()">
                        </form>
                    </span>
                </div>

                <div id="login_img">
                    <!-- 图片绘制框 -->
                    <span class="circle">
                        <span></span>
                        <span></span>
                    </span>
                    <span class="star">
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                    </span>
                    <span class="fly_star">
                        <span></span>
                        <span></span>
                    </span>
                    <p id="title">CLOUD</p>
                </div>
            </div>
        </div>
    </div>
</body>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                show_flag: "login", //login:登录显示 / sigin:注册显示
                username: "",
                password: "",
                confirm_password: ""
            },
            methods: {
                sigin: function () {
                    if (this.show_flag == "login") {
                        this.show_flag = "sigin";
                        this.username = "";
                        this.password = "";
                        this.confirm_password = "";
                    } else {
                        //在注册界面下, 再次点击注册就是提交注册数据
                        if (this.username.length < 6) {
                            alert("用户名长度不能低于6个字符");
                            return;
                        }
                        if (this.password.length < 6) {
                            alert("密码长度不能低于6个字符");
                            return;
                        }
                        if (this.password != this.confirm_password) {
                            alert("密码与确认密码不一致");
                            return;
                        }

                        var userinfo = {
                            name: this.username,
                            passwd: this.password
                        }
                        console.log("发起注册请求到服务器");
                        $.ajax({
                            type: "post",
                            url: "/reg",
                            data: JSON.stringify(userinfo),
                            context: this,  //这里的this是vue对象
                            success: function (data, status, xhr) {
                                alert("注册成功");
                                //跳转到登录界面
                                this.show_flag == "login"
                                this.username = "";
                                this.password = "";
                                this.confirm_password = "";
                            },
                            error: function (xhr) {
                                alert("注册失败" + xhr.status + xhr.responseText); 
                                this.username = "";
                                this.password = "";
                                this.confirm_password = "";
                            }
                        })
                    }
                },
                login: function () {
                    if (this.show_flag == "sigin") {
                        this.show_flag = "login";
                        this.username = "";
                        this.password = "";
                        this.confirm_password = "";
                    } else {
                        //提交登录数据
                        if (this.username.length < 6) {
                            alert("用户名长度不能低于6个字符");
                            return;
                        }
                        if (this.password.length < 6) {
                            alert("密码长度不能低于6个字符");
                            return;
                        }

                        var userinfo = {
                            name: this.username,
                            passwd: this.password
                        }
                        $.ajax({
                            type: "post",
                            url: "/login",
                            data: JSON.stringify(userinfo),
                            context: this,  //这里的this是vue对象
                            success: function (data, status, xhr) {
                                alert("登录成功");
                                //打开新窗口
                                window.location.href = "/index.html";
                            },
                            error: function (xhr) {
                                alert("登录失败" + xhr.status + xhr.responseText);
                            }
                        })

                    }
                }
            }
        })
    </script>
</html>

`
4.聊天页面 index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>一起来聊天吧~</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <style type="text/css">
        body {
            background-color: #789;
            margin: 0;
            padding: 0;
            font: 14px Helvetica, Arial, sans-serif;
        }

        div.content {
            width: 800px;
            height: 600px;
            margin: 2em auto;
            padding: 20px 50px;
            background-color: #fff;
            border-radius: 1em;
        }

        #messages {
            border: 2px solid #fec;
            border-radius: 0em;
            height: 30em;
            overflow: scroll;
            padding: 0.5em 1em;
        }

        #send_input {
            width: 650px;
        }

        a:link, a:visited {
            color: #69c;
            text-decoration: none;
        }

        @media (max-width: 700px) {
            body {
                background-color: #fff;
            }

            div.content {
                width: auto;
                margin: 0 auto;
                border-radius: 0;
                padding: 1em;
            }
        }

        #info {
            animation: change 10s linear 0s infinite;
            font-size: 15px;
            font-weight: 60;
        }

        #user_name {
            animation: change 5s linear 0s infinite;
            font-size: 12px;
            font-weight: 50;
        }

        @keyframes change {
            0% {
                color: #333;
            }

            25% {
                color: #ff0;
            }

            50% {
                color: #f60;
            }

            75% {
                color: #cf0;
            }

            100% {
                color: #f00;
            }
        }
    </style>

</head>
<body>
    <div id="app">
        <div class="content">
            <h1>欢迎来到我的IM聊天系统</h1>

            <p>
            </p>

            <div id="messages">
            </div>

            <p>
                <input type="text" id="send_input" v-model="send_msg" v-on:keyup.enter="send()"/>
                <button id="send_button" v-on:click="send()"> 发送 </button>
                <button id="quit_button" v-on:click="quit()"> 退出 </button>
            </p>
        </div>
    </div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    var app = new Vue({
        el: '#app',
        data: {
            websock: null,
            send_msg: "",
            real_msg: ""
        },
        methods: {
            init: function () {
                var ws_url = "ws://" + window.location.host + "/websocket";
                this.websock = new WebSocket(ws_url);

                this.websock.onopen = this.wsonopen;
                this.websock.onmessage = this.wsonmessage;
                this.websock.onerrer = this.wsonerror;
                this.websock.onclose = this.wsonclose;
            },
            wsonopen: function () {
                //websocket连接建立成功后的回调函数
                alert("聊天通道建立成功");
            },
            wsonerrer: function () {
                //通信发生错误时触发的回调函数
                alert("通信错误");
                this.init();
            },
            wsonclose: function () {
                //连接关闭时触发的回调函数
                alert("连接关闭");
            },
            wsonmessage: function (e) {
                //收到消息后触发的回调函数, e.data就是收到的数据
                var com_div = document.createElement("div");
                com_div.innerHTML = e.data;
                var html_div = document.getElementById("messages");
                html_div.appendChild(com_div);
            },
            get_cookie_name: function () {
                var cookie = document.cookie;
                var cookie_arry = cookie.split("; ");
                for (var i = 0; i < cookie_arry.length; i++) {
                    var arry = cookie_arry[i].split("=");
                    if (arry[0] == "NAME") {
                        return arry[1];
                    }
                }
            },
            send: function () {
                //发送数据
                if (this.send_msg.length == 0) {
                    alert("消息不能为空");
                    return;
                }
                var username = this.get_cookie_name();
                this.real_msg = "<p>" + username + ": " + this.send_msg + "</p>";
                this.websock.send(this.real_msg);
                this.send_msg = "";
            },
            quit: function () {
                //退出
                this.websock.close(); //关闭websocket连接
                window.location.href = "/login.html";
            }

        }
    });
    app.init();
</script>


</html>


至此,网页即时通讯项目就完成了。感谢。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值