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

文章目录

  • 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列是passwordstd::string storedEmail = results[0][1];    // 第1列是emailLOG(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"] = "登录成功";// 生成会话 IDstd::string session_id = StringUtil::generate_session_id();session_store[session_id] = username;// 设置会话 ID 到 Cookieresp.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.rhkb.cn/news/419837.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java | Leetcode Java题解之第391题完美矩形

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isRectangleCover(int[][] rectangles) {long area 0;int minX rectangles[0][0], minY rectangles[0][1], maxX rectangles[0][2], maxY rectangles[0][3];Map<Point, Integer> cnt new HashM…

Python AttributeError: ‘dict_values’ object has no attribute ‘index’

Python AttributeError: ‘dict_values’ object has no attribute ‘index’ 在Python编程中&#xff0c;AttributeError 是一个常见的异常类型&#xff0c;通常发生在尝试访问对象没有的属性或方法时。今天&#xff0c;我们将深入探讨一个具体的 AttributeError&#xff1a;“…

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

&#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系列 &#x1f308;Linux学习专栏&#xff1a; 南桥谈Linux &#x1f308;数据结构学习专栏&#xff1a; 数据结构杂谈 &#x1f308;数据…

ML19_GMM高斯混合模型详解

1. 中心极限定理 中心极限定理&#xff08;Central Limit Theorem, CLT&#xff09;是概率论中的一个重要定理&#xff0c;它描述了在一定条件下&#xff0c;独立同分布的随机变量序列的标准化和的分布趋向于正态分布的性质。这个定理在统计学中有着广泛的应用&#xff0c;尤其…

高通智能模组:以卓越优势引领科技潮流

一、高通智能模组的崛起与发展 在通信技术发展中&#xff0c;高通智能模组出现。5G 兴起&#xff0c;对模组有更高要求&#xff0c;高通凭借积累和创新捕捉需求。早期致力于研发 5G 技术&#xff0c;优化技术降低功耗提高处理能力&#xff0c;展现性能优势。在竞争中&#xff0…

黑豹X2(Panther-x2)刷机并驱动NPU/VPU、Jellyfin转码

文章目录 零、前言一、刷机1、下载所需文件2、开始刷机 二、连接SSH并初始化1、连接SSH2、初始化换源安装蓝牙模块驱动安装一些包检查驱动 三、安装Docker和Jellyfin1、安装Docker安装Docker Compose 2、安装Jellyfin3、配置转码 四、NPU的驱动&#xff08;可选&#xff09; 零…

消息队列 MQ 性能大揭秘

RabbitMQ 以下是rabbitmq官方针对RabbitMQ 3.12的性能测试报告&#xff0c;从报告中可以看到他测试的吞吐量是保持在万级的&#xff0c;延迟时间平均在25毫秒左右&#xff0c;最小延时可以达到微秒级。 另外图中还可以看到在低吞吐量的情况下rabbitmq的延迟速度非常的快&…

QT Creater实现国庆节主题项目【0基础完成版】

本文适用对象 想要学习qt creater的小白;想要学习c++制作软件的编程爱好者。可以先下载这篇博客绑定的资源,然后一边操作,一边学习,会更高效~0. 创建初始项目 一步步来操作吧,首先下载qt creter,之前发布过相关资源,大家直接查找下载,或者自行下载。 1. 初始代码 mai…

RabbitMQ 04 集群,用于提高系统性能

01.背景 02.单个节点的MQ会持久化的记录什么数据 03.集群情况下的MQ会持久化的记录什么数据 04.集群中的队列 单个节点的队列&#xff1a; 集群的队列&#xff1a; 05. 两个原因&#xff1a; 这样做带来的好处&#xff1a; 05.集群的交换机 交换机的本质 交换机在集…

VS Studio2022 最新的mission planner二次开发环境搭建 所有资源都在自己亲测 自己一步步搞出来的花了1个月(小白转行版

文章目录 1. 环境要求1.1 VS Studio下载1.2 Mission Planner2 Mission Planner打包msi(使用使用VisualStudio2022插件(Visual Studio Installer Projects 2022))3 打开设计器FlightData.cs1. 环境要求 Win10以上(目前实测了11,10也可以的) 1.1 VS Studio下载 VS Studio20…

C语言文件操作超详解

文章目录 1. 为什么使用文件2. 什么是文件2. 1 程序文件2. 2 数据文件2. 3 文件名3. 二进制文件和文本文件? 4. 文件的打开和关闭4. 1 流和标准流4. 1. 1 流4. 1. 2 标准流 4. 2 文件指针4. 3 文件的打开和关闭 5. 文件的顺序读写5. 1 顺序读写函数介绍5. 2 对比一组函数: 6. …

庆祝新年:白酒点亮团圆夜

随着岁末的钟声渐渐敲响&#xff0c;新年的脚步悄然而至。在这个辞旧迎新的时刻&#xff0c;家家户户都沉浸在喜庆与团圆的氛围中。而在这个特殊的夜晚&#xff0c;一瓶豪迈白酒&#xff08;HOMANLISM&#xff09;的出现&#xff0c;不仅为节日增添了几分醉人的色彩&#xff0c…

Linux编译内核选项说明

内核功能选择 编译内核时出现的提示信息是在描述内核配置界面中的导航和操作方式。具体解释如下&#xff1a; Arrow keys navigate the menu: 使用箭头键可以在菜单中上下左右移动。 <Enter> selects submenus ---> (or empty submenus ----): 按下回车键可以选择一个…

无人机之地面站篇

无人机的地面站&#xff0c;又称无人机控制站&#xff0c;是整个无人机系统的重要组成部分&#xff0c;扮演着作战指挥中心的角色。以下是对无人机地面站的详细阐述&#xff1a; 一、定义与功能 无人机地面站是指具有对无人机飞行平台和任务载荷进行监控和操纵能力的一组设备&…

我把多模态大模型接入了「小爱」,痛快来一场「表情包斗图」!

前两天&#xff0c;搞了个微信 AI 小助理-小爱(AI)&#xff0c;爸妈玩的不亦乐乎。 零风险&#xff01;零费用&#xff01;我把AI接入微信群&#xff0c;爸妈玩嗨了&#xff0c;附教程&#xff08;下&#xff09; 最近一直在迭代中&#xff0c;挖掘小爱的无限潜力: 链接丢给…

什么是点对点专线、SDH专线以及MSTP专线?

点对点专线&#xff08;Point-to-Point Circuit&#xff09;、SDH专线&#xff08;Synchronous Digital Hierarchy&#xff09;以及MSTP专线&#xff08;Multi-Service Transport Platform&#xff09;都是企业级通信服务中常见的网络连接类型&#xff0c;主要用于提供高带宽、…

【C语言】指针深入讲解(下)

目录 前言回调函数回调函数的概念回调函数的使用 qsort函数的使用和模拟实现qsort函数的介绍qsort函数的使用qsort函数模拟实现 前言 今天我们来学习指针最后一个知识点回调函数&#xff0c;这个知识点也很重要&#xff0c;希望大家能坚持学习下去。 没学习之前指针知识内容的…

通过FFmpeg和URL查看流的编码格式

FFmpeg下载后会有三个执行文件&#xff0c;跳转到FFmpeg所在文件夹 查看视频流URL地址的编码格式命令&#xff1a; // 在下载ffmpeg的文件夹中执行如下命令&#xff0c;可查看流的编码格式&#xff0c;这里的测试流是H264编码ffprobe http://devimages.apple.com/iphone/sample…

2024数学建模国赛选题建议+团队助攻资料(已更新完毕)

目录 一、题目特点和选题建议 二、模型选择 1、评价模型 2、预测模型 3、分类模型 4、优化模型 5、统计分析模型 三、white学长团队助攻资料 1、助攻代码 2、成品论文PDF版 3、成品论文word版 9月5日晚18&#xff1a;00就要公布题目了&#xff0c;根据历年竞赛题目…

激光二极管知识汇总

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 前言一、什么是激光二极管二、激光二极管的发光原理三、导电特性1、正向特性2、反向特性 四、激光二极管和LED的区别五…