C/C++精品项目之图床共享云存储(4):注册,登录,token,排序文件

devtools/2024/11/20 3:21:58/

目录

一:CHttpConn 类

二:注册

三:登录

四:获取文件


C/C++精品项目之图床共享云存储(1):基础组件-CSDN博客

C/C++精品项目之图床共享云存储(2):MySql连接池_mysql 连接池c++ ping-CSDN博客

C/C++精品项目之图床共享云存储(3):网络缓冲区类和main-CSDN博客

一:CHttpConn 类

        我们上一期讲了一点点这个 CHttpConn 类,这一期继续讲解这个类。首先我们回顾一下,我们客户端与服务端创建好连接之后,分配了一个socket,然后我们在连接上之后,触发回调函数,创建了一个CHttpConn对象,这个对象和这个socket进行绑定,然后这个对象进行初始化,再一次传入新的回调函数,当我们触发不同的读写事件,那么进入不同的读写回调函数。

void httpconn_callback(void *callback_data, uint8_t msg, uint32_t handle,uint32_t uParam, void *pParam) {NOTUSED_ARG(uParam);NOTUSED_ARG(pParam);// convert void* to uint32_t, oopsuint32_t conn_handle = *((uint32_t *)(&callback_data)); //获取连接标识  ,也就是获得跟socket绑定的http的handleCHttpConn *pConn = FindHttpConnByHandle(conn_handle);   //根据连接标识找到对应的http对象if (!pConn) {   //如果没有则返回// LogWarn("no find conn_handle: {}", conn_handle);return;}pConn->AddRef();        //添加引用计数switch (msg) {case NETLIB_MSG_READ:   //可读事件pConn->OnRead();break;case NETLIB_MSG_WRITE:  //可写事件pConn->OnWrite();break;case NETLIB_MSG_CLOSE:pConn->OnClose();   //关闭事件break;default:LogError("!!!httpconn_callback error msg:{}", msg); //fix me,可以考虑如果这里触发,直接关闭该连接// pConn->Close();break;}pConn->ReleaseRef(); //释放引用计数,如果数据发送完毕,对应的http 连接在这个位置为析构对象
}

        现在我们查看读事件中的代码:我们触发读事件,无非注册,登录,发送请求。我们在这个函数中,肯定是先将发送来的数据读出来,这个时候就用到我们网络缓冲区类了,读完数据后,要对数据进行解析。我们分别得到url和body的数据,我们根据传来的url进行区分不同的请求。而body是具体的一些参数。

//这里的onread函数:就是我们客户端向服务端发送数据,首先就是接收数据到读缓冲区,然后我们将数据拿出来,然后就是进行http解析,通过解析到不同的url \
我们能跳转到不同的处理函数中去。
void CHttpConn::OnRead() // CHttpConn业务层面的OnRead
{LogInfo("conn_handle_ = {}, socket_handle_ = {}", conn_handle_, socket_handle_);// 1. 把能读取的数据都读取出来for (;;) {uint32_t free_buf_len = in_buf_.GetAllocSize() - in_buf_.GetWriteOffset();if (free_buf_len < READ_BUF_SIZE + 1)   //这里多预留一个字节的目的是加上结束符时不会越界,因为我们可能会发送多条消息 in_buf_.Extend(READ_BUF_SIZE + 1);      //为了区分这些消息,我们每次接受完都要加上一个结束符,这样我们就可以通过结束符来区分不同的消息//读取socket数据int ret = netlib_recv(socket_handle_,in_buf_.GetBuffer() + in_buf_.GetWriteOffset(),               //写道buf的写后面READ_BUF_SIZE);if (ret <= 0)break; //没有数据可以读取了in_buf_.IncWriteOffset(ret); // 更新下一个接收数据的位置}// 2.通过http模块解析http请求的数据char *in_buf = (char *)in_buf_.GetBuffer();uint32_t buf_len = in_buf_.GetWriteOffset();in_buf[buf_len] = '\0'; // 末尾加上结束符 方便分析结束位置和打印,这里之所以不越界是因为有预留in_buf_.Extend(READ_BUF_SIZE + 1)// 如果buf_len 过长可能是受到攻击,则断开连接 ,目前我们接受的所有数据长度不得大于2Kif (buf_len > 2048) {LogError("get too much data: {}", in_buf);Close();return;}LogInfo("buf_len: {}, in_buf: {}", buf_len, in_buf); //将请求的数据都打印出来,方便调试分析http请求// 解析http数据http_parser_.ParseHttpContent(in_buf, buf_len); // 1. 从socket接口读取数据;2.然后把数据放到buffer in_buf; 3.http解析if (http_parser_.IsReadAll()) {string url = http_parser_.GetUrl();string content = http_parser_.GetBodyContent();LogInfo("url: {}", url);                     // for debug// 根据url处理不同的业务 if (strncmp(url.c_str(), "/api/reg", 8) == 0) { // 注册  url 路由。 根据根据url快速找到对应的处理函数, 能不能使用map,hash_HandleRegisterRequest(url, content);} else if (strncmp(url.c_str(), "/api/login", 10) == 0) { // 登录_HandleLoginRequest(url, content);} else if (strncmp(url.c_str(), "/api/myfiles", 10) == 0) { //获取我的文件数量_HandleMyfilesRequest(url, content);}  else {LogError("url unknown, url= {}", url);Close();}}
}

