【项目功能扩展】在线网站 -用户管理功能(用户注册登录修改等、利用cookie存储用户会话状态)

news2024/9/20 0:46:09

文章目录

  • 0. 前言
    • 开发环境 & 涉及技术
  • 1. 宏观结构
  • 2. 后端部分
    • ① sqlite 管理类
    • ② user 管理类
  • 3. 前端部分(与后端交互)
    • ① 登录
    • ② 注册
    • ③ 查看登录用户的信息
    • ④ 更新用户信息
    • ⑤ 登出用户 & 注销用户
    • 注意
  • 效果演示

0. 前言

源码链接:

源码 - onlineJudge / users

开发环境 & 涉及技术

开发环境

  • Linux ubuntu20.4
  • vscode

涉及技术

  • jsoncpp 进行数据
  • httplib网络库 使用
  • sqlite 轻量化数据库

1. 宏观结构

该功能只是一个扩展,需要的内容并不多:

—comm(负责公共的功能模块,在项目中便已实现)
------- 数据库管理
------- 字符串切割
------- 日志文件
------- … …
—users.hpp
—sqlite数据库:轻量化数据库,可以直接创建使用


2. 后端部分

① sqlite 管理类


#include <sqlite3.h>
#include <string>
#include <vector>
#include <iostream>
#include "../comm/log.hpp"

using namespace ns_log;
class SQLiteManager
{
public:
    SQLiteManager() = default; // 默认构造函数
    SQLiteManager(const std::string &dbName) : db(nullptr), dbName(dbName) {}

    ~SQLiteManager()
    {
        closeDatabase();
    }

    // 打开数据库
    bool openDatabase()
    {}

    void closeDatabase()
    {}

    bool executeQuery(const std::string &query)
    {}

    std::vector<std::vector<std::string>> executeSelect(const std::string &query)
	{}

    std::string getErrorMessage() const
    {}

    std::vector<std::vector<std::string>> getUserInfoByName(const std::string &name)
    {}

private:
    static int callback(void *data, int argc, char **argv, char **azColName)
    {}

    sqlite3 *db;
    std::string dbName;
    std::string errorMessage;
};

下面对这些函数一一进行介绍:

sqlite数据库打开与关闭

对于这一部分,当创建sqliteManager类后,根据传入的数据库位置,创建数据库对象(调用系统接口)

bool openDatabase()
{
    int result = sqlite3_open(dbName.c_str(), &db);
    if (result != SQLITE_OK)
    {
        errorMessage = "Cannot open database: " + std::string(sqlite3_errmsg(db));
        LOG("FATAL") << errorMessage << "\n";
        return false;
    }
    return true;
}

void closeDatabase()
{
    if (db)
    {
        sqlite3_close(db);
        db = nullptr;
    }
}

执行选择语句(获取返回值)

executeSelect用于实现select语句,将获取到的内容通过二维的字符串数组返回,比如执行语句:select * from users where username = ‘alice’;
此时result中的元素为:
{{alice, password123, 123@emial.com, 123-4567-8910}}

std::vector<std::vector<std::string>> executeSelect(const std::string &query)
{
    // LOG(DEBUG) << "执行语句: " << query << "\n";
    std::vector<std::vector<std::string>> result;
    char *errMsg = nullptr;
    char **results = nullptr;
    int rows, cols;

    int resultCode = sqlite3_get_table(db, query.c_str(), &results, &rows, &cols, &errMsg);
    if (resultCode != SQLITE_OK)
    {
        errorMessage = "SQL error: " + std::string(errMsg);
        sqlite3_free(errMsg);
        return result;
    }

    LOG(DEBUG) << "查询结果行数: " << rows << ", 列数: " << cols << "\n";

    if (results == nullptr)
    {
        errorMessage = "Results are null";
        return result;
    }

    // for (int i = 0; i < (rows + 1) * cols; ++i) {
    //     std::cout << "results[" << i << "] = " << (results[i] ? results[i] : "NULL") << std::endl;
    // }

    for (int i = 0; i < rows; ++i) // 从第一行数据开始
    {
        std::vector<std::string> row;
        for (int j = 0; j < cols; ++j)
        {
            row.push_back(results[(i + 1) * cols + j] ? results[(i + 1) * cols + j] : "");
        }
        result.push_back(row);
    }

    sqlite3_free_table(results);
    LOG(DEBUG) << "查询结果: " << result.size() << "行\n";
    return result;
}

执行语句

该函数直接调用系统接口,判断是否出错;

