1、完善注册/登录
1. 涉及的数据库表单:user_info
2. 引用MySQL线程池,Redis线程池
3. 完善注册功能
4. 完善登录功能
2.1 涉及的数据库表单:user_info
重新创建数据库
#创建数据库
DROP DATABASE IF EXISTS `0voice_tuchuang`;CREATE DATABASE `0voice_tuchuang`;
#使用数据库
use `0voice_tuchuang`;
先清楚user表(如果存在) ,然后重新创建表单
# 清除表单
DROP TABLE IF EXISTS `user_info`;
# 重新创建表单 id自增,nick_name user_name创建唯一索引
CREATE TABLE `user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户序号,自动递增,主键',`user_name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名称',`nick_name` varchar(32) CHARACTER SET utf8mb4 NOT NULL DEFAULT '' COMMENT '用户昵称',`password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',`phone` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号码',`email` varchar(64) DEFAULT '' COMMENT '邮箱',`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '时间',PRIMARY KEY (`id`),UNIQUE KEY `uq_nick_name` (`nick_name`),UNIQUE KEY `uq_user_name` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
这样就新增了用户表单。
2.2 引入MySQL线程池,Redis线程池
redis mysql目录为MySQL redis操作接口。
MySQL连接池,目前设置了master,slave两个连接池,master用于有写的操作,slave用于读的操作,通 过指定连接池获取连接示例代码:
CDBManager *db_manager = CDBManager::getInstance(); //这里用了单例模式
//从slave数据库读取数据(目前实际上主从都是同一个,后续可以使用主从方式测试对比性能)
CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave"); //指定连接池
AUTO_REL_DBCONN(db_manager, db_conn); //退出当前函数时自动把连接归还连接池
Redis连接池,目前设置了token,ranking_list两个连接池,token用于token的读写,ranking_list用于下载 排行榜的操作,通过指定连接池获取连接示例代码:
CacheManager *cache_manager = CacheManager::getInstance();
CacheConn *cache_conn = cache_manager->GetCacheConn("token");//指定连接池
AUTO_REL_CACHECONN(cache_manager, cache_conn); //退出当前函数时自动把连接归还连接池
2.3 实现完整的注册功能
1. 部分头文件以及通用函数 放到新建的api_common.h api_common.cc
2. 调用MySQL操作接口
3. 重点是api_register.cc的代码完善
引用MySQL操作接口
#include "db_pool.h" // 相关的MySQL操作接口的头文件
/*
* 先根据用户名查询数据库该用户是否存在,不存在才插入
*/
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(); //这里用了单例模式//从slave数据库读取数据(目前实际上主从都是同一个,后续可以使用主从方式测试对比性能)CDBConn *db_conn = db_manager->GetDBConn("tuchuang_slave");AUTO_REL_DBCONN(db_manager, db_conn);// 先查看用户是否存在string str_sql = FormatString("select id, user_name from user_info where user_name='%s'", user_name.c_str());CResultSet *result_set = db_conn->ExecuteQuery(str_sql.c_str());if (result_set && result_set->Next()) { // 检测是否存在用户记录// 存在在返回LOG_WARN << "id: " << result_set->GetInt("id") << ", user_name: " << 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(?,?,?,?,?,?)";LOG_INFO << "执行: " << str_sql;// 必须在释放连接前delete// CPrepareStatement对象,否则有可能多个线程操作mysql对象,会crash// 预处理方式写入数据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) { //提交正常返回 trueret = 0;user_id = db_conn->GetInsertId();LOG_INFO << "insert user_id: " << user_id;} else {LOG_ERROR << "insert user_info failed. " << str_sql;ret = 1;}}delete stmt;}return ret;
}
2.4 实现完整的登录功能
主要是完善:
- verifyUserPassword(string &user_name, string &pwd) 函数,通过用户名查询数据库对应的密码,对 比请求登录的密码是否一致
- setToken(string &user_name, string &token) 如果一致则 生成一个新token,并以token为key,用户 名作为value,把token存储到redis里。
2.4.1 校验用户名密码verifyUserPassword
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 = FormatString("select password from user_info where user_name='%s'", user_name.c_str());CResultSet *result_set = db_conn->ExecuteQuery(strSql.c_str());if (result_set && result_set->Next()) { //如果存在则读取密码// 存在在返回string password = result_set->GetString("password");LOG_INFO << "mysql-pwd: " << password << ", user-pwd: " << pwd;if (password == pwd) //对比密码是否一致ret = 0; //对比成功elseret = -1; //对比失败} else { // 说明用户不存在ret = -1;}delete result_set;return ret;
}
2.4.2 使用redis实现token验证机制
Redis是一个开源的内存数据存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据结构,例如字 符串、哈希、列表、集合和有序集合。Redis通过将数据存储在内存中,提供了非常高效的读写速度。
在Web应用程序中,使用Redis存储Token有以下几个优点:
- 快速:Redis的数据存储在内存中,读写速度非常快,适用于高并发的场景。
- 可扩展性:Redis支持分布式部署,可以轻松实现横向扩展。
- 多种数据结构:Redis支持多种数据结构,可以根据需求选择适合的数据结构存储Token。
- 过期时间管理:Redis提供了设置过期时间的功能,可以轻松实现Token的自动过期和续期。
因为我们使用token作为key,所以需要做到唯一,所以也就需要通过算法生成唯一的key,我们使用libuuid 这个库。
Ubuntu安装:
sudo apt-get install uuid-dev
使用方式:
头文件引用 #include
CMakeLists.txt要:
包含路径:INCLUDE_DIRECTORIES(/usr/include)
包含库文件目录:LINK_DIRECTORIES(/usr/lib)
包含库的引用 uuid:TARGET_LINK_LIBRARIES(tc_http_srv3 .... uuid)
调用uuid的接口封装generateUUID:
std::string generateUUID() {uuid_t uuid;uuid_generate_time_safe(uuid); //调用uuid的接口char uuidStr[40] = {0};uuid_unparse(uuid, uuidStr); //调用uuid的接口return std::string(uuidStr);
}
在redis设置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 = generateUUID(); // 生成唯一的tokenif (cache_conn) {//token - 用户名, 86400有效时间为24小时 有效期可以自己修改cache_conn->SetEx(token, 86400, user_name); // redis做超时} else {ret = -1;}return ret;
}
校验token和user_name
//验证登陆token,成功返回0,失败-1
int VerifyToken(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);if (cache_conn) {string temp_user_name = cache_conn->Get(token); //校验token和用户名的关系if (temp_user_name == user_name) {ret = 0;} else {ret = -1;}} else {ret = -1;}return ret;
}
参考连接:https://github.com/0voice