网页即时通讯项目
作者:互联网
设计思路
项目名称:网页即时通讯
项目描述:实现通信系统,使用户能够通过浏览器进行用户的登录,进行即时聊天
开发环境: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服务器
搭建流程如下:
- 定义操作句柄 struct mg_mgr mgr;
- 初始化操作句柄 void mg_mgr_init();
- 创建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) - 开始轮询监听(等待所有连接,有数据到来进行处理)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.代码实现业务逻辑模块
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>
至此,网页即时通讯项目就完成了。感谢。
标签:mg,网页,name,项目,即时通讯,user,mysql,return,login 来源: https://blog.csdn.net/weixin_40218502/article/details/118464921