bool executeQuery(const std::string &query)
{
    char *errMsg = nullptr;
    int result = sqlite3_exec(db, query.c_str(), nullptr, nullptr, &errMsg);
    if (result != SQLITE_OK)
    {
        errorMessage = "SQL error: " + std::string(errMsg);
        sqlite3_free(errMsg);
        return false;
    }
    return true;
}

其他函数

  • getErrorMessage():当调用了执行语句的函数后,可以通过调用该函数,来获取错误信息(也可以直接传给执行语句一个输出型参数);
  • getUserInfoByName:获取用户的所有信息;
    std::string getErrorMessage() const
    {
        return errorMessage;
    }

    std::vector<std::vector<std::string>> getUserInfoByName(const std::string &name)
    {
        std::string query = "SELECT * FROM users WHERE name = '" + name + "';";
        return executeSelect(query);
    }

② user 管理类

首先封装一个用户类,相关的接口函数根据网站待实现的功能而定:

namespace ns_users
{
    class Users
    {
    private:
        pthread_mutex_t _mutex;
        SQLiteManager dbManager; // 数据库管理器

    public:
        Users(const std::string &dbName) : dbManager(dbName)
        {
            pthread_mutex_init(&_mutex, nullptr);
            if (!dbManager.openDatabase())
            {
                std::cerr << "无法打开数据库 " << dbManager.getErrorMessage() << std::endl;
            }
            LOG(DEBUG) << "初始化用户管理器" << std::endl;
        }

        ~Users()
        {
            pthread_mutex_destroy(&_mutex);
            dbManager.closeDatabase();
        }
        
        // sqlite版本
        // 判断用户是否存在
        bool isUserExist(const std::string &username)
        {}

        // 登录函数
        bool login(const std::string &username, const std::string &password, const std::string &email)
        {}
		
		// 注册用户
        bool registerUser(const std::string &username, const std::string &password, const std::string &email, std::string &resp)
        {}

        // 获取用户信息
        Json::Value getUserInfo(const std::string &username)
        {}

        // 更新用户信息
        bool updateUserInfo(const std::string &username, const Json::Value &newInfo, std::string &resp) {}
}

根据上面的框架,用户在初始化时会同时初始化存储用户信息的数据库;

登录与注册

下面是实现的登录界面,服务器提取用户输入的内容后,将参数传给底层,底层调用login函数,再将响应发回客户端,实现登录:
在这里插入图片描述

  • 具体的login与register并不困难:
    • login(): 通过调用executeSelect函数,直接获取数据库中的用户信息,再对比即可。
    • register(): 同样调用函数,在判断用户信息不重复有效之后,将数据插入到用户表中。
// 登录函数
bool login(const std::string &username, const std::string &password, const std::string &email)
{
    // 查询是否存在指定用户名的用户
    std::string query = "SELECT password, email FROM users WHERE username = '" + username + "';";
    auto results = dbManager.executeSelect(query);

    // 检查用户是否存在
    if (!results.empty() && results[0].size() >= 2)
    {
        // 用户存在,提取存储的密码和电子邮件
        std::string storedPassword = results[0][0]; // 第0列是password
        std::string storedEmail = results[0][1];    // 第1列是email
        LOG(INFO) << "数据库信息: " << "密码: " << storedPassword << " " << "电子邮件:" << storedEmail << std::endl;
        // LOG(INFO) << "用户输入信息: " << "密码: " << password << " " << "电子邮件:" << email << std::endl;

        // 验证密码和电子邮件
        if (storedPassword == password && storedEmail == email)
        {
            LOG(DEBUG) << "登录成功" << std::endl;
            return true;
        }
        else
        {
            LOG(DEBUG) << "登录失败, 信息不匹配" << std::endl;
            return false;
        }
    }
    else
    {
        LOG(DEBUG) << "登录失败, 用户不存在" << std::endl;
        return false;
    }
}

bool isUserExist(const std::string &username)
{
    std::string query = "SELECT username FROM users WHERE username = '" + username + "';";
    auto results = dbManager.executeSelect(query);

    return !results.empty() && !results[0].empty();
}


bool registerUser(const std::string &username, const std::string &password, const std::string &email, std::string &resp)
{
    LOG(DEBUG) << "开始注册用户" << std::endl;
    // 检查用户名是否已经存在
    std::string checkUsernameQuery = "SELECT 1 FROM users WHERE username = '" + username + "';";
    auto usernameResults = dbManager.executeSelect(checkUsernameQuery);

    if (!usernameResults.empty())
    {
        // 用户名已存在
        resp = "注册失败,用户名已存在";
        return false;
    }

    // 检查邮箱是否已经存在
    std::string checkEmailQuery = "SELECT 1 FROM users WHERE email = '" + email + "';";
    auto emailResults = dbManager.executeSelect(checkEmailQuery);

    if (!emailResults.empty())
    {
        // 邮箱已存在
        resp = "注册失败,邮箱已存在";
        return false;
    }

    LOG(DEBUG) << username << " " << password << " " << email << std::endl;
    // 用户名和邮箱都不存在,执行插入操作
    std::string insertQuery = "INSERT INTO users (username, password, email) VALUES ('" + username + "', '" + password + "', '" + email + "');";
    bool success = dbManager.executeQuery(insertQuery);

    if (success)
    {
        LOG(INFO) << "注册用户成功" << std::endl;
        resp = "注册成功";
        return true;
    }
    else
    {
        resp = "注册失败,数据库错误";
        return false;
    }
}

获取用户信息 与 修改用户信息

