当前位置: 首页 > news >正文

My图床项目

引言: 

在海量文件存储中尤其是小文件我们通常会用上fastdfs对数据进行高效存储,在现实生产中fastdfs通常用于图片,文档,音频等中小文件。

 一.项目中用到的基础组件(Base)

1.网络库(muduo)

我们就以muduo网络库为例子讲解IO多路复用和reactor网络模型

1.1 IO多路复用 

我们可以借用陈硕大神的原话来理解IO的同步和异步↓↓↓

在处理 IO 的时候,阻塞和非阻塞都是同步 IO。只有使用了特殊的 API 才是异步
IO。

 

 一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪”和"数据读写"。

数据就绪阶段分为---->阻塞和非阻塞。

数据读写阶段分为---->同步和异步。

 我们重点介绍epoll

epoll相比于其他IO模型来讲好的太多了,在muduo网络库重的底层IO多路复用我们采用的就是epoll来处理IO事件的。

epoll 适合大规模高并发场景,是 Linux 下高性能网络编程首选。

 1.2 Reactor模型

Reactor模型是被广泛使用在生产环境的中的一个网络模型

 重要组件Event事件、Reactor反应堆、Demultiplex事件分发器、Evanthandler事件处理器

 muduo库的Multiple Reactors模型如下:

 1.3 muduo网络库的核心代码模块
①Channel
fd、events、revents、callbacks 两种channel listenfd-acceptorChannel connfd
connectionChannel

Poller和EPollPoller - Demultiplex

std::unordered_map<int, Channel*> channels 

③EventLoop - Reactor 

ChannelList activeChannels_;
std::unique_ptr poller_;
int wakeupFd ; -> loop
std::unique_ptr wakeupChannel ;

 ④Thread和EventLoopThread 

⑤EventLoopThreadPool 

getNextLoop() : 通过轮询算法获取下一个subloop baseLoop
一个thread对应一个loop => one loop per thread

⑥Socket 

⑦Acceptor 

主要封装了listenfd相关的操作 socket bind listen baseLoop 

⑧Buffer 

缓冲区 应用写数据 -> 缓冲区 -> Tcp发送缓冲区 -> send
prependable readeridx writeridx

⑨TcpConnection 

一个连接成功的客户端对应一个TcpConnection Socket Channel 各种回调 发送和接收缓冲

⑩TcpServer 

Acceptor EventLoopThreadPool
ConnectionMap connections_; 
 1.4 muduo网络库的核心思想是one loop peer thread

什么是one loop peer thread呢?是指示一个loop对应一个线程

 每个eventloop中都对应着一个线程,我们可以看到有一个poller和很多channel,并管理着这些channel。

在main主线程中会生成一个执行一个eventloop 对应着mainreactor这个loop负责管理主线程中对客户端的连接,当有连接来的时候会分发给其他loop 对应着subreactor

 1.5我们介绍一些核心的代码块

①根据poller通知的channel发生的具体事件,由channel负责调具体的回调操作

//根据poller通知的channel发生的具体事件,由channel负责调用具体的回调操作  
void Channel::handleEventWithGuard(Timestamp receiveTime)
{LOG_INFO("channel handleEvents revents:%d\n",revents_);if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){if (closecallback_){closecallback_();}}if (revents_ & EPOLLERR){if (errorcallback_){errorcallback_();}}if (revents_ & (EPOLLIN | EPOLLPRI)){if (readcallback_){readcallback_(receiveTime);}}if (revents_ & EPOLLOUT){if (writecallback_){writecallback_();}}
}

处理挂起事件(EPOLLHUP)

if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN))

如果发生了挂起事件(如对端关闭连接),并且没有可读事件,则调用 closecallback_() 关闭回调。

处理错误事件(EPOLLERR)

if (revents_ & EPOLLERR)         

如果发生了错误事件,则调用 errorcallback_() 错误回调。