二:注册

        对于注册来说,无非就是从数据库上查询和插入嘛。首先我们通过解析body中的所有参数,也就是我们注册所填的参数:姓名,昵称,密码,等等。我们通过一个http解析的函数将这些数据解析出来,然后进入到注册的具体环节。

int ApiRegisterUser(string &post_data, string &resp_json) {int ret = 0;string user_name;string nick_name;string pwd;string phone;string email;LogInfo("post_data: {}", post_data);// 判断数据是否为空if (post_data.empty()) {LogError("decodeRegisterJson failed");// 封装注册结果encodeRegisterJson(1, resp_json);return -1;}// 解析jsonif (decodeRegisterJson(post_data, user_name, nick_name, pwd, phone, email) < 0) {LogError("decodeRegisterJson failed");// 封装注册结果encodeRegisterJson(1, resp_json);return -1;}// 注册账号ret = registerUser(user_name, nick_name, pwd, phone, email);// 封装注册结果ret = encodeRegisterJson(ret, resp_json);return 0;
}

        注册的具体环节也就是和数据库打交道,我们首先通过我们的MySql连接池获得一个空闲的连接,然后给数据库发送查询命令,看看是否存在了已经。已存在返回具体的值,不存在则进行插入环节。我们准备好具体的SQL命令,通过添加其中所需要的参数,然后执行。这样就注册成功了。