  • getUserInfo 与 updateUserInfo 一致:均调用executeSelete后,前者获取用户信息封装成Json:Value,后者直接更新数据库信息。
// 获取用户信息
Json::Value getUserInfo(const std::string &username)
{
    LOG(DEBUG) << "getUserInfo代码执行" << "\n";
    Json::Value userInfo;

    // 查询用户信息
    std::string query = "SELECT * FROM users WHERE username = '" + username + "';";
    auto results = dbManager.executeSelect(query);
    std::string errMsg = dbManager.getErrorMessage();
    if (!errMsg.empty())
    {
        LOG(ERROR) << "查询用户信息失败: " << errMsg << std::endl;
        userInfo["error"] = errMsg;
        return userInfo;
    }

    // 检查用户是否存在
    if (!results.empty() && !results[0].empty())
    {
        // 用户存在,提取存储的信息
        if (results[0].size() > 0)
            userInfo["id"] = results[0][0];
        if (results[0].size() > 1)
            userInfo["username"] = results[0][1];
        if (results[0].size() > 2)
            userInfo["password"] = results[0][2];
        if (results[0].size() > 3)
            userInfo["email"] = results[0][3];
        if (results[0].size() > 4)
            userInfo["phone"] = results[0][4];
        if (results[0].size() > 5)
            userInfo["gender"] = results[0][5];
        if (results[0].size() > 6)
            userInfo["description"] = results[0][6];
    }
    else
    {
        // 用户不存在
        userInfo["error"] = "用户不存在";
    }

    // LOG(DEBUG) << "获取用户信息: " << userInfo.toStyledString() << std::endl;
    return userInfo;
}

// 更新用户信息
bool updateUserInfo(const std::string &username, const Json::Value &newInfo, std::string &resp) {
    LOG(DEBUG) << "updateUserInfo代码执行" << "\n";
    
    // 检查用户是否存在
    Json::Value userInfo = getUserInfo(username);
    if (userInfo.isMember("error")) {
        resp = userInfo["error"].asString();
        return false;
    }

    // 构建更新语句
    std::string updateQuery = "UPDATE users SET ";
    bool first = true;

    if (newInfo.isMember("password")) {
        updateQuery += "password = '" + newInfo["password"].asString() + "'";
        first = false;
    }
    if (newInfo.isMember("email")) {
        if (!first) updateQuery += ", ";
        updateQuery += "email = '" + newInfo["email"].asString() + "'";
        first = false;
    }
    if (newInfo.isMember("phone")) {
        if (!first) updateQuery += ", ";
        updateQuery += "phone = '" + newInfo["phone"].asString() + "'";
        first = false;
    }
    if (newInfo.isMember("gender")) {
        if (!first) updateQuery += ", ";
        std::string gender = newInfo["gender"].asString();
        if (gender == "男") {
            gender = "Male";
        } else if (gender == "女") {
            gender = "Female";
        } else {
            gender = "Other";
        }
        updateQuery += "gender = '" + gender + "'";
        first = false;
    }
    if (newInfo.isMember("description")) {
        if (!first) updateQuery += ", ";
        updateQuery += "description = '" + newInfo["description"].asString() + "'";
    }

    updateQuery += " WHERE username = '" + username + "';";

    // 执行更新
    if (dbManager.executeQuery(updateQuery)) {
        std::cout << "用户信息更新成功!" << std::endl;
        return true;
    } else {
        resp = "更新失败: " + dbManager.getErrorMessage();
        LOG(ERROR) << "更新用户信息失败: " << resp << std::endl;
        return false;
    }
}

注销账户

