【项目】基于Linux和C++的动态在线视频点播系统设计

文章目录

  • 1. 前言
    • 1.1 源码
    • 1.2 项目简介
    • 1.3 实现内容
    • 1.4 涉及技术 / 环境
  • 2. 整体架构
    • 2.1 服务器功能
    • 2.2 服务器结构
  • 3. 前提步骤
    • 3.1 思路分析
    • 3.2 创建视频表
  • 4. 后端 基本功能实现(视频点播)
    • 4.1 服务端工具类实现
    • 4.2 日志输出类
    • 4.3 数据库/表 管理类
    • 4.4 服务器
  • 5. 功能测试
  • 6. 前端部分
    • 6.1 部分功能实现
      • ① 视频播放 | 显示
      • ② 搜索功能
    • 6.2 根据视频类型搜索
  • 7. 功能扩充
    • 用户管理类
    • 前端部分(待补充...)

1. 前言

1.1 源码

源码链接:

1.2 项目简介

  • 搭建视频点播服务器,根据不同用户对服务器的访问,实现视频的增删查改,可以根据类型进行查看播放的功能。

  • 用户管理

  • 两种用户,普通用户可以上传视频,修改 / 删除 自己上传 的视频;管理员用户 可以对服务器的所有视频进行增删查改;


1.3 实现内容

完成服务器端的程序业务功能的实现 以及前端访问界面 html 的编写,能够⽀持客户端中不同用户在浏览器针对服务器上的视频进行操作;


1.4 涉及技术 / 环境

  • 编程语言:C++
  • 操作系统:Linux unbuntu20.4
  • 数据库:MariaDB
  • 序列化 / 反序列胡:jsoncpp
  • 网络库:httplib
  • 网络协议: http
  • 前端:html/css/javascript AJAX

2. 整体架构

2.1 服务器功能

  • 针对客户端上传的视频文件以及封面图片进行备份存储。
  • 针对客户端上传的视频完成增删改查功能
  • ⽀持客户端浏览器进行视频的观看功能
  • 用户的基本管理(注册、登录、修改)
  • 根据不同用户对视频的管理权限不同

2.2 服务器结构

  • 用户处理模块:处理用户相关的操作。
  • 数据管理模块:负责针对客⼾端上传的视频信息进行管理。
  • 网络通信模块:搭建⽹络通信服务器,实现与客户端通信。
  • 业务处理模块:针对客⼾端的各个请求进⾏对应业务处理并响应结果。
  • 前端界面模块:完成前端浏览器上视频共享点播的各个 html 页面,在页面中⽀持增删改查以及观看功能,以及用户的相关操作

3. 前提步骤

3.1 思路分析

如何存储视频信息以及如何将视频发送给客户端?

显然这是首要的问题,对于视频信息的存储,这里利用 数据库 + 文件 的方法,即:

  • 数据库存储每个视频的相关信息,比如名称、简介、视频路径、封面路径等;对于视频的本体内容,数据库中存储的是其存储路径;
  • 根据后文实现的工具类,我们通过二进制方式将视频文件写入到一个新文件中,当客户端要获取内容时,同理将视频文件提取出来。

3.2 创建视频表

我们可以根据下面的语句 创建视频表(其他属性可以根据需求添加,比如视频时常等):
在这里插入图片描述

表结构如下:
在这里插入图片描述


4. 后端 基本功能实现(视频点播)

4.1 服务端工具类实现

工具类主要实现多个Util类,代码如下:

namespace aod
{class JsonUtil{public:static bool Serialize(const Json::Value& jv, std::string& body) {} // 将json对象序列化成字符串static bool Deserialize(const std::string& body, Json::Value& jv){} // 将字符串反序列化成json对象};// 文件功能类class FileUtil{private:std::string _file_name;public:FileUtil(std::string file_name) : _file_name(file_name) {}bool Exists() {} // 文件是否存在size_t FileSize() {} // 获取文件大小bool CreateDirectory() {} // 创建文件夹bool GetContent(std::string& content) {} // 将文件读取到content中bool SetContent(const std::string& content) {}; // 将Content设置到文件中};
}

4.2 日志输出类

为了方便在编码阶段进行功能测试,可以编写一个日志辅助类:Logger.hpp

#ifndef __M_LOG_H__
#define __M_LOG_H__#include <iostream>
#include <cstdio>
#include <ctime>#define DBG_LEVEL 0
#define INF_LEVEL 1
#define ERR_LEVEL 2
#define DEFAULT_LEVEL DBG_LEVEL#define LOG(level_str, level, format, ...) \if (level >= DEFAULT_LEVEL) { \time_t t = time(nullptr); \struct tm* ptm = localtime(&t); \char time_str[32]; \strftime(time_str, /*sizeof(time_str)*/31, "%H:%M:%S", ptm); \printf("[%s][%s][%s:%d]\t" format "\n", level_str, time_str, __FILE__, __LINE__, ##__VA_ARGS__); \} \

#define DLOG(format, ...) LOG("DBG", DBG_LEVEL, format, ##__VA_ARGS__)
#define ILOG(format, ...) LOG("INF", INF_LEVEL, format, ##__VA_ARGS__)
#define ELOG(format, ...) LOG("ERR", ERR_LEVEL, format, ##__VA_ARGS__)#endif

4.3 数据库/表 管理类

对于这一部分,主要实现两个类,即SqlManagerTableVideo类,分别用于进行数据库的相关操作以及视频表的相关操作:

  • SqlManager
    • MysqlInit() - 初始化Mysql句柄
    • MysqlDestory() - 关闭mysql
    • MysqlQuery() - 执行mysql语句
  • TableVideo
    • Insert() - 插入指定视频
    • Delete() - 删除指定视频
    • Update() - 更新视频信息
    • SelectAll - 查找所有视频
    • SelectById() - 查找单个指定视频
    • SelectByType() - 根据类型查找视频
    • SelectLikes() - 查找关键词相关视频
#ifndef SQL_MANAGER_HPP
#define SQL_MANAGER_HPP#include <iostream>
#include <mariadb/mysql.h>
#include <mutex>
#include <jsoncpp/json/json.h>
#include "../common/Logger.hpp"namespace aod
{#define HOSTNAME "localhost"#define USERNAME "user"#define PASSWORD "123456"#define DATABASE "aod_system"class SqlManager{public:static MYSQL* MysqlInit() // 初始化mysql句柄{MYSQL* mysql = mysql_init(nullptr);if (mysql == nullptr){DLOG("mysql_init failed: %s", mysql_error(mysql));return nullptr;}if(mysql_real_connect(mysql, HOSTNAME, USERNAME, PASSWORD, DATABASE, 0, nullptr, 0) == nullptr) {DLOG("mysql_real_connect failed: %s", mysql_error(mysql));mysql_close(mysql);return nullptr;}return mysql;}static bool MysqlDestroy(MYSQL* mysql){if(mysql == nullptr) {DLOG("mysql is nullptr when destroy");return true;}mysql_close(mysql);return true;}static bool MysqlQuery(MYSQL* mysql, const std::string& sql){if(mysql_query(mysql, sql.c_str()) != 0) {DLOG("mysql_query failed: %s | %s", sql.c_str(), mysql_error(mysql));return false;}return true;}};class TableVideo{private:MYSQL* _mysql;std::mutex _mutex;public:TableVideo() {_mysql = SqlManager::MysqlInit();if(_mysql == nullptr) {DLOG("TableVideo init failed");exit(-1);}}~TableVideo() {SqlManager::MysqlDestroy(_mysql);}bool Insert(const Json::Value& video) {} // 插入新的视频bool Delete(const int& video_id) {} // 删除视频// 更新视频信息bool Update(int video_id, const Json::Value& video) {} // 更新视频信息bool SelectAll(Json::Value* videos) {} // 选择全部视频bool SelectById(const int& video_id, Json::Value* video) {} // 根据id选择视频 / 选择一个指定视频bool SelectByType(const std::string video_type, Json::Value* videos) {} // 根据视频类型选择bool SelectLikes(const std::string& key, Json::Value* videos) {} // 模糊匹配,查找输入的关键词};
}#endif // SQLMANAGER_H

对于TableVideo,即具体的表操作,这里分别以InsertSelectAll举例:

对视频的相关的增删查改,首先先定义一个sql语句,再调用SqlManagerSqlQuery执行即可,这里我们利用resize、#define直接定义,与sprintf搭配将内容输出到sql语句。

bool Insert(const Json::Value& video) {if(video["name"].asString().empty()) {DLOG("video name is empty when insert");return false;}std::string sql;sql.resize(video["info"].asString().size() + 4096);#define INSERT_VIDEO "INSERT INTO tb_video VALUES (NULL, '%s', '%s', '%s', '%s', '%s');"sprintf(&sql[0], INSERT_VIDEO, video["name"].asCString(), video["info"].asCString(), video["video"].asCString(), video["image"].asCString(), video["type"].asCString());return SqlManager::MysqlQuery(_mysql, sql.c_str());
} // 插入新的视频

对于查找,首先通过MysqlQuery执行查询语句:

  • 后通过 MYSQL_RES* res = mysql_store_result(_mysql); 获取结果
  • 再利用size_t num_rows = mysql_num_rows(res); 获取结果行数
  • 循环 进行 MYSQL_ROW row = mysql_fetch_row(res); 获取每行的内容,将内容加载到Json::Value的变量video中,最后上层拿到输出型参数videos
bool SelectAll(Json::Value* videos) {#define SELECT_ALL "SELECT * FROM tb_video;"_mutex.lock(); // 保护 查询本地数据的操作过程if(!SqlManager::MysqlQuery(_mysql, SELECT_ALL)) {DLOG("mysql_query failed: %s | %s", SELECT_ALL, mysql_error(_mysql));_mutex.unlock();return false;}MYSQL_RES* res = mysql_store_result(_mysql);if(res == nullptr) {DLOG("mysql_store_result failed: %s", mysql_error(_mysql));_mutex.unlock();return false;}_mutex.unlock();size_t num_rows = mysql_num_rows(res);if(num_rows == 0) {DLOG("mysql_num_rows failed: %s", mysql_error(_mysql));mysql_free_result(res);return true;}for(int i = 0; i < num_rows; ++i) {MYSQL_ROW row = mysql_fetch_row(res);if(row == nullptr) {DLOG("mysql_fetch_row failed: %s", mysql_error(_mysql));mysql_free_result(res);return false;}Json::Value video;video["id"] = atoi(row[0]);video["name"] = row[1];video["info"] = row[2];video["video"] = row[3];video["image"] = row[4];video["type"] = row[5];videos->append(video);  }mysql_free_result(res);return true;}

4.4 服务器

对于服务器,首先是下面的代码,保留了每个成员函数的声明,其主要功能是:

通过RunModel 启动服务器,对于该函数:

  1. 初始化数据库、创建视频存储相关路径
  2. 设置静态资源根目录(方便客户端获取资源)
  3. 添加 [请求-响应] 的映射关系: _server.Post("/video", Insert);
  4. 启动服务,监听内容;
#include <iostream>
#include "httplib.h"
#include "../common/Logger.hpp"
#include "../common/Util.hpp"
#include "../common/sqlManager.hpp"
#include "../common/userManager.hpp"namespace aod {
#define WWWROOT "./wwwroot"
#define VIDEOROOT "/video/"
#define IMAGEROOT "/image/"TableVideo* _tb_video = nullptr;TableUser* _tb_user = nullptr;class Server {private:uint16_t _port;httplib::Server _server;// 设置错误响应static void set_error_response(httplib::Response& resp, int code, const std::string& reason);public:Server(uint16_t port) : _port(port) {}// 启动服务器并监听指定端口bool RunModel();private:// 注册用户处理函数static bool Register(const httplib::Request& req, httplib::Response& resp);// 用户登录处理函数static bool Login(const httplib::Request& req, httplib::Response& resp);// 用户登出处理函数static bool Logout(const httplib::Request& req, httplib::Response& resp);// 获取用户信息处理函数static void GetUserInfo(const httplib::Request& req, httplib::Response& resp);// 插入视频信息处理函数static void Insert(const httplib::Request& req, httplib::Response& resp);// 删除视频信息处理函数static void Delete(const httplib::Request& req, httplib::Response& resp);// 更新视频信息处理函数static bool Update(const httplib::Request& req, httplib::Response& resp);// 根据视频ID查询视频信息处理函数static void SelectById(const httplib::Request& req, httplib::Response& resp);// 查询所有视频信息处理函数static void SelectAll(const httplib::Request& req, httplib::Response& resp);// 根据视频类型查询视频信息处理函数static void SelectByType(const httplib::Request& req, httplib::Response& resp);};
}
bool RunModel()
{// 1. 初始化数据库_tb_video = new TableVideo();std::string wwwroot_path = WWWROOT;FileUtil(wwwroot_path).CreateDirectory();std::string video_path = wwwroot_path + VIDEOROOT;FileUtil(video_path).CreateDirectory();std::string image_path = wwwroot_path + IMAGEROOT;FileUtil(image_path).CreateDirectory();// 1.5 设置静态资源根目录_server.set_mount_point("/", wwwroot_path);// 2. 添加 请求-处理函数 映射关系_server.Post("/video", Insert);_server.Put("/video/(\\d+)", Update);_server.Delete("/video/(\\d+)", Delete);_server.Get("/video", SelectAll);_server.Get("/video/(\\d+)", SelectById);_server.Get("/video/type", SelectByType);_server.Post("/login", Login);_server.Post("/register", Register);_server.Get("/logout", Logout);// 3. 启动服务bool ret = _server.listen("0.0.0.0", _port);if (ret == false){ELOG("server start failed");return false;}return true;
}

5. 功能测试

完成上述功能后,对于视频点播的后端部分其实已经基本完毕,此时可以进行测试,这里使用Apifox工具进行测试:

这里以获取视频信息为例,当设置环境为当前服务器主机后,直接发送Get请求,可以正确获取到内容,对于其他功能,均测试成功;
在这里插入图片描述


6. 前端部分

前端部分这里首先对一个简单模板进行修改,得到一个css修饰后的html界面。对于html、css部分,这里不再概述(源码中看),重点在于前后端交互,是如何获取后端响应并显示到屏幕中的:

6.1 部分功能实现

① 视频播放 | 显示

对于主页index.html的vue.js,我们定义一个get_all_videos的方法,发送请求到客户端,将结果获取到videos数组中,最后调用该函数

<script>let app = new Vue({el: '#myapp',data: {author: "wqy",videos: []},methods: {get_all_videos: function () {$.ajax({url: "/video",type: "get",context: this,success: function (result, status, xhr) {this.videos = result;}})}}});app.get_allvideos();
</script>

如何获取视频展示到界面中?在get_all_videos中已经获取了所有的视频信息,此时在html中利用v-for遍历所有视频,并根据其内容进行展示:

在这里插入图片描述

<div class="row auto-clear">
<article class="col-lg-3 col-md-6 col-sm-4" v-for="video in videos"><!-- POST L size --><div class="post post-medium"><div class="thumbr"><a class="afterglow post-thumb" v-bind:href="'/video.html?id='+video.id"target="_blank"><span class="play-btn-border" title="Play"><iclass="fa fa-play-circle headline-round"aria-hidden="true"></i></span><div class="cactus-note ct-time font-size-1"><span>02:02</span></div><img class="img-responsive" v-bind:src="video.image" alt="#"v-cloak></a></div><div class="infor" style="display: flex; flex-direction: column;"><h4><a class="title" href="#" v-cloak>{{video.name}}</a></h4><span class="posts-txt" title="Posts from Channel" style="display: flex; align-items: center; justify-content: space-between;"><div style="display: flex; align-items: center;"><i class="fa fa-thumbs-up" aria-hidden="true"></i><span id="random-number"></span></div><div style="display: flex; align-items: center; margin-left: auto;"><img src="img/video-type.png" alt="视频类型图标" style="width: 16px; height: 16px; vertical-align: middle;"><span class="video-type">{{video.type}}</span></div></span></div></div>
</article>
</div>

当我们点击视频图标时,会创建并跳转到一个新的网页用于播放视频:

<a class="afterglow post-thumb" v-bind:href="'/video.html?id='+video.id" target="_blank"><span class="play-btn-border" title="Play"><i class="fa fa-play-circle headline-round" aria-hidden="true"></i></span><img class="img-responsive" v-bind:src="video.image" alt="#" v-cloak>
</a>

② 搜索功能

对于搜索模块,当输入内容后点击搜索,会转入到新网页用于展示搜索到的内容:

<div class="search-block"><form method="GET" action="search_results.html"><input type="search" name="search" placeholder="Search" required> <!-- 添加name属性 --><button type="submit" style="">Search</button> <!-- 添加提交按钮 --></form>
</div>

对于search.html, 各种控件与index.html没有差别,主要是js部分:

由于后端在SelectAll中有两种情况,即返回全部结果,或者根据给出的关键词进行匹配搜索,对于search.html,自然进行后者,所以在发送AJAX请求时,应该传递一个search参数,后获取服务器返回的结果;

<script>let searchApp = new Vue({el: '#searchApp',data: {searchResults: []},created() {// 获取 URL 中的搜索参数let urlParams = new URLSearchParams(window.location.search);let searchQuery = urlParams.get('search');// 发起 AJAX 请求$.ajax({url: '/video',  // 发向你的后端处理函数type: 'get',data: { search: searchQuery },  // 传递 search 参数context: this,success: function (result) {this.searchResults = result;  // 将结果存储并渲染}});}});
</script>

6.2 根据视频类型搜索

在这里插入图片描述

要实现具体的功能,我们编写下面的js代码:

  1. 数据categories 包含视频分类,selectedCategory 存储当前选中的分类,videosfilteredVideos 用于存储视频数据和过滤后的结果。

  2. 生命周期钩子created 方法在实例创建时调用,触发 fetchVideos 方法从服务器获取视频数据。

  3. 方法

    • fetchVideos 使用 AJAX 请求获取视频列表,并在成功后调用 filterVideos 进行初步过滤。
    • filterByCategory 方法根据用户选择的分类更新 selectedCategory,并调用 filterVideos 进行过滤。
    • filterVideos 根据 selectedCategory 更新 filteredVideos,如果选中“全部”,则显示所有视频,否则只显示匹配的分类视频。
<script>let categoryApp = new Vue({el: '#categoryApp',data: {categories: ['全部', '美食', '教育', '娱乐', '科技', '游戏', '动画', '舞蹈'],selectedCategory: '全部',videos: [],filteredVideos: []},created() {this.fetchVideos();},methods: {fetchVideos() {$.ajax({url: '/video',type: 'get',context: this,success: function (result) {this.videos = result;this.filterVideos();}});},filterByCategory(category) {this.selectedCategory = category;this.filterVideos();},filterVideos() {if (this.selectedCategory === '全部') {this.filteredVideos = this.videos;} else {this.filteredVideos = this.videos.filter(video => video.type === this.selectedCategory);}}}});
</script>

在总体的显示界面,通过遍历filterVideos,获取不同类别的内容并显示:

<section id="video-main"><h2 class="icon"><i class="fa fa-video-camera" aria-hidden="true"></i> {{ selectedCategory }}类别的视频</h2><div class="row"><div class="col-lg-9 col-md-12 col-sm-12"><div class="row auto-clear"><div v-if="filteredVideos.length === 0" class="no-results"><h3>没有找到相关内容</h3></div><article v-else class="col-lg-3 col-md-6 col-sm-4" v-for="video in filteredVideos":key="video.id"><div class="post post-medium"><div class="thumbr"><a class="afterglow post-thumb" :href="'/video.html?id='+video.id"target="_blank"><span class="play-btn-border" title="Play"><i class="fa fa-play-circle headline-round"aria-hidden="true"></i></span><div class="cactus-note ct-time font-size-1"><span>{{ video.duration }}</span></div><img class="img-responsive" :src="video.image" alt="#"></a></div><div class="infor" style="display: flex; flex-direction: column;"><h4><a class="title" href="#" v-cloak>{{ video.name }}</a></h4><span class="posts-txt"style="display: flex; align-items: center; justify-content: space-between;"><div style="display: flex; align-items: center;"><i class="fa fa-thumbs-up" aria-hidden="true"></i><span>{{ video.likes }}</span></div><div style="display: flex; align-items: center; margin-left: auto;"><img src="img/video-type.png" alt="视频类型图标"style="width: 16px; height: 16px;"><span class="video-type">{{ video.type }}</span></div></span></div></div></article></div></div><div class="col-lg-3 hidden-md col-sm-12 text-center top-sidebar"></div></div>
</section>

7. 功能扩充

用户管理类

我们根据之前对视频表的管理类同理编写一个用户管理类,下面是整体类的框架(基本函数的声明)

对于用户表,一般都应该有一个不可重复项,用于底层代码管理用户,可以是id、name、email等等;这里将email设为不可重复项,并依此管理用户:

class TableUser{private:std::mutex _mutex;MYSQL *_mysql;public:TableUser(){_mysql = SqlManager::MysqlInit();if (_mysql == nullptr){DLOG("mysql init failed");exit(-1);}}~TableUser(){SqlManager::MysqlDestroy(_mysql);}public:bool DeleteSessionID(const std::string &sessionID) {}// 删除会话idbool GetUserInfoBySessionID(const std::string &sessionID, Json::Value &user) {}// 根据会话id获取用户信息bool InsertSessionID(const std::string &email, const std::string &sessionID) {}// 加入某个用户的会话idbool UserInfoMatching(const Json::Value &user) {}// 判断用户信息是否匹配(登陆时用)bool isUserExist(const std::string &email) {} // 判断是否已经存在该用户(email不允许重复)bool Insert(const Json::Value &user){}// 将用户加入到用户表bool Delete(int userId) {}// 删除用户bool Update(const int& id, const Json::Value &user) {}// 更新指定用户信息};

对于该用户管理类的成员函数,下面是具体实现:

bool DeleteSessionID(const std::string &sessionID) {if(sessionID.empty()) {DLOG("Invalid sessionID when delete sessionID");return false;}#define DELETE_SESSION "UPDATE tb_user SET sessionID = '' WHERE sessionID = '%s';"std::string sql;sql.resize(256);sprintf(&sql[0], DELETE_SESSION, sessionID.c_str());return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 删除sessionIDbool GetUserInfoBySessionID(const std::string &sessionID, Json::Value &user) {if(sessionID.empty()) {DLOG("Invalid sessionID when get user info");return false;}#define GET_USER_INFO_BY_SESSION "SELECT * FROM tb_user WHERE sessionID = '%s';"std::string sql;sql.resize(256);sprintf(&sql[0], GET_USER_INFO_BY_SESSION, sessionID.c_str());if(SqlManager::MysqlQuery(_mysql, sql.c_str())== false) {DLOG("执行语句失败");return false;}MYSQL_RES *result = mysql_store_result(_mysql);if(result == nullptr) {DLOG("获取结果失败");return false;}MYSQL_ROW row = mysql_fetch_row(result);if(row == nullptr) {DLOG("获取结果为空");return false;}user["id"] = std::stoi(row[0]) ? std::stoi(row[0]) : -1;user["identity"] = row[1] ? row[1] : "未知";user["name"] = row[2] ? row[2] : "未知";user["email"] = row[3] ? row[3] : "未知";user["password"] = row[4] ? row[4] : "未知";user["sessionID"] = row[5] ? row[5] : "未知";mysql_free_result(result);return true;} // 通过sessionID获取用户信息bool InsertSessionID(const std::string &email, const std::string &sessionID) {if (email.empty() || sessionID.empty()) {DLOG("Invalid email or sessionID when insert sessionID");return false;}#define INSERT_SESSION "UPDATE tb_user SET sessionID = '%s' WHERE email = '%s';"std::string sql;sql.resize(256);sprintf(&sql[0], INSERT_SESSION, sessionID.c_str(), email.c_str());return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 加入会话idbool UserInfoMatching(const Json::Value &user) {std::string identity = user["identity"].asString();std::string name = user["name"].asString();std::string email = user["email"].asString();std::string password = user["password"].asString();if(identity.empty() || name.empty() || email.empty() || password.empty()) {DLOG("Invalid user info when check matching");return false;}#define USER_INFO_MATCHING "SELECT * FROM tb_user WHERE identity = '%s' AND name = '%s' AND email = '%s' AND password = '%s';"std::string sql;sql.resize(256);sprintf(&sql[0], USER_INFO_MATCHING, identity.c_str(), name.c_str(), email.c_str(), password.c_str());return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 判断用户信息是否匹配bool isUserExist(const std::string &email) {if (email.empty()) {DLOG("Invalid email when check user exist");return false;}   std::string sql;sql.resize(128);#define CHECK_USER "SELECT * FROM tb_user WHERE email = '%s';"sprintf(&sql[0], CHECK_USER, email.c_str());return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 判断是否已经存在该用户(email不允许重复)bool Insert(const Json::Value &user){if (user["name"].asString().empty() || user["email"].asString().empty() || user["password"].asString().empty()){DLOG("User name, email or password is empty when insert");return false;}std::string sql;sql.resize(1024);#define INSERT_USER "INSERT INTO tb_user (identity, name, email, password) VALUES ('%s', '%s', '%s', '%s');"sprintf(&sql[0], INSERT_USER, user["identity"].asCString(),user["name"].asCString(),user["email"].asCString(),user["password"].asCString());return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 插入用户到表中bool Delete(int userId) {if (userId <= 0) {DLOG("Invalid user ID when delete");return false;}std::string sql;sql.resize(128);#define DELETE_USER "DELETE FROM tb_user WHERE id = %d;"sprintf(&sql[0], DELETE_USER, userId);return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 从表中删除指定用户bool Update(const int& id, const Json::Value &user) {if(user["name"].asString().empty()) {DLOG("User name is empty when update");return false;}std::string sql;sql.resize(1024);#define UPDATE_USER "UPDATE tb_user SET name = '%s', email = '%s', password = '%s' WHERE id = %d;"sprintf(&sql[0], UPDATE_USER, user["name"].asCString(), user["email"].asCString(), user["password"].asCString(), id);return SqlManager::MysqlQuery(_mysql, sql.c_str());} // 更新指定用户信息

再完成了用户管理类的基本操作后,此时可以编写服务器对客户端发来的用户相关请求的处理函数:

首先在Util.hpp 中 实现一个功能:生成会话id(用于登录用户后保存会话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();} // 生成32位随机session_id

下面是服务器的处理函数:

 // 注册
static bool Register(const httplib::Request &req, httplib::Response &resp) {}// 登录用户
static bool Login(const httplib::Request &req, httplib::Response &resp) {}// 登出用户
static bool Logout(const httplib::Request &req, httplib::Response &resp) {}// 注销用户
static bool DeleteUser(const httplib::Request &req, httplib::Response &resp) {}// 获取当前登录用户信息
static void GetUserInfo(const httplib::Request &req, httplib::Response &resp) {}

具体实现类似之前服务器对视频请求的处理,这里仅以Login进行举例:

static bool Login(const httplib::Request &req, httplib::Response &resp) {DLOG("recive login request");if(!req.has_file("identity") || !req.has_file("name") || !req.has_file("email") || !req.has_file("password")) {DLOG("login failed, lack of necessary information");set_error_response(resp, 500, "登录失败,缺少必要信息");return false;}Json::Value user_json;user_json["name"] = req.get_param_value("name");user_json["email"] = req.get_param_value("email");user_json["password"] = req.get_param_value("password");user_json["identity"] = req.get_param_value("identity");if(_tb_user->UserInfoMatching(user_json) == false) {DLOG("login failed, user information does not match");set_error_response(resp, 500, "登录失败,用户信息不匹配");return false;}std::string session_id = SessionUtil::generate_session_id();if(_tb_user->InsertSessionID(user_json["email"].asString(), session_id) == false) {DLOG("login failed, insert session id failed");set_error_response(resp, 500, "登录失败,插入session id失败");return false;}resp.status = 200;resp.body = R"({"result": true, "reason": "登录成功", "status": "success"})";resp.set_header("Content-Type", "application/json;");return true;
} // 登录用户

前端部分(待补充…)

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

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

相关文章

前端开发之代理模式

介绍 代理模式是一种结构型设计模式&#xff0c;它通过为一个对象提供一个代理对象来控制对该对象的访问。代理对象可以在访问真实对象之前或之后添加一些额外的操作。 class RealImg {fileName: string;constructor(fileName: string) {this.fileName fileName;}disPlay()…

ValueError: Out of range float values are not JSON compliant

可能原因一 可能原因二 数据里面有NaN

优化java中 HashMap 的容量](capacity值)

我们很多人都知道&#xff0c;分配比我们所需更多的内存可能会对应用程序的性能产生负面影响。因此&#xff0c;使用带有容量的构造函数创建列表可能会产生很大的不同。 但是&#xff0c;使用Maps时&#xff0c;这个优化步骤可能不是那么简单。在本文中&#xff0c;我们将学习…

Django 基础之启动命令和启动配置修改

Django启动 django启动一般可以通过ide或者命令启动 ide启动&#xff1a; 启动命令&#xff1a; python manage.py runserver该命令后续可以增加参数&#xff0c;如&#xff1a; python manage.py runserver 8081 python manage.py runserver 127.0.0.1:8082 注意&#xff1…

PDF转换器哪个好?这5款PDF工具值得推荐

PDF转换器哪个好&#xff1f;选择一款优质的PDF转换器&#xff0c;能够极大地提升我们的工作效率与灵活性。它不仅能轻松实现PDF文件与Word、Excel、PPT等多种格式间的互转&#xff0c;还支持图片、TXT等多种格式的转换&#xff0c;满足多样化的办公与学习需求。此外&#xff0…

南卡首款耳夹开放式耳机,舒适与音质双双达行业峰值,再次“颠覆”行业!

近日&#xff0c;南卡Ultra夹耳式蓝牙耳机的正式上市&#xff0c;再次在蓝牙耳机圈内掀起波澜。这款耳机以其超舒适的夹耳式设计和卓越音质&#xff0c;为用户带来了全新的音乐体验&#xff0c;有望重新定义夹耳式耳机的市场标准。 南卡品牌背后有着强大的研发实力和丰富的行业…

一文读懂Service以及实践攻略

一文读懂Service以及实践攻略 目录 1 一文读懂 Kubernetes Service 以及实践攻略 1.1 1. 什么是 Service&#xff1f; 1.1.1 为什么需要 Service&#xff1f; 1.2 2. Service 的工作原理 1.2.1 核心概念1.2.2 流量转发过程 1.3 3. Service 的几种类型及应用场景 2 实践&#…

网络与信息安全工程师(工信部教育与考试中心)

在当今数字化时代&#xff0c;大量的敏感信息与业务流程在网络上传输和处理&#xff0c;使得网络与信息安全成为保障企业运营、政务管理以及金融交易等关键领域不可忽视的一环。 因此&#xff0c;对网络安全专家的需求日益增长。 例如&#xff0c;金融机构、大型电信运营商以…

云专线和虚拟专线是什么?两者有什么区别?

云专线和虚拟专线是两种用于企业网络连接的技术方案&#xff0c;它们各自有不同的特点和适用场景。下面将分别介绍这两种技术&#xff0c;并指出它们之间的主要区别。 云专线 云专线是一种物理的或逻辑的专用线路&#xff0c;它直接连接企业的本地数据中心或分支机构与云端服务…

为什么美联储降息和我国刺激措施可能提振铜价

美联储降低利率通常对铜价产生积极影响。这主要是由于利率与美元汇率之间的关系。当美联储降息时&#xff0c;往往会使美元对其他货币贬值。 由于全球市场上的铜价是以美元计价的&#xff0c;美元走弱会使用其他货币购买的金属价格更便宜。这可能刺激来自国际买家的需求&#x…

RTE 大会报名丨AI 时代新基建:云边端架构和 AI Infra ,RTE2024 技术专场第二弹!

所有 AI Infra 都在探寻规格和性能的最佳平衡&#xff0c;如何构建高可用的云边端协同架构&#xff1f; 语音 AI 实现 human-like 的最后一步是什么&#xff1f; AI 视频的爆炸增长&#xff0c;给新一代编解码技术提出了什么新挑战&#xff1f; 当大模型进化到实时多模态&am…

【深度学习】(7)--神经网络之保存最优模型

文章目录 保存最优模型一、两种保存方法1. 保存模型参数2. 保存完整模型 二、迭代模型 总结 保存最优模型 我们在迭代模型训练时&#xff0c;随着次数初始的增多&#xff0c;模型的准确率会逐渐的上升&#xff0c;但是同时也随着迭代次数越来越多&#xff0c;由于模型会开始学…

亲测好用,吐血整理 ChatGPT 3.5/4.0新手使用手册~

都知道ChatGPT很强大&#xff0c;聊聊天、写论文、搞翻译、写代码、写文案、审合同等等&#xff0c;无所不能~ 那么到底怎么使用呢&#xff1f;其实很简单了&#xff0c;国内AI产品发展也很快&#xff0c;很多都很好用了~ 我一直在用&#xff0c;建议收藏下来~ 有最先进、最…

PHP程序如何实现限制一台电脑登录?

PHP程序如何实现限制一台电脑登录&#xff1f; 可以使用以下几种方法&#xff1a; 1. IP地址限制&#xff1a;在PHP中&#xff0c;可以通过获取客户端的IP地址&#xff0c;然后与允许登录的IP地址列表进行比对。如果客户端的IP地址不在列表中&#xff0c;就禁止登录。 “php $…

U盘未格式化之谜:数据丢失与恢复全攻略

U盘未格式化问题描述 在日常的数字生活中&#xff0c;U盘作为便携的数据存储设备&#xff0c;扮演着不可或缺的角色。然而&#xff0c;不少用户都曾遭遇过这样的困境&#xff1a;当尝试访问未进行格式化操作的U盘时&#xff0c;却发现原本存储的文件竟然不翼而飞&#xff0c;U…

【机器学习】音乐生成——AI如何创作个性化音乐与配乐

我的主页&#xff1a;2的n次方_ 音乐是人类文化的重要组成部分&#xff0c;它具有极强的情感表达和艺术价值。近年来&#xff0c;随着人工智能技术的飞速发展&#xff0c;AI已经能够自动生成音乐&#xff0c;甚至根据用户需求创作个性化配乐。AI生成音乐的应用场景广泛&…

35岁java转大模型笔记,大模型智能体(LLM Agent)学习笔记

\1. 什么是大模型&#xff1f; 大模型对应的英文是Large Language Model&#xff08;LLM&#xff09;&#xff0c;即大语言模型&#xff0c;简称大模型。技术层面讲&#xff0c;大模型是一种基于深度学习技术的机器学习模型。 为什么叫大模型呢&#xff1f;它是相对于小模型而…

代码随想录算法训练营第十四天|递归 226.翻转二叉树 101. 对称二叉树 104.二叉树的最大深度 111.二叉树的最小深度

226.翻转二叉树 翻转一棵二叉树。 思路&#xff1a; 在这里需要注意的是&#xff0c;在递归的时候唯独中序遍历是不可用的&#xff0c;这是因为先对左子树进行了反转&#xff0c;又对自身进行了反转&#xff0c;对自身反转后原本的左子树变成了右子树&#xff0c;如果此时又轮…

流媒体服务软件-LiveNVR channeltree 未授权访问

0x01 产品描述&#xff1a; LiveNVR 能够通过简单的网络摄像机通道配置&#xff0c;将传统监控行业里面的高清网络摄像机 IPCamera、NVR 等具有 RTSP/Onvif 协议输出的设备接入到 LiveNVR&#xff0c;LiveNVR 能够将这些设备源的音/视频数据进行采集、转换、输出&#xff0c;进…

MySQL 5.8 Performance Schema 配置详解

MySQL 5.8 Performance Schema 配置详解 MySQL 的 Performance Schema 是一个用于监控和优化数据库性能的子系统&#xff0c;专门用来收集 MySQL 服务器的运行情况和性能指标。它的核心原理是通过“生产者”和“消费者”的概念来采集和存储数据库中的事件信息&#xff0c;帮助…