处理读事件(EPOLLIN | EPOLLPRI)

if (revents_ & (EPOLLIN | EPOLLPRI)) 

如果有可读事件(普通或优先级数据),则调用 readcallback_() 读回调,并传递接收时间。

处理写事件(EPOLLOUT)        

if (revents_ & EPOLLOUT)

如果有可写事件,则调用 writecallback_() 写回调。

 该函数根据 epoll 返回的事件类型,安全地调用相应的回调函数,完成事件驱动的分发和处理。这样可以让上层业务只需关注回调逻辑,而不用关心底层事件分发细节。

②evetloop:loop开启事件循环代码模块

// 开启事件循环
void EventLoop::loop()
{looping_ = true;quit_ = false;LOG_INFO("eventloop %p start looping\n", this);while (!quit_){activeChannels_.clear();// 监听两类fd 一种是client的fd 一种wakeupfdpollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel *channel : activeChannels_){// Poller监听哪些channel发生事件了 然后上报给EventLoop 通知channel处理相应的事件channel->handleEvent(pollReturnTime_);}// 执行当前eventloop事件循环 需要处理的回调操作/*IO线程 mainloop accept fd《=channel subloopmainloop 事先注册一个回调cb (需要subloop来执行)  wakeup subloop后,执行下面的方法,执行之前mainloop注册的cb操作*/doPendingFunctors();}LOG_INFO("EventLoop%p stop looping \n", this);looping_ = false;
}

 开启loop-->会执行poller->poll接口开启事件监听和对channel的管理,不断的处理活跃的channel和活跃的事件并不断的执行回调函数.

③one loop peer thread 的具体体现

EventLoop *EventLoopThread::startloop()
{thread_.start(); // 启动底层新线程EventLoop *loop = nullptr;{std::unique_lock<std::mutex> lock(mutex_);while (loop_ == nullptr){cond_.wait(lock);}loop=loop_;}return loop;
}
// 下面这个方法是在单独的新线程里面运行的
void EventLoopThread::threadFunc()
{// “One loop per thread”(每个线程一个事件循环)EventLoop loop; // 创建了一个独立的eventloop 和 上面的线程是一一对应的 one loop per thread模型if (callback_){callback_(&loop);}{std::unique_lock<std::mutex> lock(mutex_);loop_ = &loop;cond_.notify_one();}loop.loop();//EventLoop loop => Poller.pollstd::unique_lock<std::mutex> lock(mutex_);loop_=nullptr;
}

 main主线程会主动执行start 从而 启动一个maineventloop和mainreactor进行对连接的监听,我们可以启动任意个分线程从而对应着起任意个subloop。

1.6 muduo网络库的具体案例
#include <iostream>
#include <mymuduo/TcpServer.h>
#include <ostream>
#include <string>
#include <mymuduo/logger.h>
#include <functional>class EchoServer
{
public:EchoServer(EventLoop *loop, const InetAddress &addr, const std::string &name): server_(loop, addr, name),loop_(loop){// 注册回调函数server_.setConnectionCallback(std::bind(&EchoServer::onConnection, this, std::placeholders::_1));server_.setMessageCallback(std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));// 设置合适的loop线程数量  loopthreadserver_.setThreadNum(3);}void start(){server_.start();}private:// 连接建立或者断开的回调void onConnection(const TcpConnectionPtr &conn){if (conn->connected()){LOG_INFO("conn up:%s", conn->peerAddress().toIpPort().c_str());}else{LOG_INFO("conn down:%s", conn->peerAddress().toIpPort().c_str());}}// 可读写事件回调void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time){std::string msg = buf->retrieveAllAsString();std::cout<<msg<<std::endl;conn->send(msg);// conn->shutdown(); // 写端 EPOLLHUP => closecallback}EventLoop *loop_;TcpServer server_;
};int main()
{EventLoop loop;InetAddress addr(8080, "192.168.217.148");EchoServer echoServer(&loop, addr, "myserver-01"); // accpertor non-blocking listenfd create bindechoServer.start();                                // listen  loopthread  listenfd => accpetorchannle => mainlooploop.loop();                                       // 启动mainloop的底层pollerreturn 0;
}
 2.Thread_pool线程池

.h

#pragma once
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <atomic>class ThreadPool {
public:ThreadPool(size_t thread_count);~ThreadPool();template<class F, class... Args>auto enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>;private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;std::atomic<bool> stop;void worker();
};template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>
{// 推导任务的返回类型using return_type = typename std::result_of<F(Args...)>::type;// 用packaged_task包装任务和参数,支持返回值auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));// 获取future,用于异步获取任务结果std::future<return_type> res = task->get_future();{// 加锁保护任务队列std::lock_guard<std::mutex> lock(queue_mutex);// 如果线程池已停止,抛出异常if (stop) throw std::runtime_error("enqueue on stopped ThreadPool");// 将任务包装为无参void函数,放入任务队列tasks.emplace([task]() { (*task)(); });}// 通知一个工作线程有新任务condition.notify_one();// 返回future给调用者return res;
}

.c

#include "Thread_pool.h"ThreadPool::ThreadPool(size_t thread_count) : stop(false) {for (size_t i = 0; i < thread_count; ++i) {workers.emplace_back(&ThreadPool::worker, this);}
}void ThreadPool::worker() {while (!stop) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queue_mutex);condition.wait(lock, [this] { return stop || !tasks.empty(); });if (stop && tasks.empty()) return;task = std::move(tasks.front());tasks.pop();}task();}
}ThreadPool::~ThreadPool() {stop = true;condition.notify_all();for (auto& t : workers) {if (t.joinable()) t.join();}
}// =================== 示例用法 ===================#include <iostream>int add(int a, int b) {return a + b;
}void print_task(int i) {std::cout << "Task " << i << " is running in thread "<< std::this_thread::get_id() << std::endl;
}int main() {ThreadPool pool(4);auto fut = pool.enqueue(add, 3, 5);std::cout << "add(3,5) = " << fut.get() << std::endl;for (int i = 0; i < 8; ++i) {pool.enqueue(std::bind(print_task, i));}std::this_thread::sleep_for(std::chrono::seconds(1));return 0;
}
+-------------------+
|    ThreadPool     |
+-------------------+
| - workers         |----> [std::thread, std::thread, ...]
| - tasks           |----> +--------------------------+
| - queue_mutex     |      | 任务队列 (std::queue)    |
| - condition       |      +--------------------------+
| - stop            |
+-------------------+|| 1. 主线程调用 enqueue() 提交任务v
+--------------------------+
|   任务队列 (tasks)       |
+--------------------------+|| 2. 工作线程等待任务v
+--------------------------+
|   工作线程 (worker)      |
|--------------------------|
| while (!stop)            |
|   等待任务               |
|   取出任务               |
|   执行任务               |
+--------------------------+|| 3. 析构时 stop=true, notify_all 唤醒所有线程安全退出v
+--------------------------+
|   线程安全销毁           |
+--------------------------+

 线程池主要是基于生产者消费者模型进行设计的