 // 注销函数
bool logoff(const std::string &username)
{
    // 查询是否存在指定用户名的用户
    std::string query = "SELECT username FROM users WHERE username = '" + username + "';";
    auto results = dbManager.executeSelect(query);

    // 检查用户是否存在
    if (!results.empty() && results[0].size() >= 1)
    {
        // 用户存在,记录注销日志
        query = "delete from users where username = '" + username + "';";
        dbManager.executeQuery(query);
        LOG(DEBUG) << "注销成功,用户: " << username << std::endl;
        
        return true;
    }
    else
    {
        LOG(DEBUG) << "注销失败,用户不存在: " << username << std::endl;
        return false;
    }
}

此时用户类封装完毕:


3. 前端部分(与后端交互)

① 登录

在这里插入图片描述

下面简单看一下前端代码:

login.html

<div class="content">
   <h1 class="font_">用户登录</h1>
   <div class="login-form">
       <form id="login-form" action="/api/login" method="post">
           <!-- <form id="login-form" action="/login.html" method="post"> -->
           <input type="text" id="username" name="username" placeholder="用户名" required><br>
           <input type="email" id="email" name="email" placeholder="电子邮件" required><br>
           <input type="password" id="password" name="password" placeholder="密码" required><br>
           <input type="button" value="还没有账号?点击注册" onclick="window.location.href='/register.html';"><br>
           <input type="submit" value="登录">
       </form>
   </div>
</div>

登录模块在login.html的content部分,下面是其js代码:

js部分在用户点击了登录按钮后,给服务器发送一个post请求,随后接收服务器处理的响应:

  • 根据登录结果,弹出提示框;如果登录成功
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('login-form').addEventListener('submit', (event) => {
      event.preventDefault(); // 防止表单默认提交

      const username = document.getElementById('username').value;
      const password = document.getElementById('password').value;
      const email = document.getElementById('email').value;

      fetch('/api/login', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/x-www-form-urlencoded'
          },
          body: new URLSearchParams({
              'username': username,
              'password': password,
              'email': email
          })
      })
          .then(response => {
              return response.text().then(text => {
                  try {
                      return JSON.parse(text);
                  } catch (e) {
                      throw new Error('服务器响应不是有效的 JSON: ' + text);
                  }
              });
          })
          .then(data => {
              if (data.status === 'success') {
                  alert("登录成功!");
                  window.location.href = '/userInfo.html';
              } else {
                  alert(data.message);
              }
          })
          .catch(error => {
              console.error('请求失败:', error);
              alert('发生错误,请重试!');
          });
  });
});

服务器对登录请求的处理

svr->Post("/api/login", [&users](const httplib::Request &req, httplib::Response &resp)
{
  LOG(INFO) << "Received a login request" << std::endl;

  std::string username = req.get_param_value("username");
  std::string password = req.get_param_value("password");
  std::string email = req.get_param_value("email");
  Json::Value responseJson;

  if (user.login(username, password, email)) {
      responseJson["status"] = "success";
      responseJson["message"] = "登录成功";

      // 生成会话 ID
      std::string session_id = StringUtil::generate_session_id();
      session_store[session_id] = username;

      // 设置会话 ID 到 Cookie
      resp.set_header("Set-Cookie", "session_id=" + session_id);
      
      // 设置响应头和状态码
      resp.status = 200; // 使用 200 状态码
      resp.set_header("Content-Type", "application/json");
      resp.set_content(responseJson.toStyledString(), "application/json");

  } else {
      responseJson["status"] = "error";
      responseJson["message"] = "登录失败: 用户名或密码错误";

      resp.status = 200; // 使用 200 状态码
      resp.set_header("Content-Type", "application/json");
      resp.set_content(responseJson.toStyledString(), "application/json");
  } });

② 注册

在这里插入图片描述

注册功能与登录如出一辙,regiser.html的部分内容:

<div class="content">
    <h1 class="font_">用户注册</h1>
    <div class="register-form">
        <form id="register-form" action="/api/register" method="post">
            <!-- <form id="register-form"> -->
            <input type="text" id = "username" name="username" placeholder="用户名" required>
            <br>
            <input type="email" id = "email" name="email" placeholder="电子邮件" required>
            <br>
            <input type="password" id = "password" name="password" placeholder="密码" required>
            <br>
            <input type="submit" value="注册">
            <br>
            <input type="button" value="返回登录" onclick="window.location.href='/login.html';">
        </form>
        <!-- 用于显示响应消息 -->
        <div id="response-message" style="color: red; margin-top: 10px;"></div>
    </div>         
</div>

对于js代码,与登录功能基本一致,这里不放出来:

服务器对注册请求的处理

