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

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 有效,服务器就认为该用户已经登录,并允许其访问

相关文章:

My图床项目

引言: 在海量文件存储中尤其是小文件我们通常会用上fastdfs对数据进行高效存储,在现实生产中fastdfs通常用于图片,文档,音频等中小文件。 一.项目中用到的基础组件(Base) 1.网络库(muduo) 我们就以muduo网络库为例子讲解IO多路复用和reactor网络模型 1.1 IO多路复用 我们可以…...

SpringBoot3项目架构设计与模块解析

一、项目概述 这是一个基于SpringBoot3构建的企业级后台管理系统&#xff0c;从项目结构来看&#xff0c;系统采用了经典的分层架构设计&#xff0c;包含完整的控制器层、服务层、数据访问层和实体层。项目整合了Web开发、数据库访问、权限控制等核心功能模块。 二、项目整体…...

C#文件压缩与解压缩全攻略:使用ZipFile与ZipArchive实现高效操作

C#文件压缩与解压缩全攻略&#xff1a;使用ZipFile与ZipArchive实现高效操作 在.NET 开发中&#xff0c;文件压缩与解压缩是常见的需求。无论是减少存储空间、加速网络传输&#xff0c;还是实现数据备份&#xff0c;System.IO.Compression命名空间都提供了强大的工具。本文将结…...

1、Go语言基础中的基础

摘要&#xff1a;马士兵教育的Go语言基础的视频笔记。 第一章&#xff1a;走进Golang 1.1、Go的SDK介绍 1.2、Go的项目基本目录结构 1.3、HelloWorld 1.4、编译 1.5、执行 1.6、一步到位 1.7、执行流程分析 1.8、语法注意事项 &#xff08;1&#xff09;源文件以"go&qu…...

Go语言基础知识总结(超详细整理)

1. Go语言简介 Go语言&#xff08;又称Golang&#xff09;是Google于2009年发布的开源编程语言&#xff0c;具备简洁、高效、并发等特点&#xff0c;适合服务器开发、云计算、大数据等场景。 2. 环境安装与配置 下载地址&#xff1a;https://golang.org/dl/安装后配置环境变量…...

buuctf——web刷题第二页

[网鼎杯 2018]Fakebook和[SWPU2019]Web1没有&#xff0c;共30题 目录 [BSidesCF 2020]Had a bad day [网鼎杯 2020 朱雀组]phpweb [BJDCTF2020]The mystery of ip [BUUCTF 2018]Online Tool [GXYCTF2019]禁止套娃 [GWCTF 2019]我有一个数据库 [CISCN2019 华北赛区 Day2…...

MVC与MVP设计模式对比详解

MVC&#xff08;Model-View-Controller&#xff09;和MVP&#xff08;Model-View-Presenter&#xff09;是两种广泛使用的分层架构模式&#xff0c;核心目标是解耦业务逻辑、数据和界面&#xff0c;提升代码可维护性和可测试性。以下是它们的对比详解&#xff1a; MVC 模式&…...

内嵌式mqtt server

添加moquette依赖 <dependency><groupId>io.moquette</groupId><artifactId>moquette-broker</artifactId><version>0.17</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>…...

二叉树的遍历总结