生产者源源不断往任务队列tasks里面添加任务task, 消费线程也就是工作线程不断从任务队列里面取数据进行任务处理,没有任务的时候工作线程进入休眠等待,当任务队列有任务的时候通过condition_wait唤醒工作线程。

3.mysql连接池 

MySQL 连接池是一种复用数据库连接资源、提升高并发访问效率的技术。其核心思想是:预先创建一定数量的数据库连接,放入池中,业务线程需要时从池中获取,用完后归还,而不是每次都新建和销毁连接

我们主要介绍CDBConn这个类来介绍对mysql进行建表,插入,更新,查询等操作 

// 初始化数据库连接
int CDBConn::Init()
{m_mysql = mysql_init(NULL); // 初始化mysql连接if (!m_mysql){LOG_ERROR << "mysql_init failed";return 1;}bool reconnect = true;mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect);mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4"); // 设置字符集// 连接数据库if (!mysql_real_connect(m_mysql, m_pDBPool->GetDBServerIP(), m_pDBPool->GetUsername(), m_pDBPool->GetPasswrod(),m_pDBPool->GetDBName(), m_pDBPool->GetDBServerPort(), NULL, 0)){LOG_ERROR << "mysql_real_connect failed: " << mysql_error(m_mysql);return 2;}return 0;
}// 获取连接池名称
const char *CDBConn::GetPoolName()
{return m_pDBPool->GetPoolName();
}// 执行建表、插入等操作
bool CDBConn::ExecuteCreate(const char *sql_query)
{mysql_ping(m_mysql);if (mysql_real_query(m_mysql, sql_query, strlen(sql_query))){LOG_ERROR << "mysql_real_query failed: " <<  mysql_error(m_mysql) <<  ", sql: start transaction";return false;}return true;
}// 执行更新操作
bool CDBConn::ExecutePassQuery(const char *sql_query)
{mysql_ping(m_mysql);if (mysql_real_query(m_mysql, sql_query, strlen(sql_query))){LOG_ERROR << "mysql_real_query failed: " <<  mysql_error(m_mysql) << ", sql: start transaction";return false;}return true;
}// 执行删除操作
bool CDBConn::ExecuteDrop(const char *sql_query)
{mysql_ping(m_mysql);if (mysql_real_query(m_mysql, sql_query, strlen(sql_query))){LOG_ERROR << "mysql_real_query failed: " << mysql_error(m_mysql) << ", sql: start transaction";return false;}return true;
}// 执行查询操作,返回结果集
CResultSet *CDBConn::ExecuteQuery(const char *sql_query)
{mysql_ping(m_mysql);row_num = 0;if (mysql_real_query(m_mysql, sql_query, strlen(sql_query))){LOG_ERROR <<  "mysql_real_query failed: " << mysql_error(m_mysql) << ", sql: %s\n" << sql_query;return NULL;}MYSQL_RES *res = mysql_store_result(m_mysql); // 获取结果集if (!res){LOG_ERROR <<  "mysql_store_result failed: " << mysql_error(m_mysql);return NULL;}row_num = mysql_num_rows(res);LOG_INFO << "row_num: " << row_num;CResultSet *result_set = new CResultSet(res);return result_set;
}

 MYSQL *m_mysql;            // MySQL连接指针