svr->Post("/api/register", [&users](const httplib::Request &req, httplib::Response &resp)
{
    LOG(INFO) << "Received a register request" << std::endl;

    std::string username = req.get_param_value("username");
    std::string email = req.get_param_value("email");
    std::string password = req.get_param_value("password");
    std::string message;

    // 创建一个 JSON 对象
    Json::Value jsonResponse;

    // 假设 user.registerUser 返回一个 bool 表示成功或失败
    bool success = user.registerUser(username, password, email, message);

    // 设置 JSON 响应内容
    jsonResponse["status"] = success ? "success" : "error";
    jsonResponse["message"] = message;

    // 将 JSON 对象转换为字符串
    Json::StreamWriterBuilder writer;
    std::string jsonString = Json::writeString(writer, jsonResponse);

    // 设置响应内容
    resp.set_content(jsonString, "application/json; charset=utf-8");
    resp.status = success ? 200 : 400; // 根据成功或失败设置状态码
});

③ 查看登录用户的信息

该功能用于实现查看已登录用户的相关信息。

在这里插入图片描述

userInfo.html

<div class="content">
        <div class="profile">
            <h1>用户信息</h1>
            <p><strong>姓名:</strong> <span id="username"></span></p>
            <p><strong>电子邮件:</strong> <span id="email"></span></p>
            <p><strong>电话:</strong> <span id="phone"></span></p>
            <p><strong>性别:</strong> <span id="gender"></span></p>
            <p><strong>个人描述:</strong> <span id="description"></span></p>
        </div>

        <div class="form-button">
            <input type="button" value="返回首页" onclick="window.location.href='/index.html';"><br>
        </div>
    </div>

    <!-- 遮罩层 -->
    <div id="editModalBackdrop"></div>

    <!-- 模态窗口 -->
    <div id="editModal">
        <h2>编辑个人信息</h2>
        <form id="editForm">
            <label for="newName">姓名:</label>
            <input type="text" id="newName" name="newName">

            <label for="newEmail">邮箱:</label>
            <input type="email" id="newEmail" name="newEmail">

            <label for="newPhone">电话:</label>
            <input type="tel" id="newPhone" name="newPhone">

            <label for="newGender">性别:</label>
            <select id="newGender" name="newGender">
                <option value=""></option>
                <option value=""></option>
            </select>

            <label for="newDescription">描述:</label>
            <textarea id="newDescription" name="newDescription"></textarea>

            <div class="form-buttons">
                <button type="submit" class="btn-primary">保存</button>
                <button type="button" id="closeModal" class="btn-secondary">关闭</button>
            </div>
        </form>
    </div>

下面是userInfo.js

document.addEventListener('DOMContentLoaded', function () {
    const editModal = document.getElementById('editModal');
    const editModalBackdrop = document.getElementById('editModalBackdrop');
    const editProfileLink = document.getElementById('editProfileLink');
    const closeModal = document.getElementById('closeModal');
    const editForm = document.getElementById('editForm');

    // 从服务器获取用户信息
    fetch('/api/user-info', {
        method: "GET",
        credentials: 'include'
    })

        .then(response => response.json())
        .then(data => {
            const userInfo = data.data;

            document.getElementById('username').textContent = userInfo.username;
            document.getElementById('email').textContent = userInfo.email;
            document.getElementById('phone').textContent = userInfo.phone;
            document.getElementById('gender').textContent = userInfo.gender;
            document.getElementById('description').textContent = userInfo.description;

            editProfileLink.addEventListener('click', function () {
                document.getElementById('newName').value = userInfo.username;
                document.getElementById('newEmail').value = userInfo.email;
                document.getElementById('newPhone').value = userInfo.phone;
                document.getElementById('newGender').value = userInfo.gender;
                document.getElementById('newDescription').value = userInfo.description;

                editModal.style.display = 'block';
                editModalBackdrop.style.display = 'block';
            });
        })

        .catch(error => console.error('Error fetching user info:', error));
});

服务器对查看用户信息请求的处理