int registerUser(string &user_name, string &nick_name, string &pwd,string &phone, string &email) {int ret = 0;uint32_t user_id;CDBManager *db_manager = CDBManager::getInstance();                 //通过数据库的manger,我们可以拿到相关数据库的连接池。CDBConn *db_conn = db_manager->GetDBConn("tuchuang_master");AUTO_REL_DBCONN(db_manager, db_conn);       //栈上构建一个对象 退出的时候自动把连接规划连接池// 先查看用户是否存在string str_sql = FormatString("select * from user_info where user_name='%s'", user_name.c_str());LogInfo("执行: {}", str_sql);CResultSet *result_set = db_conn->ExecuteQuery(str_sql.c_str());if (result_set && result_set->Next()) { // 检测是否存在用户记录// 存在在返回LogWarn("id: {}, user_name: {}  已经存在", result_set->GetInt("id"), result_set->GetString("user_name"));delete result_set;ret = 2;} else { // 如果不存在则注册time_t now;char create_time[TIME_STRING_LEN];//获取当前时间now = time(NULL);strftime(create_time, TIME_STRING_LEN - 1, "%Y-%m-%d %H:%M:%S",localtime(&now));str_sql = "insert into user_info ""(`user_name`,`nick_name`,`password`,`phone`,`email`,`create_""time`) values(?,?,?,?,?,?)";LogInfo("执行: {}", str_sql);// mysql操作 如果不熟悉可以参考:https://www.yuque.com/linuxer/linux_senior/rcz4xl?singleDoc# 《mysql api c客户端》CPrepareStatement *stmt = new CPrepareStatement();if (stmt->Init(db_conn->GetMysql(), str_sql)) {uint32_t index = 0;string c_time = create_time;stmt->SetParam(index++, user_name);stmt->SetParam(index++, nick_name);stmt->SetParam(index++, pwd);stmt->SetParam(index++, phone);stmt->SetParam(index++, email);stmt->SetParam(index++, c_time);bool bRet = stmt->ExecuteUpdate();if (bRet) {ret = 0;user_id = db_conn->GetInsertId();LogInfo("insert user_id: {}", user_id); //用户id是自增id} else {LogError("insert user_info failed. {}", str_sql);ret = 1;}}delete stmt;}return ret;
}

 注册成功我们也需要返回一些数据给客户端,首先就是通信的头部,然后就是将代表注册成功的信息填入我们要发送的数据中,然后发送。

// 账号注册处理
void CHttpConn::_HandleRegisterRequest(string &url, string &post_data) {string resp_json;int ret = ApiRegisterUser(post_data, resp_json);char *http_body = new char[HTTP_RESPONSE_HTML_MAX];uint32_t ulen = resp_json.length();snprintf(http_body, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, ulen,resp_json.c_str()); 	ret = Send((void *)http_body, strlen(http_body));delete[] http_body;LogInfo("Send remain = {}", ret); 
}

三:登录

        其实登录和注册也差不多,就是从数据库中查询是否有这个用户,先解析姓名和密码,然后通过一个函数验证这个姓名和密码是否存在于数据库中。

int ApiUserLogin(string &post_data, string &resp_json) {string user_name;string pwd;string token;// 判断数据是否为空if (post_data.empty()) {return -1;}// 解析jsonif (decodeLoginJson(post_data, user_name, pwd) < 0) {LogError("decodeRegisterJson failed");encodeLoginJson(1, token, resp_json);return -1;}// 验证账号和密码是否匹配if (verifyUserPassword(user_name, pwd) < 0) {LogError("verifyUserPassword failed");encodeLoginJson(1, token, resp_json);return -1;}// 生成token并存储在redisif (setToken(user_name, token) < 0) {LogError("setToken failed");encodeLoginJson(1, token, resp_json);return -1;} else {encodeLoginJson(0, token, resp_json);return 0;}
}

这个函数就是进行检查的,先连接数据库,执行相应的命令,返回具体的结果。

int verifyUserPassword(string &user_name, string &pwd) {int ret = 0;CDBManager *db_manager = CDBManager::getInstance();CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");AUTO_REL_DBCONN(db_manager, db_conn);// 先查看用户是否存在string strSql;strSql = FormatString("select password from user_info where user_name='%s'", user_name.c_str());CResultSet *result_set = db_conn->ExecuteQuery(strSql.c_str());uint32_t nowtime = time(NULL);if (result_set && result_set->Next()) {// 存在在返回string password = result_set->GetString("password");LogInfo("mysql-pwd: {}, user-pwd: {}", password, pwd);if (result_set->GetString("password") == pwd)ret = 0;elseret = -1;} else { // 如果不存在则注册ret = -1;}delete result_set;return ret;
}

 当我们验证登陆成功之后,我们开始创建一个token,token是什么呢?
Token 的中文有人翻译成 “令牌”,意思就是,你拿着这个令牌,才能过一些关卡。
        Token 是一个用户自定义的任意字符串。在成功提交了开发者自定义的这个字符串之后,Token 的值会保存到服务器后台。只有服务器和客户端前端知道这个字符串,于是 Token 就成了这两者之间的密钥,它可以让服务器确认请求是来自客户端还是恶意的第三方。这里所说的 Token,本质上就是 http session。使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1) 客户端使用用户名跟密码请求登录