我们通过对m_mysql连接指针进行初始化操作 我们拿到一个对mysql的连接。

mysql_options 接口

用于在建立 MySQL 连接前设置连接相关的参数和行为(如自动重连、字符集等),要在mysql_real_connect接口执行之前执行

mysql_real_connect 接口

建立与 MySQL 服务器的实际连接,后续所有数据库操作都基于该连接句柄进行。

mysql_ping 接口

 是 MySQL C API 的一个函数,用于检测当前数据库连接是否可用,如果连接已断开会尝试自动重连。

mysql_real_query 接口

是 MySQL C API 中用于执行 SQL 语句的函数,可以发送任意 SQL(如 SELECT、INSERT、UPDATE、DELETE 等)到服务器执行 

 二.注册,登录API接口的实现

注册和登录功能是我们http服务中的最入门的接口,我们在实现这两个接口的前提,我们需要了解json协议

1.json数据交换格式
sequenceDiagramparticipant Client as 客户端 (例如: 浏览器, 移动应用)participant Server as 服务器 (例如: Web API)Client->>Client: 1. 准备数据 (应用内部对象/结构)Client->>Client: 2. 将数据序列化为 JSON 字符串Note over Client,Server: HTTP 请求 (例如: POST, GET)Client->>Server: 3. 发送 HTTP 请求 (请求体包含 JSON 数据, Content-Type: application/json)Server->>Server: 4. 接收 HTTP 请求Server->>Server: 5. 解析请求体中的 JSON 字符串为服务器端对象/结构Server->>Server: 6. 处理业务逻辑 (例如: 查询数据库, 执行操作)Server->>Server: 7. 准备响应数据 (服务器端对象/结构)Server->>Server: 8. 将响应数据序列化为 JSON 字符串Note over Client,Server: HTTP 响应Server->>Client: 9. 发送 HTTP 响应 (响应体包含 JSON 数据, Content-Type: application/json)Client->>Client: 10. 接收 HTTP 响应Client->>Client: 11. 解析响应体中的 JSON 字符串为客户端对象/结构Client->>Client: 12. 使用数据 (例如: 更新 UI, 存储数据)
  1. 客户端准备数据:应用程序在客户端生成需要发送的数据。
  2. 序列化为 JSON:客户端将这些数据转换(序列化)成 JSON 格式的字符串。
  3. 发送 HTTP 请求:客户端通过 HTTP 协议向服务器发送请求,JSON 字符串通常放在请求体 (request body) 中。
  4. 服务器接收请求:服务器接收到 HTTP 请求。
  5. 解析 JSON:服务器从请求体中提取 JSON 字符串,并将其转换(反序列化/解析)为服务器端编程语言可以处理的数据结构(如对象、字典等)。
  6. 处理业务逻辑:服务器根据解析后的数据执行相应的业务操作。
  7. 服务器准备响应数据:服务器生成要返回给客户端的数据。
  8. 序列化为 JSON:服务器将响应数据序列化为 JSON 格式的字符串。
  9. 发送 HTTP 响应:服务器通过 HTTP 协议将包含 JSON 数据的响应发送回客户端。
  10. 客户端接收响应:客户端接收到服务器的 HTTP 响应。
  11. 解析 JSON:客户端解析响应体中的 JSON 字符串,将其转换为客户端编程语言可以处理的数据结构。
  12. 客户端使用数据:客户端使用这些解析后的数据进行后续操作,如更新用户界面。
 2.注册

