项目名称:网页IM即时通信
项目描述:实现通信系统,使用户能够通过浏览器进行用户的注册登录,进行即时聊天的系统
开发环境:Centos7.5-vim/g++/gdb/makefile/git
概要设计:采用mvc框架
model数据管理模块,使用mysql数据库进行用户信息管理,设计用户信息表tb_user
管理
用户数据,封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
controller业务处理模块,搭建http服务器实现网络通信,设计通信接口进行系统的业务逻辑处理,静态网页,注册,登录,协议切换,聊天消息
view前端界面模块,使用html+css+js设计前端展示界面,注册登录界面和聊天界面
详细设计
数据管理模块,使用mysql数据库进行用户信息管理,设计用户信息表tb_user
管理
用户数据,封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
设计用户信息表tb_user
管理
用户数据
id,用户名,密码,状态
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 comment '密码',
status varchar(8) not null comment '状态信息-offline/online'
);
mysql -uroot<db.sql
封装数据库表访问类TableUser实现用户信息的增删改查,用户验证以及用户是否存在
用户信息的插入,
bool Insert(const string &name, const string &passwd)
用户信息的删除,
bool Delete(const string &name)
用户状态的修改,
bool UpdateStatus(const string &name, const string &status)
用户密码的修改,
bool UpdatePasswd(const string &name, const string &passwd)
查询单个用户信息,通过用户名和json接收单个用户信息
bool SelectOne(const string &name, Json::Value *user)
查询所有用户信息,通过json接收所有用户信息
bool SelectAll(Json::Value *users)
用户信息的验证,用于登录
bool VerifyUser(const string &name, const string &passwd)
用户是否存在,用于注册时判断用户名是否被占用
bool Exists(const string &name)
im.hpp
#include <cstdio>
#include <iostream>
#include <sstream> //stringstream保存cookie
#include <mutex>//互斥锁
#include <list>//链表保存session
#include <mysql/mysql.h>//mysql
#include <jsoncpp/json/json.h>//json
#include "mongoose.h"
using namespace std;
//设计im命名空间,防止函数和变量命名冲突,防止命名污染
namespace im
{
//定义宏用于连接mysql服务器的参数设置
#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:
//构造函数
//完成数据库操作的初始化
//使用参数列表进行初始化,mysql句柄默认为NULL
TableUser() : _mysql(NULL)
{
//初始化mysql句柄
_mysql = mysql_init(NULL);
if (_mysql == NULL)
{
printf("init mysql instance failed!\n");
exit(-1);//初始化失败退出
}
//连接mysql服务器
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)
{
//对密码进行MD5加密
#define INSERT_USER "insert tb_user value(null,'%s',MD5('%s'),'%s');"
char tmp_sql[4096] = {0};
//sprintf按照指定格式组织一个字符串放到tmp_sql缓冲区中
sprintf(tmp_sql, INSERT_USER, name.c_str(), passwd.c_str(), OFFLINE);//默认为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_STATU "update tb_user set status='%s' where name='%s';"
char tmp_sql[4096] = {0};
sprintf(tmp_sql, UPDATE_USER_STATU, status.c_str(), name.c_str());
return QuerySql(tmp_sql);
}
//用户密码的修改
bool UpdatePasswd(const string &name, const string &passwd)
{
//对密码进行MD5加密
#define UPDATE_USER_PASS "update tb_user set passwd=MD5('%s') where name='%s';"
char tmp_sql[4096] = {0};
sprintf(tmp_sql, UPDATE_USER_PASS, passwd.c_str(), name.c_str());
return QuerySql(tmp_sql);
}
//查询单个用户信息,通过用户名和json接收单个用户信息
bool SelectOne(const string &name, Json::Value *user)
{
#define SELECT_USER_ONE "select id,passwd,status from tb_user where name='%s';"
char tmp_sql[4096] = {0};
sprintf(tmp_sql, SELECT_USER_ONE, 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)//!=1,因为获取的是单个用户,防止出现0的情况
{
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;
}
//查询所有用户信息,通过json接收所有用户信息
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 all 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)
{
//对密码进行MD5加密,MD5是sql中的一个聚合函数
#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 failed!\n");
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:
//封装语句的执行,代码复用,设置为private
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;
};
}
im.cpp
#include "im.hpp"
void sql_test()
{
//实例化一个user对象
im::TableUser user;
//测试用户信息的插入
user.Insert("lisi","111111");
user.Insert("zhangsan","111111");
//测试修改密码
user.UpdatePasswd("lisi","111112");//1
//测试验证用户信息
cout<<user.VerifyUser("zhangsan","111111")<<endl;//1
//测试验证用户是否存在
cout<<user.Exists("wangwu")<<endl;//have no user;0;
cout<<user.Exists("lisi")<<endl;//1
//测试查询单个用户信息
Json::Value val;
user.SelectOne("zhangsan",&val);
Json::StyledWriter writer;
cout<<writer.write(val)<<endl;
//测试查询所有用户信息
Json::Value val;
user.SelectAll(&val);
Json::StyledWriter writer;
cout<<writer.write(val)<<endl;
//测试用户信息的删除
user.Delete("zhangsan");
}
int main(int argc, char *argv[])
{
sql_test();
return 0;
}
Makefile
im:im.cpp im.hpp
g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -lpthread -lmysqlclient -ljsoncpp
#-L/usr/lib64/mysql
#指定mysql库文件的路径,这个路径不是默认路径,所以需要显式指定
make
./im
业务处理模块,搭建http服务器实现网络通信,设计通信接口,
对各个请求进行对应的业务处理,静态网页+注册+登录+协议切换+聊天消息
1.网络通信模块,实现网络通信
http协议是一种简单的请求响应协议,客户端发送请求,服务器针对请求进行响应,在实现网页IM聊天中,一个用户发送消息后,其他用户都需要能够获取到这条消息,如果使用http协议实现比较麻烦,因为每个用户都需要每隔几秒钟从服务器上拉一下当前的聊天信息,因此网页IM系统使用websocket协议实现聊天信息的推送
http协议只能轮询不断发送请求获取响应
websocket协议允许服务端主动向客户端推送数据,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输
websocket通过HTTP/1.1协议的101状态码进行握手
使用mongoose库搭建http服务器,mongoose库支持http协议升级
2.业务处理模块,
静态网页+注册+登录+协议切换+聊天消息
静态网页,注册,登录功能通过HTTP协议实现传输
聊天消息通过websocket协议进行传输
接口设计
1.静态网页,正文返回页面数据
2.注册功能,检测用户名是否已经被占用,如果没有占用则添加数据库,然后进行响应
3.登录功能,数据库验证登录信息,验证成功则创建会话,然后进行响应
登录成功之后,客户端请求聊天页面进行聊天,但是聊天就必须知道是谁在发消息,因此需要进行客户端的状态维护,使用cookie+session进行状态管理
用户登录成功之后,服务端为客户端创建session会话,记录用户名以及对应状态信息,生成session_id,通过cookie将session_id和name返回给客户端
浏览器客户端下次请求的时候,就会自动将这些cookie信息发送给服务端,服务端收到后就可以分辨消息是谁发送的
4.websocket握手请求,确认升级websocket协议
5.使用websocket协议进行聊天消息的传输和推送
网页IM功能流程
1.客户端浏览器请求注册页面(静态页面请求)---返回页面文件
2.填写用户注册信息,点击注册提交(提交用户名和密码到服务器)---检测用户名是否已经被占用,如果没有占用则添加数据库,然后进行响应
3.客户端浏览器请求登录页面(静态页面请求)--返回页面文件
4.界面中填写登录信息,点击登录提交(提交用户名和密码到服务器)---数据库验证登录信息,验证成功则创建会话,然后进行响应
5.登录成功,客户端浏览器请求聊天界面(静态网页请求)---返回页面文件
6.建立websocket聊天通道(http协议切换,websocket握手请求)---确认升级websocket协议
7.使用websocket协议进行聊天通信发送消息---将聊天消息广播给其他已经建立了websocket的用户
im.hpp
struct session
{
string name;
string status;
uint64_t session_id;
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;
//创建http监听连接
//操作句柄 监听地址 回调函数 传入参数
_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:
//分割字符串
//Cookie:SESSION_ID-12312535; NAME=zhangsan; path=/
static int Split(const string &str,const string &sep,vector<string>*list){
//string::substr(),从pos位置开始截取指定长度字符串
//string::find(),从pos位置开始找_s分隔符,返回所在位置
int count=0;
size_t pos=0,idx=0;//idx起始找寻位置
while(1){
pos=str.find(sep,idx);//从str字符串的idx位置开始找sep分隔符
if(pos==string::npos){
break;
}
list->push_back(str.substr(idx,pos-idx));
idx=pos+sep.size();
count++;
}
if(idx<str.size()){
list->push_back(str.substr(idx));
count++;
}
return count;
}
//获取cookie,通过分割字符串获取
static bool GetCookie(const string &cookie,const string &key,string *val){
vector<string>list;
int count=Split(cookie,"; ",&list);
for(auto s:list){
vector<string>arry_cookie;
Split(s,"=",&arry_cookie);
if(arry_cookie[0]==key){
*val=arry_cookie[1];
return true;
}
}
return false;;
}
//创建session
static void CreateSession(struct session *s,struct mg_connection*c,const string &name){
s->name=name;
s->session_id=(uint64_t)(mg_time()*1000000);//session_id唯一
s->login_time=mg_time();
s->last_atime=mg_time();
s->conn=c;
return;
}
//删除session
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;
}
//获取session-byconn
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;
}
}
//获取session-byname
static struct session *GetSessionByName(const string &name){
auto it=_list.end();
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;
}
//用户登录成功之后创建session,以及设置客户端cookie,并且设置用户处于在线状态
//#
//设置用户处于在线状态
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 Broadcast(const string &msg)
{
struct mg_connection *c;
//遍历链表
for (c = _mgr.conns; c != NULL; c = c->next)
{
//如果c是一个websocket连接,则调用mg_ws_send接口发送数据
if (c->is_websocket){
mg_ws_send(c, msg.c_str(), msg.size(), WEBSOCKET_OP_TEXT);
//WEBSOCKET_OP_TEXT,选项标志位
}
}
return;
}
//回调函数,设置为static函数,没有this指针,因为回调函数只有4个参数
//回调函数:当前连接,触发事件,处理完毕的数据,传入参数
static void callback(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
//http
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
//websocket
struct mg_ws_message *wm = (struct mg_ws_message *)ev_data;
switch (ev)
{
case MG_EV_HTTP_MSG:
//注册的提交表单数据请求
if (mg_http_match_uri(hm, "/reg"))
{
//对注册封装一个函数reg
reg(c, hm);
}
//登录的提交表单数据请求
else if (mg_http_match_uri(hm, "/login"))
{
//对登录封装一个函数login
login(c, hm);
}
//websocket的握手请求
//在建立websocket聊天通道的时候,应该检测这个客户端是否已经登录
//获取到请求头部中的cookie,通过cookie中的session或者name查找session
else if (mg_http_match_uri(hm, "/websocket"))
{
//GetCookie(const string &cookie,const string &key,string *val)
struct mg_str* cookie_str=mg_http_get_header(hm,"Cookie");//hm-头部信息
if(cookie_str==NULL){
//现在处于未登录状态,未登录用户
//c++11,R"()",括号中的数据是一个原始字符串,没有特殊含义
string body=R"({"reson":"未登录"})";
string header="Content-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+" 加入聊天室...welcome!";
Broadcast(msg);
mg_ws_upgrade(c, hm, NULL);
}
//静态页面请求
//除了登录界面,过来的时候,都应该检测一下Cookie,判断是否登录成功了
//如果没有检测到session,则应该跳转到登录页面
else
{
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:
{
string msg;
msg.assign(wm->data.ptr, wm->data.len); //wm是已经解析好的websocket消息
Broadcast(msg);
}
break;
//连接断开
//当一个连接断开的时候,删除当前用户session,设置用户为下线状态
case MG_EV_CLOSE:
//加个{}保证局部变量不会超出作用域
{
struct session *ss=GetSessionByConn(c);
if(ss!=NULL){
string msg=ss->name+"退出聊天室...";
Broadcast(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;//监听连接
//使用链表保存session-需要频繁插入删除-不用map和vector-使用list快速插入删除
static list<struct session> _list;//cookie需要频繁插入删除,所以用链表
};
//在类外进行static成员的初始化
TableUser* IM::_tb_user = NULL;
struct mg_mgr IM::_mgr;
struct mg_connection* IM::_lst_http = NULL;
list<struct session> IM::_list;
im.cpp
#include "im.hpp"
int main(int argc, char *argv[])
{
// sql_test();
im::IM im_server;//实例化一个对象
im_server.Init();//初始化
im_server.Run();//运行
return 0;
}
Makefile
im:im.cpp im.hpp mongoose.c
g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -lpthread -lmysqlclient -ljsoncpp
make
./im
前端界面模块,使用html+css+js设计前端展示界面,即注册登录界面和聊天界面
ajax,向服务器发送请求,得到响应后渲染页面,相当于
在浏览器后台创建了一个http客户端,用来和进行服务器数据传输
login.html
<body>
<div id="bg">
<!-- //vue对象,略过背景 -->
<div id="app">
<div id="login_wrap">
<div id="login"><!-- 登录注册切换动画 -->
<div id="status">
<!-- //设置login/sigin显示其一 -->
<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>
<!-- //sigin显示 -->
<p class="form" v-show="show_flag=='sigin'">
<!-- //数据双向绑定 -->
<input type="password" id="confirm-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>
<!-- //vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 链接jquery ajax -->
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
//创建vue对象
var app = new Vue({
el: '#app',
data: {
show_flag:"login",//login-登录显示,sigin-注册显示
username:"",
password:"",
confirm_password:""
},
methods:{
//设置sigin注册点击事件
sigin:function(){
//登录状态下点击注册显示注册界面
if(this.show_flag=="login"){
this.show_flag="sigin";
//切换页面清空输入框
this.username="";
this.password="";
this.confirm_password="";
//注册状态下,点击注册就是数据提交
}else{
//数据双向绑定
if(this.username.lenght<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),//js对象转为json格式字符串
context:this,//这里的this就是vue对象
//success回调函数
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登录点击事件
login:function(){
//切换
if(this.show_flag=="sigin"){
this.show_flag="login";
//切换页面清空输入框
this.username="";
this.password="";
this.confirm_password="";
//登录数据提交
}else{
//数据双向绑定
//username是vue对象中的数据,函数内部认为是访问局部变量了,所以加上this
if(this.username.lenght<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>
index.html
<body>
<div id="app">
<div class="content">
<h1>welcome to syc's IM system</h1>
<p>
</p>
<div id="messages">
</div>
<p>
<!-- //v-model双向绑定send_msg -->
<input type="text" id="send_input" v-model="send_msg" />
<!-- //设置点击事件 -->
<button id="send_button" v-on:click="send()"> 发送 </button>
<button id="quit_button" v-on:click="quit()"> 退出 </button>
</p>
</div>
</div>
</body>
<!-- //vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- //ajax -->
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script>
//创建一个vue对象
var app = new Vue({
//vue对象选择器
el: '#app',
data: {
send_msg:"",//发送的消息
websock:null,
real_msg:""
},
methods:{
//初始化
init:function(){
var ws_url="ws://"+window.location.host+"/websocket";//主机地址+请求资源路径
this.websock=new WebSocket(ws_url);//new一个websocket对象,创建一个websocket连接
//设置回调函数
this.websock.onopen=this.wsonopen;//连接建立成功
this.websock.onmessage=this.wsonmessage;//收到消息
this.websock.onerror=this.wsonerror;//出错
this.websock.onclose=this.wsonclose;//连接关闭
},
//websocket连接建立成功后触发的回调函数
wsonopen:function(){
alert("聊天通道建立成功")
},
//通信发生错误时触发的回调函数
wsonerror:function(){
alert("通信错误")
this.init();//出错重新初始化
},
//连接关闭时触发的回调函数
wsonclose:function(){
alert("连接关闭")
},
//收到消息后触发的回调函数,e.data就是收到的数据
wsonmessage:function(e){
var com_div=document.createElement("div");
com_div.innerHTML=e.data;
var html_div=document.getElementById("messages");//获取message控件
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];
}
}
return "匿名";
},
//send点击事件
send:function(){
console.log("call 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>";//将消息组织为一个html标签
this.websock.send(this.real_msg);//发送
this.send_msg="";
},
//quit点击事件
quit:function(){
this.websock.close();//关闭websocket连接
window.location.href="/login.html"//退出返回登录界面
}
}
});
app.init();//vue对象创建成功后,调用init接口,创建websocket连接
</script>