2) 服务端收到请求,去验证用户名与密码
3) 验证成功后,服务端生成一个 Token,这个 Token 可以存储在内存、磁盘、或者数据库里,再把这个 Token 发送给客户端
4) 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage

5) 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6) 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

int setToken(string &user_name, string &token) {int ret = 0;CacheManager *cache_manager = CacheManager::getInstance();CacheConn *cache_conn = cache_manager->GetCacheConn("token");AUTO_REL_CACHECONN(cache_manager, cache_conn);token = RandomString(32); // 随机32个字母if (cache_conn) {//用户名:token, 86400有效时间为24小时cache_conn->SetEx(user_name, 86400, token); // redis做超时} else {ret = -1;}return ret;
}

 在最后我们不是已经生成好token了吗,我们要将这个token返回给客户端,让客户端前端和服务器之间有令牌。

void CHttpConn::_HandleLoginRequest(string &url, string &post_data)
{string resp_json;int ret = ApiUserLogin( post_data, resp_json);char *http_body = new char[HTTP_RESPONSE_HTML_MAX];uint32_t ulen = resp_json.length();snprintf(http_body, HTTP_RESPONSE_HTML_MAX, HTTP_RESPONSE_HTML, ulen,resp_json.c_str()); 	ret = Send((void *)http_body, strlen(http_body));delete[] http_body;LogInfo("Send remain = {}", ret); 
}

四:获取文件

        获取文件列表,这个操作是在登陆成功之后做的事情,那么让我们看看其中token的作用,以及我们怎么获取文件列表的。首先我们看到的就是一些变量,下面就是一个解析cmd的函数,这里的cmd是什么意思呢?比如我们要访问 127.0.0.1:80/api/myfiles&cmd=normal ,我们写入这个url,那么就代表我们要对文件进行排序操作。而count这个变量是在具体的json里面的。如果为0,那就是返回全部。

        接下来除了一开始的解析 json 参数外,最重要的也就是验证token,因为这里的操作已经是在登录以后得了。

//获取文件列表,首先就是要解析json,看看是要获取文件个数还是文件信息,无论是什么,首先就是要获取token,这个是为了确保使我们当前的连接 \
如果是数量,那么我们就直接通过传入来的用户名进行查找,当然查找之前要验证token,所以我们要连接mysql和redis,当token通过就开始查找数量
//如果是对文件进行排序的话,和它差不多,首先获取具体命令,验证token,连接mysql,拿取数据。
int ApiMyfiles(string &url, string &post_data, string &resp_json) {// 解析url有没有命令// count 获取用户文件个数// display 获取用户文件信息,展示到前端char cmd[20];string user_name;string token;           //这个tocken是在服务之间进行传递的,用来验证用户的合法性,它存在于json中。int ret = 0;int start = 0; //文件起点int count = 0; //文件个数//解析命令 解析url获取自定义参数,这里的自定义参数是指按什么顺序返回,比如从大到小QueryParseKeyValue(url.c_str(), "cmd", cmd, NULL);LogInfo("url: {}, cmd: {} ",url, cmd);if (strcmp(cmd, "count") == 0) {// 解析jsonif (decodeCountJson(post_data, user_name, token) < 0) {encodeCountJson(1, 0, resp_json);LogError("decodeCountJson failed");return -1;}//验证登陆token,成功返回0,失败-1ret = VerifyToken(user_name, token); // util_cgi.hif (ret == 0) {// 获取文件数量if (handleUserFilesCount(user_name, count) < 0) { //获取用户文件个数LogError("handleUserFilesCount failed");encodeCountJson(1, 0, resp_json);} else {LogInfo("handleUserFilesCount ok, count: {}", count);encodeCountJson(0, count, resp_json);}} else {LogError("VerifyToken failed");encodeCountJson(1, 0, resp_json);}return 0;} else {if ((strcmp(cmd, "normal") != 0) && (strcmp(cmd, "pvasc") != 0) &&(strcmp(cmd, "pvdesc") != 0)) {LogError("unknow cmd: {}", cmd);encodeCountJson(1, 0, resp_json);}//获取用户文件信息 127.0.0.1:80/api/myfiles&cmd=normal//按下载量升序 127.0.0.1:80/api/myfiles?cmd=pvasc//按下载量降序127.0.0.1:80/api/myfiles?cmd=pvdescret = decodeFileslistJson(post_data, user_name, token, start,count); //通过json包获取信息LogInfo("user_name: {}, token:{}, start: {}, count: {}", user_name,token, start, count);if (ret == 0) {//验证登陆token,成功返回0,失败-1ret = VerifyToken(user_name, token); // util_cgi.hif (ret == 0) {string str_cmd = cmd;if (getUserFileList(str_cmd, user_name, start, count,resp_json) < 0) { //获取用户文件列表LogError("getUserFileList failed");encodeCountJson(1, 0, resp_json);}} else {LogError("VerifyToken failed");encodeCountJson(1, 0, resp_json);}} else {LogError("decodeFileslistJson failed");encodeCountJson(1, 0, resp_json);}}return 0;
}