①注册的入口函数

// 注册接口主流程
int ApiRegisterUser(string &url, string &post_data, string &str_json)
{UNUSED(url);int ret = 0;string user_name;string nick_name;string pwd;string phone;string email;// 判断数据是否为空if (post_data.empty()){return -1;}// 解析jsonif (decodeRegisterJson(post_data, user_name, nick_name, pwd, phone, email) < 0){LOG_ERROR << "decodeRegisterJson failed";encodeRegisterJson(1, str_json);return -1;}// 注册账号ret = registerUser(user_name, nick_name, pwd, phone, email);encodeRegisterJson(ret, str_json);return ret;
}

②解析json数据 

int decodeRegisterJson(const std::string &str_json, string &user_name, string &nick_name, string &pwd, string &phone, string &email)
{bool res;Json::Value root;Json::Reader jsonReader;res = jsonReader.parse(str_json, root);if (!res){LOG_ERROR << "parse reg json failed ";return -1;}// 用户名if (root["userName"].isNull()){LOG_ERROR << "userName null\n";return -1;}user_name = root["userName"].asString();// 昵称if (root["nickName"].isNull()){LOG_ERROR << "nickName null\n";return -1;}nick_name = root["nickName"].asString();// 密码if (root["firstPwd"].isNull()){LOG_ERROR << "firstPwd null\n";return -1;}pwd = root["firstPwd"].asString();// 电话(非必须)if (root["phone"].isNull()){LOG_WARN << "phone null\n";}else{phone = root["phone"].asString();}// 邮箱(非必须)if (root["email"].isNull()){LOG_WARN << "email null\n";}else{email = root["email"].asString();}return 0;
}

③封装注册的用户信息 

