【项目】基于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 数据库/表 管理类
对于这一部分,主要实现两个类,即SqlManager
与TableVideo
类,分别用于进行数据库的相关操作以及视频表的相关操作:
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
,即具体的表操作,这里分别以Insert
与SelectAll
举例:
对视频的相关的增删查改,首先先定义一个sql语句,再调用SqlManager
的SqlQuery
执行即可,这里我们利用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
启动服务器,对于该函数:
- 初始化数据库、创建视频存储相关路径
- 设置静态资源根目录(方便客户端获取资源)
- 添加 [请求-响应] 的映射关系:
_server.Post("/video", Insert);
- 启动服务,监听内容;
#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"><i
class="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代码:
-
数据:
categories
包含视频分类,selectedCategory
存储当前选中的分类,videos
和filteredVideos
用于存储视频数据和过滤后的结果。 -
生命周期钩子:
created
方法在实例创建时调用,触发fetchVideos
方法从服务器获取视频数据。 -
方法:
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) {}
// 删除会话id
bool GetUserInfoBySessionID(const std::string &sessionID, Json::Value &user) {}
// 根据会话id获取用户信息
bool InsertSessionID(const std::string &email, const std::string &sessionID) {}
// 加入某个用户的会话id
bool 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());
} // 删除sessionID
bool 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());
} // 加入会话id
bool 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;
} // 登录用户