我们这里的验证token是需要连接redis数据库,为什么用户数据和文件数据存放在mysql,而token需要放在redis,因为redis是内存数据库,速度很快,我们每一次的内部操作都需要验证token,所以存放在redis中。我们通过传入来的用户信息,来获得具体的token。

int VerifyToken(string &user_name, string &token) {int ret = 0;CacheManager *cache_manager = CacheManager::getInstance();// increase message countCacheConn *cache_conn = cache_manager->GetCacheConn("token");AUTO_REL_CACHECONN(cache_manager, cache_conn);if (cache_conn) {string tmp_token = cache_conn->Get(user_name);if (tmp_token == token) {ret = 0;} else {ret = -1;}} else {ret = -1;}return ret;
}

接下来肯定就是具体的查找逻辑了,当然在这操作之前,肯定要获取一个空闲的mysql连接了。

int DBGetUserFilesCountByUsername(CDBConn *db_conn, string user_name,int &count) {count = 0;int ret = 0;// 先查看用户是否存在string str_sql;str_sql = FormatString("select count(*) from user_file_list where user='%s'", user_name.c_str());LogInfo("执行: {}", str_sql);CResultSet *result_set = db_conn->ExecuteQuery(str_sql.c_str());if (result_set && result_set->Next()) {// 存在在返回count = result_set->GetInt("count(*)");LogInfo("count: {}", count);ret = 0;delete result_set;} else if (!result_set) { // 操作失败LogError("{} 操作失败", str_sql);LogError("{} 操作失败", str_sql);ret = -1;} else {// 没有记录则初始化记录数量为0ret = 0;LogInfo("没有记录: count: {}", count);}return ret;
}

而这里就是查找全部的文件信息了,一样是线连接mysql数据库,然后设置sql命令,返回结果之后,我们将结果返回给客户端。