int encodeRegisterJson(int ret, string &str_json)
{Json::Value root;root["code"] = ret;Json::FastWriter writer;str_json = writer.write(root);return 0;
}

 ④把用户信息注册到数据库中

// 注册用户到数据库
int registerUser(string &user_name, string &nick_name, string &pwd, string &phone, string &email)
{int ret = 0;uint32_t user_id;CDBManager *pDBManager = CDBManager::getInstance();CDBConn *pDBConn = pDBManager->GetDBConn("tuchuang_slave");AUTO_REAL_DBCONN(pDBManager, pDBConn);// 先查看用户是否存在string strSql;strSql = formatString2("select * from user_info where user_name='%s'", user_name.c_str());CResultSet *pResultSet = pDBConn->ExecuteQuery(strSql.c_str());if (pResultSet && pResultSet->Next()){ // 检测是否存在用户记录// 存在则返回LOG_WARN << "id: " << pResultSet->GetInt("id") << ", user_name: " << pResultSet->GetString("user_name") << " 已经存在";delete pResultSet;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));strSql = "insert into user_info (`user_name`,`nick_name`,`password`,`phone`,`email`,`create_time`) values(?,?,?,?,?,?)";LOG_INFO << "执行: " << strSql;// 必须在释放连接前delete CPrepareStatement对象,否则有可能多个线程操作mysql对象,会crashCPrepareStatement *stmt = new CPrepareStatement();if (stmt->Init(pDBConn->GetMysql(), strSql)){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 = pDBConn->GetInsertId();LOG_INFO << "insert user " << user_id;}else{LOG_ERROR << "insert user_info failed. " << strSql;ret = 1;}}delete stmt;}return ret;
}

总结:ApiRegisterUser接口   拿到http请求传来的数据 通过decodeRegisterJson接口进行json解析,从而得到用户的信息调用encodeRegisterJson接口 封装注册用户的信息,然后调用registerUser接口把用户信息注册到数据库中

 3.登录

①用户登录的入口函数

 * @brief  用户登录主流程** @param url       请求url* @param post_data 请求体(json字符串)* @param str_json  返回的json字符串* @returns         成功: 0,失败:-1*/
int ApiUserLogin(string &url, string &post_data, string &str_json)
{UNUSED(url);string user_name;string pwd;string token;// 判断数据是否为空if (post_data.empty()){return -1;}// 解析jsonif (decodeLoginJson(post_data, user_name, pwd) < 0){LOG_ERROR << "decodeRegisterJson failed";encodeLoginJson(1, token, str_json);return -1;}// 验证账号和密码是否匹配if (verifyUserPassword(user_name, pwd) < 0){LOG_ERROR << "verifyUserPassword failed";encodeLoginJson(1, token, str_json);return -1;}// 生成tokenif (setToken(user_name, token) < 0){LOG_ERROR << "setToken failed";encodeLoginJson(1, token, str_json);return -1;}// 返回登录成功结果encodeLoginJson(0, token, str_json);return 0;
}

 ②解析json数据

// 解析登录信息,将json字符串解析为用户名和密码
int decodeLoginJson(const std::string &str_json, string &user_name, string &pwd)
{bool res;Json::Value root;Json::Reader jsonReader;res = jsonReader.parse(str_json, root);if (!res){LOG_ERROR << "parse reg json failed ";return -1;}// 用户名if (root["user"].isNull()){LOG_ERROR << "user null\n";return -1;}user_name = root["user"].asString();// 密码if (root["pwd"].isNull()){LOG_ERROR << "pwd null\n";return -1;}pwd = root["pwd"].asString();return 0;
}

 ③封装登录的用户信息


