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 connfdconnectionChannel
②Poller和EPollPoller - Demultiplex
std::unordered_map<int, Channel*> channels
③EventLoop - Reactor
ChannelList activeChannels_;std::unique_ptr poller_;int wakeupFd ; -> loopstd::unique_ptr wakeupChannel ;
④Thread和EventLoopThread
⑤EventLoopThreadPool
getNextLoop() : 通过轮询算法获取下一个subloop baseLoop一个thread对应一个loop => one loop per thread
⑥Socket
⑦Acceptor
主要封装了listenfd相关的操作 socket bind listen baseLoop
⑧Buffer
缓冲区 应用写数据 -> 缓冲区 -> Tcp发送缓冲区 -> sendprependable readeridx writeridx
⑨TcpConnection
一个连接成功的客户端对应一个TcpConnection Socket Channel 各种回调 发送和接收缓冲区
⑩TcpServer
Acceptor EventLoopThreadPoolConnectionMap 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, 存储数据)
- 客户端准备数据:应用程序在客户端生成需要发送的数据。
- 序列化为 JSON:客户端将这些数据转换(序列化)成 JSON 格式的字符串。
- 发送 HTTP 请求:客户端通过 HTTP 协议向服务器发送请求,JSON 字符串通常放在请求体 (request body) 中。
- 服务器接收请求:服务器接收到 HTTP 请求。
- 解析 JSON:服务器从请求体中提取 JSON 字符串,并将其转换(反序列化/解析)为服务器端编程语言可以处理的数据结构(如对象、字典等)。
- 处理业务逻辑:服务器根据解析后的数据执行相应的业务操作。
- 服务器准备响应数据:服务器生成要返回给客户端的数据。
- 序列化为 JSON:服务器将响应数据序列化为 JSON 格式的字符串。
- 发送 HTTP 响应:服务器通过 HTTP 协议将包含 JSON 数据的响应发送回客户端。
- 客户端接收响应:客户端接收到服务器的 HTTP 响应。
- 解析 JSON:客户端解析响应体中的 JSON 字符串,将其转换为客户端编程语言可以处理的数据结构。
- 客户端使用数据:客户端使用这些解析后的数据进行后续操作,如更新用户界面。
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构建的企业级后台管理系统,从项目结构来看,系统采用了经典的分层架构设计,包含完整的控制器层、服务层、数据访问层和实体层。项目整合了Web开发、数据库访问、权限控制等核心功能模块。 二、项目整体…...
C#文件压缩与解压缩全攻略:使用ZipFile与ZipArchive实现高效操作
C#文件压缩与解压缩全攻略:使用ZipFile与ZipArchive实现高效操作 在.NET 开发中,文件压缩与解压缩是常见的需求。无论是减少存储空间、加速网络传输,还是实现数据备份,System.IO.Compression命名空间都提供了强大的工具。本文将结…...

1、Go语言基础中的基础
摘要:马士兵教育的Go语言基础的视频笔记。 第一章:走进Golang 1.1、Go的SDK介绍 1.2、Go的项目基本目录结构 1.3、HelloWorld 1.4、编译 1.5、执行 1.6、一步到位 1.7、执行流程分析 1.8、语法注意事项 (1)源文件以"go&qu…...
Go语言基础知识总结(超详细整理)
1. Go语言简介 Go语言(又称Golang)是Google于2009年发布的开源编程语言,具备简洁、高效、并发等特点,适合服务器开发、云计算、大数据等场景。 2. 环境安装与配置 下载地址:https://golang.org/dl/安装后配置环境变量…...

buuctf——web刷题第二页
[网鼎杯 2018]Fakebook和[SWPU2019]Web1没有,共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(Model-View-Controller)和MVP(Model-View-Presenter)是两种广泛使用的分层架构模式,核心目标是解耦业务逻辑、数据和界面,提升代码可维护性和可测试性。以下是它们的对比详解: 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函数 作用:创建在另一个进程的虚拟地址空间中运行的线程 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 版本(2018 年 8 月)引入的依赖管理系统,用…...

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

agent 开发
什么是 agent? Agent智能体(又称AI Agent)是一种具备自主感知、决策与行动能力的智能系统,其核心在于模仿人类的认知过程来处理复杂任务。以下是其关键特性和发展现状的综合分析: 一、核心定义与特征 ### 自主决策…...
多系统一键打包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 出行导航的核心能力详解(无代码版),聚焦智能交互、多端协同与场景化创新: 一、交互革新:从被动响应到主动服务 意图驱动导航 自然语义理解:用户通过语音指令(如…...

深度学习环境配置指南:基于Anaconda与PyCharm的全流程操作
一、环境搭建前的准备 1. 查看基础环境位置 conda env list 操作说明:通过该命令确认Anaconda默认环境(base)所在磁盘路径(如D盘),后续操作需跳转至该磁盘根目录。 二、创建与激活独立虚拟环境 1. 创…...
03 Deep learning神经网络的编程基础 代价函数(Cost function)--吴恩达
深度学习中的损失函数(Cost Function)用于量化模型预测与真实数据的差距,是优化神经网络的核心指标。以下是常见类型及数学表达: 核心原理 逻辑回归通过sigmoid函数将线性预测结果转换为概率: y ^ ( i ) \hat{y}^{(i)}...

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

在SpringBoot中使用AWS SDK实现邮箱验证码服务
1.依赖导入(maven) <dependency><groupId>software.amazon.awssdk</groupId><artifactId>ses</artifactId><version>2.31.46</version></dependency> 2.申请两个key 发件人邮箱需要验证: …...
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 常见问题解答 引言 在地图应用开发中,图层过滤是常见的需…...

深入理解二叉搜索树:原理到实践
1.二叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树 若它的左树不为空,则左子树上所有节点的值都小于或等于根节点的值。若它的右树不为空,则右子树上所有节点的值都大于或等于根节点的…...

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

终极数据结构详解:从理论到实践
终极数据结构详解:从理论到实践 我将从 底层原理、时间复杂度、空间优化、实际应用 和 代码实现 五个维度,彻底解析数据结构。内容涵盖: 线性结构(数组、链表、栈、队列)非线性结构(树、图)高…...
STM32实战: CAN总线数据记录仪设计方案
以下是基于STM32的CAN总线数据记录仪/转发器的设计与实现方案,结合了核心功能和进阶需求: 系统架构 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使用介绍 知识点回顾: tensorboard的发展历史和原理tensorboard的常见操作tensorboard在cifar上的实战:MLP和CNN模型 之前的内容中,我们在神经网络训练中,为了帮助自己理解,借用了很多的组件&#x…...
Python训练营打卡Day46(2025.6.6)
知识点回顾: 不同CNN层的特征图:不同通道的特征图什么是注意力:注意力家族,类似于动物园,都是不同的模块,好不好试了才知道。通道注意力:模型的定义和插入的位置通道注意力后的特征图和热力图 i…...

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