svr->Get("/api/user-info", [](const httplib::Request &req, httplib::Response &resp)
{
     LOG(INFO) << "Received a userInfo request" << std::endl;
     auto cookies = req.get_header_value("Cookie");
     std::string session_id = StringUtil::extract_session_id(cookies);

     Json::Value responseJson;

     if (session_store.find(session_id) != session_store.end()) {
         std::string username = session_store[session_id];
         
         // 用户已登录,获取用户信息
         LOG(INFO) << "用户 \"" << username << "\" 处于登录状态中。" << std::endl;
         Json::Value userInfo = user.getUserInfo(username);
         LOG(INFO) << "getInfo执行完毕" << "\n";
         
         // 检查 userInfo 是否有效
         if (!userInfo.isNull()) {
             responseJson["status"] = "success";
             responseJson["message"] = "用户信息获取成功";
             responseJson["data"] = userInfo;  // 将用户信息包含在响应中
         } else {
             responseJson["status"] = "error";
             responseJson["message"] = "无法获取用户信息";
         }
     } else {
         // 用户未登录
         responseJson["status"] = "error";
         responseJson["message"] = "用户未登录";
     }

     LOG(INFO) << "Response JSON: " << responseJson << std::endl;
     Json::StreamWriterBuilder writer;
     std::string responseBody = Json::writeString(writer, responseJson);
     resp.set_content(responseBody, "application/json; charset=utf-8"); });

在上面的代码中,我们如何记录当前主机的登录用户?👇

【跳转】会话ID的记录


④ 更新用户信息

在这里插入图片描述

更新用户信息我们将其作为一个模态窗口:

closeModal.addEventListener('click', function () {
        editModal.style.display = 'none';
        editModalBackdrop.style.display = 'none';
    });

editModalBackdrop.addEventListener('click', function () {
    editModal.style.display = 'none';
    editModalBackdrop.style.display = 'none';
});

editForm.addEventListener('submit', function (event) {
    event.preventDefault();

    const updatedUserInfo = {
        username: document.getElementById('newName').value,
        email: document.getElementById('newEmail').value,
        phone: document.getElementById('newPhone').value,
        gender: document.getElementById('newGender').value,
        description: document.getElementById('newDescription').value
    };

    // 发送更新请求到服务器
    fetch('/api/update-user-info', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(updatedUserInfo),
        credentials: 'include'
    })
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                document.getElementById('username').textContent = updatedUserInfo.username;
                document.getElementById('email').textContent = updatedUserInfo.email;
                document.getElementById('phone').textContent = updatedUserInfo.phone;
                document.getElementById('gender').textContent = updatedUserInfo.gender;
                
                document.getElementById('description').textContent = updatedUserInfo.description;

                editModal.style.display = 'none';
                editModalBackdrop.style.display = 'none';
            } else {
                console.error('Error updating user info:', data.message);
            }
        })
        .catch(error => console.error('Error updating user info:', error));
});

同理于前面的代码,这里不再写出其他代码,请在源码处查看。


⑤ 登出用户 & 注销用户

根据上面提供的登录、注册、查看用户信息的html、js与服务器处理的c++代码,对于这两个功能的实现已经很简单了(属于userInfo.html的子部分):

// 登出用户
    const logoutLink = document.querySelector("#logoutLink");

    logoutLink.addEventListener("click", function (event) {
        event.preventDefault(); // 阻止默认行为

        if (confirm("确定要退出账户吗?")) {
            fetch('/api/logout', {
                method: 'GET',
                credentials: 'include' // 确保包括 Cookie
            })
                .then(response => {
                    if (response.redirected) {
                        window.location.href = response.url;
                    }
                })
                .catch(error => {
                    console.error('登出请求失败:', error);
                });
        }
    });

    // 注销账户
    const logoffLink = document.querySelector("#logoffLink");

    logoffLink.addEventListener("click", function (event) {
        event.preventDefault(); // 阻止默认行为
    
        if (confirm("确定要注销账户吗?")) {
            fetch('/api/log-off', {
                method: 'POST', // 将 GET 更改为 POST 方法
                credentials: 'include' // 确保包括 Cookie
            })
                .then(response => {
                    if (response.redirected) {
                        window.location.href = response.url;
                    }
                })
                .catch(error => {
                    console.error('登出请求失败:', error);
                });
        }
    });

注意

关于记录用户信息 - 会话ID

在实现 登录、查看用户信息等功能时,需要思考的问题是,网站如何知道当前主机的登录用户是哪一个?

这里就用到cookie的概念:

Cookie 是一种由服务器发送到客户端并存储在客户端设备上的小型数据文件。它们通常用于保存用户的会话状态、偏好设置或其他跟踪信息。当用户重新访问网站时,浏览器会将这些 Cookie 发送回服务器,帮助服务器识别用户并维持会话。例如,登录状态、购物车内容等信息通常通过 Cookie 进行管理。

就像上面代码中服务器响应的部分:

// 生成会话 ID
std::string session_id = StringUtil::generate_session_id();
session_store[session_id] = username;

// 设置会话 ID 到 Cookie
resp.set_header("Set-Cookie", "session_id=" + session_id);