// 封装登录结果的json,将登录结果和token写入json字符串
int encodeLoginJson(int ret, string &token, string &str_json)
{Json::Value root;root["code"] = ret;if (ret == 0){root["token"] = token; // 正常返回的时候才写入token}Json::FastWriter writer;str_json = writer.write(root);return 0;
}

 ④在数据库中验证用户是否存在

int verifyUserPassword(string &user_name, string &pwd)
{int ret = 0;CDBManager *pDBManager = CDBManager::getInstance();CDBConn *pDBConn = pDBManager->GetDBConn("tuchuang_slave");AUTO_REAL_DBCONN(pDBManager, pDBConn); // 自动归还数据库连接// 查询用户密码string strSql = formatString1("select password from user_info where user_name='%s'", user_name.c_str());CResultSet *pResultSet = pDBConn->ExecuteQuery(strSql.c_str());uint32_t nowtime = time(NULL);if (pResultSet && pResultSet->Next()){// 用户存在,校验密码string password = pResultSet->GetString("password");LOG_INFO << "mysql-pwd: " << password << ", user-pwd: " << pwd;if (pResultSet->GetString("password") == pwd)ret = 0;elseret = -1;}else{ // 用户不存在ret = -1;}delete pResultSet;return ret;
}

 ⑤生成token

int setToken(string &user_name, string &token)
{int ret = 0;CacheManager *pCacheManager = CacheManager::getInstance();// 获取Redis连接CacheConn *pCacheConn = pCacheManager->GetCacheConn("token");// 通过RAII自动归还连接AUTO_REAL_CACHECONN(pCacheManager, pCacheConn);token = RandomString(32); // 生成32位随机tokenif (pCacheConn){// 用户名:token, 86400秒有效(24小时)pCacheConn->setex(user_name, 86400, token);}else{ret = -1;}return ret;
}

总结: ApiUserLogin接口   拿到http请求传来的数据 通过decodeLoginJson接口进行json解析,从而得到用户的信息调用encodeLoginJson接口 封装登录用户的信息,然后调用verifyUserPassword接口把用户信息与数据库中的数据进行校验中 setToken接口 给用户返回一个token

token 的作用

客户端在后续的请求中(例如,请求受保护的资源、执行敏感操作等),需要在请求头或请求体中携带这个 token。

服务器收到请求后,会验证 token 的有效性(例如,检查 token 是否存在、是否过期、是否被篡改等)。如果 token 有效,服务器就认为该用户已经登录,并允许其访问

相关文章:

  • STM32 控制12VRGB灯带颜色亮度调节,TFTLCD显示
  • 2025.06.06【Ribo-seq】|RiboCode:基于Ribo-seq数据的全基因组翻译ORF检测工具详解与实战流程
  • 中国首套1公里高分辨率大气湿度指数数据集(2003~2020)
  • 电池电压采集之低功耗设计
  • 【vLLM 学习】Cpu Offload Lmcache
  • 2025年6月6日 奇门遁甲与股市
  • 西北某省级联通公司:3D动环模块如何实现机房“一屏统管”?
  • 学习数字孪生,为你的职业发展开辟新赛道
  • 【原神 × 二叉树】角色天赋树、任务分支和圣遗物强化路径的算法秘密!
  • 【OpenGL学习】(五)自定义着色器类
  • 【PmHub面试篇】性能监控与分布式追踪利器Skywalking面试专题分析
  • BugKu Web渗透之需要管理员
  • DDD架构实战 充血模型 电商订单
  • Qt客户端技巧 -- 窗口美化 -- 圆角窗口
  • 当下AI智能硬件方案浅谈
  • JS-- for...in和for...of
  • pandas随笔
  • Google机器学习实践指南(机器学习模型泛化能力)
  • 博弈论概述
  • RockyLinux9.6搭建k8s集群
  • 崇明注册公司/seo快速优化软件网站
  • 怎么让学生在网站上做问卷调查/北京百度关键词排名
  • 长沙网站建设设计/苹果被曝开发搜索引擎对标谷歌
  • 做网站背景全覆盖的代码/企业产品推广运营公司
  • 怎么在备案号添加网站/如何自己开发一个网站
  • 卖建材的网站有哪些/会员制营销