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负责调具体的回调操作

cpp 复制代码
//根据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开启事件循环代码模块

cpp 复制代码
// 开启事件循环
void EventLoop::loop()
{
    looping_ = true;
    quit_ = false;

    LOG_INFO("eventloop %p start looping\n", this);

    while (!quit_)
    {
        activeChannels_.clear();
        // 监听两类fd 一种是client的fd 一种wakeupfd
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
        for (Channel *channel : activeChannels_)
        {
            // Poller监听哪些channel发生事件了 然后上报给EventLoop 通知channel处理相应的事件
            channel->handleEvent(pollReturnTime_);
        }
        // 执行当前eventloop事件循环 需要处理的回调操作
        /*
         IO线程 mainloop accept fd《=channel subloop
         mainloop 事先注册一个回调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 的具体体现

cpp 复制代码
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;
}
cpp 复制代码
// 下面这个方法是在单独的新线程里面运行的
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.poll

    std::unique_lock<std::mutex> lock(mutex_);
    loop_=nullptr;
}

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

1.6 muduo网络库的具体案例
cpp 复制代码
#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线程数量  loopthread
        server_.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 bind
    echoServer.start();                                // listen  loopthread  listenfd => accpetorchannle => mainloop
    loop.loop();                                       // 启动mainloop的底层poller
    return 0;
}
2.Thread_pool线程池

.h

cpp 复制代码
#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

cpp 复制代码
#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;
}
cpp 复制代码
+-------------------+
|    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进行建表,插入,更新,查询等操作

cpp 复制代码
// 初始化数据库连接
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数据交换格式
cpp 复制代码
sequenceDiagram
    participant 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.注册

①注册的入口函数

cpp 复制代码
// 注册接口主流程
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;
    }
    // 解析json
    if (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数据

cpp 复制代码
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;
}

③封装注册的用户信息

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

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

cpp 复制代码
// 注册用户到数据库
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对象,会crash
        CPrepareStatement *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.登录

①用户登录的入口函数

cpp 复制代码
 * @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;
    }
    // 解析json
    if (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;
    }

    // 生成token
    if (setToken(user_name, token) < 0)
    {
        LOG_ERROR << "setToken failed";
        encodeLoginJson(1, token, str_json);
        return -1;
    }
    // 返回登录成功结果
    encodeLoginJson(0, token, str_json);
    return 0;
}

②解析json数据

cpp 复制代码
// 解析登录信息,将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;
}

③封装登录的用户信息

cpp 复制代码
// 封装登录结果的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;
}

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

cpp 复制代码
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;
        else
            ret = -1;
    }
    else
    { // 用户不存在
        ret = -1;
    }

    delete pResultSet;

    return ret;
}

⑤生成token

cpp 复制代码
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位随机token

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

相关推荐
Dynadot_tech1 天前
使用API有效率地管理Dynadot域名,查看域名市场中所售域名的详细信息
api·域名·dynadot·域名市场
ApiHug2 天前
ApiHug 1.3.9 支持 Spring 3.5.0 + Plugin 0.7.4 内置小插件升级!儿童节快乐!!!
java·后端·spring·api·apihug·apismart
webmote334 天前
DeepSeek私域数据训练之封装Anything LLM的API 【net 9】
.net·api·deepseek
慧都小妮子4 天前
跨平台浏览器集成库JxBrowser 支持 Chrome 扩展程序,高效赋能 Java 桌面应用
开发语言·python·api·jxbrowser·chrome 扩展程序
Web极客码4 天前
WordPress 6.5版本带来的新功能
前端·api·wordpress
天才在此5 天前
开源库免费API服务平台 ALLBEAPI
开源·api·免费
Estar.Lee7 天前
腾讯云开发者社区文章内容提取免费API接口教程
android·云计算·腾讯云·api·免费api·api大全
dearxue8 天前
ApiHug 1.3.9 支持 Spring 3.5.0 + Plugin 0.7.4 内置小插件升级 & 儿童节快乐!
spring·api·intellij idea
PPIO派欧云8 天前
PPIO私有化部署模板上新!10分钟拥有专属最新DeepSeek模型
llm·aigc·api