对于上面的generate_session_id(),是我们自实现的模拟生成会话id的功能,通过生成会话id与从Cookie中获取id,就可以实现在网站中找到当前登录用户的信息

// 生成一个随机的32位session_id
static std::string generate_session_id()
{
    std::stringstream ss;
    std::random_device rd;
    std::mt19937 mt(rd());
    std::uniform_int_distribution<int> dist(0, 15);
    for (int i = 0; i < 32; ++i)
    {
        int r = dist(mt);
        ss << (r < 10 ? char('0' + r) : char('a' + r - 10));
    }
    return ss.str();
}

// 从cookies中提取session_id
static std::string extract_session_id(const std::string &cookies)
{
    std::string session_id;
    std::istringstream cookie_stream(cookies);
    std::string cookie;

    while (std::getline(cookie_stream, cookie, ';'))
    {
        std::string::size_type pos = cookie.find("session_id=");
        if (pos != std::string::npos)
        {
            session_id = cookie.substr(pos + 11); // 11 is the length of "session_id="
            break;
        }
    }
    return session_id;
}

效果演示

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity】简易而又实用的概率算法

1.两个数中任选一个&#xff08;抛硬币&#xff09; 基础版本&#xff1a; public int RandomBetweenTwoNumber(int a,int b) {float random Random.Range(0,1f);return radom<0.5f ? a : b ; } 升级版本&#xff08;支持概率调整&#xff09;&#xff1a; /*pa表示“…

并发编程:读写锁

一、ReentrantReadWriteLock 1.ReentrantReadWriteLock 是什么&#xff1f; ReentrantReadWriteLock 实现了 ReadWriteLock &#xff0c;是一个可重入的读写锁&#xff0c;既可以保证多个线程同时读的效率&#xff0c;同时又可以保证有写入操作时的线程安全。 public class …

CDGA|做好数据治理的几个策略,不看后悔

在当今这个数据驱动的时代&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;随着数据量的爆炸性增长和来源的多样化&#xff0c;如何有效地管理和利用这些数据&#xff0c;即数据治理&#xff0c;成为了企业面临的重要挑战。 良好的数据治理不仅能够提升数据质量&…

中医世家龚洪海博士:用医术和真诚赢得患者的心

医生&#xff0c;可以说是世界上最伟大的人&#xff0c;他们以高超的医疗技术和崇高的职业道德&#xff0c;以患者为先&#xff0c;为患者带来生的奇迹&#xff0c;抚平患者的病痛&#xff0c;是生命忠诚的的捍卫者。明代御医龚廷贤龚氏传人龚洪海博士就是这样一个&#xff0c;…

英国数字化战略下的人工智能时代:挑战与发展机遇

文章目录 前言一、英国数字化转型初探二、数字化转型重点举措1、 供应链2、金融市场3、数字基础设施4、科学研究5、数字技术赋能绿色转型6、数字包容性7、国际合作:重视与发展中国家合作8、完善数字民主建设三、战略启示前言 后疫情时代,英国正面临包括首相更迭频繁导致的内…

AnyGPT:多模态语言模型,任意处理语音、图像和音乐

人工智能咨询培训老师叶梓 转载标明出处 大模型的能力大多局限于文本处理&#xff0c;而现实世界环境本质上是多模态的&#xff0c;涉及视觉、语言、声音和触觉等多种感知渠道。为了使LLM能够更好地模拟人类的多模态感知能力&#xff0c;复旦大学的研究团队提出了AnyGPT&#x…

巴西美客多广告打法,这样开广告有泼天的流量!

听说做巴西美客多本土店不需要开广告就有流量&#xff1f;这是真的吗&#xff1f;相信这对于一直在做欧美市场的卖家来说&#xff0c;简直是不敢相信&#xff0c;What? 有运营巴西美客多本土店铺多年的卖家说&#xff0c;确实是不开广告也能获得不错的流量&#xff0c;过去几…

汽车EDI:montaplast EDI对接

Montaplast 是一家总部位于德国的全球知名汽车零部件供应商&#xff0c;专注于高精度塑料部件的设计、开发和生产。公司成立于1958年&#xff0c;主要为汽车行业提供轻量化、高性能的塑料解决方案。Montaplast 以其在注塑成型技术、表面处理和装配技术方面的专业能力而著称&…

vue3 项目中使用git

一.vue项目创建 二.创建本地仓库并和远程仓库进行绑定 在vue3-project-git 项目文件夹下 初始化一个新的Git仓库&#xff0c;可以看到初始化成功之后就会出现一个.git文件&#xff0c;该文件包含所有必要的 Git 配置和版本控制信息。 创建远程仓库: 打开gitee ,点击右上角 ‘…