int getUserFileList(string &cmd, string &user_name, int &start, int &count,string &str_json) {LogInfo("getUserFileList into");int ret = 0;int total = 0;string str_sql;CDBManager *db_manager = CDBManager::getInstance();CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");AUTO_REL_DBCONN(db_manager, db_conn);CacheManager *cache_manager = CacheManager::getInstance();CacheConn *cache_conn = cache_manager->GetCacheConn("token");AUTO_REL_CACHECONN(cache_manager, cache_conn);ret = getUserFilesCount(db_conn, cache_conn, user_name,total); // 总共的文件数量if (ret < 0) {LogError("getUserFilesCount failed");return -1;} else {if (total == 0) {Json::Value root;root["code"] = 0;root["count"] = 0;root["total"] = 0;Json::FastWriter writer;str_json = writer.write(root);LogWarn("getUserFilesCount = 0");return 0;}}//多表指定行范围查询if (cmd == "normal") //获取用户文件信息{str_sql = FormatString("select user_file_list.*, file_info.url, file_info.size,  file_info.type from file_info, user_file_list where user = '%s' \and file_info.md5 = user_file_list.md5 limit %d, %d",user_name.c_str(), start, count);} else {LogError("unknown cmd: {}", cmd);return -1;}LogInfo("执行: {}", str_sql);CResultSet *result_set = db_conn->ExecuteQuery(str_sql.c_str());if (result_set) {// 遍历所有的内容// 获取大小int file_index = 0;Json::Value root, files;root["code"] = 0;while (result_set->Next()) {Json::Value file;file["user"] = result_set->GetString("user");file["md5"] = result_set->GetString("md5");file["create_time"] = result_set->GetString("create_time");file["file_name"] = result_set->GetString("file_name");file["share_status"] = result_set->GetInt("shared_status");file["pv"] = result_set->GetInt("pv");file["url"] = result_set->GetString("url");file["size"] = result_set->GetInt("size");file["type"] = result_set->GetString("type");files[file_index] = file;file_index++;}root["files"] = files;root["count"] = file_index;root["total"] = total;Json::FastWriter writer;str_json = writer.write(root);delete result_set;return 0;} else {LogError("{} 操作失败", str_sql);return -1;}
}

        本篇主要讲解,注册,登录,token,获取文件的一些具体操作,其中比较重要的就是token,而其他的业务操作全都是和数据库打交道,将查询的结果返回到客户端这里。0voice · GitHub


http://www.ppmy.cn/devtools/135367.html

相关文章

百度世界大会2024,展现科技改变生活的力量

百度世界2024大会以“应用来了”为主题&#xff0c;于2024年11月12日在上海世博中心隆重举行。大会吸引了众多科技爱好者、行业专家和媒体人士参与&#xff0c;共同见证AI技术的最新进展和未来趋势。 会上&#xff0c;李彦宏首先宣布了检索增强的文生图技术——文心iRAG的正式亮…

20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡

20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡 2024/11/14 15:44 缘起&#xff1a;使用EVB2的方案&#xff0c;RJ45加进去怎么也不通。 实在没有办法&#xff0c;只能将荣品的SDK&#xff1a;rk-android13-20240713.tgz 解压缩&#xff0c;编译之…

蓝队基础4 -- 安全运营与监控

声明&#xff1a; 本文的学习内容来源于B站up主“泷羽sec”视频“蓝队基础之网络七层杀伤链”的公开分享&#xff0c;所有内容仅限于网络安全技术的交流学习&#xff0c;不涉及任何侵犯版权或其他侵权意图。如有任何侵权问题&#xff0c;请联系本人&#xff0c;我将立即删除相关…

【泛型 Plus】Kotlin 的加强版类型推断:@BuilderInference

视频先行 下面是视频内容的脚本文案原稿分享。 小剧场 面试官&#xff1a;「既然协程和泛型你都熟悉&#xff0c;flow() 函数是怎么实现类型推断的有了解过吗&#xff1f;」 求职者&#xff1a;「嗯……」 求职者&#xff1a;「嗯……在Kotlin协程中&#xff0c;flow 是一种构建…

基于Java Springboot论坛系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

wangeditor富文本编辑器以文本的形式展示公式

最终展示的效果 1.首先将要传给后端的富文本值进行转化 //假设workContent是富文本写入的值this.workContent this.escapeHTML(this.workContent)//通过escapeHTML方法转化传给后端 methods:{escapeHTML(str) {return str.replace(/&/g, &amp;) // 将 & 替换为…

移动零

移动零 1、题目描述2、解答思路 1、题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 2、解答思路 已知数组后端若干元素为0&…

React中使用echarts写出3d旋转扇形图

效果 技术 React TypeScript Less Echarts 代码块 import * as echarts from "echarts"; import React, { useEffect, useRef } from "react"; import "echarts-gl"; import "./index.less";const LeftEcharts () > {const ch…