144.二叉树的前序遍历(opens new window)145.二叉树的后序遍历(opens new window)94.二叉树的中序遍历 二叉数的先中后序统一遍历法 public static void preOrder(BiTree root){BiTree p root;LinkedList<BiTree> stack new LinkedList<>();while(p ! null ||…...

win32相关(远程线程和远程线程注入)

远程线程和远程线程注入 CreateRemoteThread函数 作用&#xff1a;创建在另一个进程的虚拟地址空间中运行的线程 HANDLE CreateRemoteThread([in] HANDLE hProcess, // 需要在哪个进程中创建线程[in] LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全…...

【Go语言基础【5】】Go module概述:项目与依赖管理

文章目录 一、Go Module 概述二、Go Module 核心特性1. 项目结构2. 依赖查找机制 三、如何启用 Go Module四、创建 Go Module 项目五、Go Module 关键命令 一、Go Module 概述 Go Module 是 Go 1.11 版本&#xff08;2018 年 8 月&#xff09;引入的依赖管理系统&#xff0c;用…...

[Spring]-AOP

AOP场景 AOP: Aspect Oriented Programming (面向切面编程) OOP: Object Oriented Programming (面向对象编程) 场景设计 设计: 编写一个计算器接口和实现类&#xff0c;提供加减乘除四则运算 需求: 在加减乘除运算的时候需要记录操作日志(运算前参数、运算后结果)实现方案:…...

agent 开发

什么是 agent&#xff1f; Agent智能体&#xff08;又称AI Agent&#xff09;是一种具备自主感知、决策与行动能力的智能系统&#xff0c;其核心在于模仿人类的认知过程来处理复杂任务。以下是其关键特性和发展现状的综合分析&#xff1a; 一、核心定义与特征 #‌## 自主决策…...

多系统一键打包docker compose下所有镜像并且使用

本方法适合在已经pull好docker镜像正常使用的机器 将环境迁移到无网络 或者网络不好的机器使用 linux 用法 cd 到 docker-compose.yml 所在目录 ./save_compose_images.sh #!/bin/bash # 拉取镜像并保存为 .tar 文件 docker save $(docker-compose images | awk {print…...

Golang——5、函数详解、time包及日期函数

函数详解、time包及日期函数 1、函数1.1、函数定义1.2、函数参数1.3、函数返回值1.4、函数类型与变量1.5、函数作参数和返回值1.6、匿名函数、函数递归和闭包1.7、defer语句1.8、panic和recover 2、time包以及日期函数2.1、time.Now()获取当前时间2.2、Format方法格式化输出日期…...

【HarmonyOS 5】出行导航开发实践介绍以及详细案例

以下是 ‌HarmonyOS 5‌ 出行导航的核心能力详解&#xff08;无代码版&#xff09;&#xff0c;聚焦智能交互、多端协同与场景化创新&#xff1a; 一、交互革新&#xff1a;从被动响应到主动服务 ‌意图驱动导航‌ ‌自然语义理解‌&#xff1a;用户通过语音指令&#xff08;如…...

深度学习环境配置指南:基于Anaconda与PyCharm的全流程操作

一、环境搭建前的准备 1. 查看基础环境位置 conda env list 操作说明&#xff1a;通过该命令确认Anaconda默认环境&#xff08;base&#xff09;所在磁盘路径&#xff08;如D盘&#xff09;&#xff0c;后续操作需跳转至该磁盘根目录。 二、创建与激活独立虚拟环境 1. 创…...

03 Deep learning神经网络的编程基础 代价函数(Cost function)--吴恩达

深度学习中的损失函数(Cost Function)用于量化模型预测与真实数据的差距,是优化神经网络的核心指标。以下是常见类型及数学表达: 核心原理 逻辑回归通过sigmoid函数将线性预测结果转换为概率: y ^ ( i ) \hat{y}^{(i)}...

打卡day46

知识点回顾&#xff1a; 不同CNN层的特征图&#xff1a;不同通道的特征图什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。通道注意力&#xff1a;模型的定义和插入的位置通道注意力后的特征图和热力图 内…...

在SpringBoot中使用AWS SDK实现邮箱验证码服务

1.依赖导入&#xff08;maven&#xff09; <dependency><groupId>software.amazon.awssdk</groupId><artifactId>ses</artifactId><version>2.31.46</version></dependency> 2.申请两个key 发件人邮箱需要验证&#xff1a; …...

AndroidR车机TextToSpeech音频焦点异常问题分析

一、引言 文章《Android车机之TextToSpeech》介绍了TextToSpeech的使用,当前较多座舱系统语音服务都接入了原生TextToSpeech接口调用。 我司自研语音TTS服务,也接入了此TTS接口调用,对外提供TextToSpeech能力,播报时由客户端Client自行管理音频焦点,播报前申请音频焦点,…...

ArcGIS Maps SDK for JavaScript:使用图层过滤器只显示FeatureLayer的部分要素

文章目录 引言1 需求场景分析2精确过滤实现方案2.1 基础过滤语法2.2 动态过滤实现 3 模糊查询进阶技巧3.1 LIKE操作符使用3.2 特殊字段处理 4. 性能优化与注意事项4.1 服务端vs客户端过滤4.2 最佳实践建议 5 常见问题解答 引言 在地图应用开发中&#xff0c;图层过滤是常见的需…...

深入理解二叉搜索树:原理到实践

1.二叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树 若它的左树不为空&#xff0c;则左子树上所有节点的值都小于或等于根节点的值。若它的右树不为空&#xff0c;则右子树上所有节点的值都大于或等于根节点的…...

测试W5500的第11步_使用ARP解析IP地址对应的MAC地址

本文介绍了基于W5500芯片的ARP协议实现方法&#xff0c;详细阐述了ARP请求与回复的工作机制。ARP协议通过广播请求和单播回复实现IP地址与MAC地址的映射&#xff0c;确保局域网设备间的可靠通信。文章提供了完整的STM32F10x开发环境下的代码实现&#xff0c;包括网络初始化、SP…...

终极数据结构详解:从理论到实践

终极数据结构详解&#xff1a;从理论到实践 我将从 底层原理、时间复杂度、空间优化、实际应用 和 代码实现 五个维度&#xff0c;彻底解析数据结构。内容涵盖&#xff1a; 线性结构&#xff08;数组、链表、栈、队列&#xff09;非线性结构&#xff08;树、图&#xff09;高…...

STM32实战: CAN总线数据记录仪设计方案

以下是基于STM32的CAN总线数据记录仪/转发器的设计与实现方案&#xff0c;结合了核心功能和进阶需求&#xff1a; 系统架构 graph TBA[CAN总线] -->|CAN_H/CAN_L| B(STM32 bxCAN)B --> C[数据处理核心]C --> D[SD卡存储<br>FATFS文件系统]C --> E[串口输出…...

【k8s】k8s集群搭建

k8s集群搭建 一、环境准备1.1 集群类型1.2 安装方式1.3 主机规划1.4 环境配置1.4.1 说明1.4.2 初始化1.4.3 关闭防火墙和禁止防火墙开机启动1.4.4 设置主机名1.4.5 主机名解析1.4.6 时间同步1.4.7 关闭selinux1.4.8 关闭swap分区1.4.9 将桥接的IPv4流量传递到iptables的链1.4.1…...

60天python训练计划----day45

DAY 45 Tensorboard使用介绍 知识点回顾&#xff1a; tensorboard的发展历史和原理tensorboard的常见操作tensorboard在cifar上的实战&#xff1a;MLP和CNN模型 之前的内容中&#xff0c;我们在神经网络训练中&#xff0c;为了帮助自己理解&#xff0c;借用了很多的组件&#x…...

Python训练营打卡Day46(2025.6.6)

知识点回顾&#xff1a; 不同CNN层的特征图&#xff1a;不同通道的特征图什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。通道注意力&#xff1a;模型的定义和插入的位置通道注意力后的特征图和热力图 i…...

C# Wkhtmltopdf HTML转PDF碰到的问题

最近碰到一个Html转PDF的需求&#xff0c;看了一下基本上都是需要依赖Wkhtmltopdf&#xff0c;需要在Windows或者linux安装这个可以后使用。找了一下选择了HtmlToPDFCore&#xff0c;这个库是对Wkhtmltopdf.NetCore简单二次封装&#xff0c;这个库的好处就是通过NuGet安装HtmlT…...