电源模块检测方法之功率因数的测量

在设计和维护电源系统时&#xff0c;功率因数是一个不可忽视的参数。那么功率因数是什么呢&#xff1f;怎么测试电源模块的功率因数呢&#xff1f;又该如何提高功率因数呢&#xff1f;让我们一起来探讨吧。 一、功率因数概述 功率因数是指交流电路中有功功率和视在功率的比值&a…

安全产品概述

防火墙 防火墙的核心功能是过滤掉有害的流量&#xff0c;在专用网络和公共网络之间建立保护屏障。防火墙过滤通常基于一系列规则&#xff0c;如 IP 地址、域名、协议、端口号、关键字等&#xff0c;对入站和出站的流量进行过滤。这些规则也称为访问控制列表&#xff08;ACCESS…

假装勤奋,无效努力!看了上热搜的北京十一学校,我更加理解了衡水家长们的无奈——早读(逆天打工人爬取热门微信文章解读)

怀念伟大 引言Python 代码第一篇 看了上热搜的北京十一学校&#xff0c;我更加理解了衡水家长们的无奈第二篇 股市路注定是孤独的结尾 引言 不断尝试新的改变 发现最近把很多时间用在股票上 但是总结复盘却没有多 所以得改 一般是看一个视频讲解 然后一不小心就睡着了 日复一日…

超轻量级、支持插件的 .NET 网络通信框架

目录 前言 项目介绍 项目环境 项目功能 1、功能导图 2、项目文档 项目特点 1、传统 IOCP 与 TouchSocket 的 IOCP 模式 2、数据处理适配器 3、兼容性与适配 项目使用 1、Nuget安装 2、TcpService 3、TcpClient 4、TcpClient 断线重连 项目案例 1、工程师软件工…

elementUI之不会用

form表单注意事项 <template><div class"container"><el-form :model"form" label-width"80px" :rules"RulesName" ref"loginForm"><el-form-item label"姓名" prop"username">…

【阿雄不会写代码】全国职业院校技能大赛GZ036第九套

也不说那么多了&#xff0c;要用到这篇博客&#xff0c;肯定也知道他是干嘛的&#xff0c;给博主点点关注点点赞&#xff01;&#xff01;&#xff01;这样博主才能更新更多免费的教程&#xff0c;不然就直接丢付费专栏里了&#xff0c;需要相关文件请私聊

软件测试外包公司分享:软件产品鉴定测试内容和作用

软件产品鉴定测试是指对软件在不同阶段进行系统性、全面性的检测与评估&#xff0c;确保产品在功能、安全性、性能等方面达到既定标准。这对企业选择、验证及应用软件产品至关重要。 软件产品鉴定测试的内容主要包括以下几项&#xff1a;   1、功能测试&#xff1a;验证软件…

Sumsub 获评 Gartner® “新兴技术:针对在线欺诈预防的 GenAI 安全服务”代表性供应商

AI 为业务增长提供了巨大机遇。但也带来了重大威胁&#xff0c;比如可能会导致企业蒙受数百万美元损失的深度伪造攻击。 近日&#xff0c;全球领先的验证平台 Sumsub 被权威机构 Gartner 列入《新兴技术&#xff1a;针对在线欺诈预防的 GenAI 安全服务》报告的代表性供应商名单…

十二、C语言:内存函数

一、memcpy 1.1 使用 void * memcpy ( void * destination, const void * source, size_t num ); 1.前两个参数类型都是void*&#xff0c;因此可以拷贝任何数据类型&#xff1b; 2.num参数为要拷贝的字节数&#xff1b; int main() {char arr[10] "abcdef";char b…

第三届828 B2B企业节开幕,大腾智能携手华为云共谱数字化新篇章

8月27日&#xff0c;由华为携手上万家伙伴共同发起的第三届828 B2B企业节在贵州正式开幕。 本届企业节推出上万款数智产品&#xff0c;600多个精选解决方案&#xff0c;旨在融通数智供需&#xff0c;加速企业智改数转&#xff0c;助推中国数智产业实力再升级。中共贵州省委副书…

英文外链代发服务靠谱吗?

英文外链代发服务的可靠性因供应商和服务类型而异。外链代发服务的主要目标是提高网站在搜索引擎中的排名&#xff0c;通过增加指向目标网站的链接数量和质量来实现。然而&#xff0c;并不是所有的外链代发服务都是可靠的&#xff0c;很多外链都是只管发&#xff0c;但是发了有…