当前位置: 首页 > news >正文

【项目实战】C++视频共享点播系统

目录

一、项目介绍

1.1 对视频共享点播系统的认识

1.2服务端程序负责功能

1.3 服务端功能模块划分

1.4 项目界面演示

1.5预备知识

二.环境搭建

2.1 安装 Jsoncpp 库

2.1.1 使用jsoncpp

2.2 引入httplib库

2.2.1 安装Git(如果你的系统尚未安装Git)

2.2.2 克隆仓库

2.2.3 使用httplib

2.3 Mysql 数据库及开发包安装

2.3.1 安装MySQL服务器

2.3.2 安装MySQL客户端

2.3.3 安全配置(安装后建议执行)

2.3.4 启动、停止、重启 登录 MySQL服务

三.项⽬实现

3.1服务端⼯具类实现

3.1.1⽂件实⽤⼯具类设计

3.1.2 Json 实⽤⼯具类设计

3.2 服务端数据管理模块

3.2.1 视频数据表的设计

3.2.2 数据管理类设计

3.3 服务端业务处理模块

3.3.1 ⽹络通信接⼝设计

3.3.2 业务处理模块类的设计

3.3.2 最终合并调试

3.4 前端界⾯模块实现

3.4.1 前端视频展示⻚⾯

3.4.2 前端视频观看页面的实现

四.项⽬总结


一、项目介绍

1.1 对视频共享点播系统的认识

  • 搭建视频共享点播服务器,可以让所有人通过浏览器访问服务器,实现视频的上传观看,以及管理并播放的功能。
  • 主要是完成服务器端的程序业务功能的实现以及前端访问界面 html 的编写,能够支持客户端浏览器针对服务器上的所有视频进行操作。

1.2服务端程序负责功能

  1. 针对客⼾端上传的视频⽂件以及封⾯图⽚进⾏备份存储。
  2. 针对客⼾端上传的视频完成增删改查功能
  3. ⽀持客⼾端浏览器进⾏视频的观看功能

1.3 服务端功能模块划分

该视频点播系统基本上包含四个模块:数据管理、网络通信、业务处理、前端界面

  1. 数据管理模块:负责针对客户端上传的视频信息进行管理。
  2. 网络通信模块:搭建网络通信服务器,实现与客户端通信。
  3. 业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。
  4. 前端界面模块:前端浏览器上视频共享点播的各个 html 页面,在页面中支持增删改查以及观看功能

1.4 项目界面演示

项目整体有俩个页面,分别为主界面和观看界面

主界面主要    用于 视频展示、视频上传、视频搜索

观看界面主要用于 视频观看、视频信息修改、视频删除

以下为项目页面功能展示

1.5预备知识

认识 JsonCpp

http://t.csdnimg.cn/lsz2oicon-default.png?t=N7T8http://t.csdnimg.cn/lsz2o

认识 MySQL数据库的API

http://t.csdnimg.cn/hpxg1icon-default.png?t=N7T8http://t.csdnimg.cn/hpxg1

认识 httplib

http://t.csdnimg.cn/SvQJzicon-default.png?t=N7T8http://t.csdnimg.cn/SvQJz

二.环境搭建

我的服务器Ubuntu22.04

2.1 安装 Jsoncpp

sudo apt update
sudo apt -y install libjsoncpp25
2.1.1 使用jsoncpp
#include <jsoncpp/json/json.h>

2.2 引入httplib库

2.2.1 安装Git(如果你的系统尚未安装Git)
sudo apt update
sudo apt install git
2.2.2 克隆仓库

打开终端,使用cd命令切换到你想要存放cpp-httplib库的目录,然后运行以下命令来克隆仓库:

git clone https://github.com/yhirose/cpp-httplib.git
2.2.3 使用httplib

直接在你的C++代码中包含httplib.h头文件。你可以将其复制到你的项目中合适的位置,然后在源文件中包含它

#include "httplib.h"

2.3 Mysql 数据库及开发包安装

2.3.1 安装MySQL服务器
sudo apt update //更新本地包数据库
sudo apt install mysql-server//安装最新版本的MySQL
2.3.2 安装MySQL客户端
sudo apt install mysql-client
2.3.3 安全配置(安装后建议执行)
sudo mysql_secure_installation//按照提示设置root用户的密码,移除匿名用户,禁止root用户远程登录等
sudo systemctl status mysql //确认MySQL服务状态
2.3.4 启动、停止、重启 登录 MySQL服务
sudo systemctl start mysql //启动服务
sudo systemctl stop mysql  //停止服务
sudo systemctl restart mysql//重启服务mysql -u root -p 使用root用户登录

三.项⽬实现

3.1服务端⼯具类实现

3.1.1⽂件实⽤⼯具类设计

在视频点播系统中因为涉及到⽂件上传,需要对上传的⽂件进⾏备份存储,因此⾸先设计封装⽂件操作类,这个类封装完毕之后,则在任意模块中对⽂件进⾏操作时都将变的简单化

功能:

  1. 构造函数 FileUtil(const std::string name)

    • 接收一个字符串参数 name,表示文件的路径和名称,并将这个值赋给私有成员变量 _name
  2. Exists - 检查文件是否存在:

    • 使用 access 函数和 F_OK 标志来检查文件是否存在。
    • 如果 access 函数返回非0值,表示文件不存在,函数输出错误信息并返回 false
    • 如果文件存在,返回 true
  3. Size - 获取文件大小:

    • 首先检查文件是否存在,如果不存在则返回0。
    • 使用 stat 函数获取文件的属性,并将文件大小存储在 st.st_size 中。
    • 如果 stat 函数返回非0值,表示获取文件属性失败,函数输出错误信息并返回0。
    • 成功获取属性后,返回文件大小。
  4. GetContent - 读取文件内容:

    • 打开文件以二进制模式读取。
    • 如果文件无法打开,输出错误信息并返回 false
    • 使用 Size 函数获取文件大小,并根据大小调整 body 字符串的容量。
    • 读取文件内容到 body 字符串中。
    • 如果读取失败,输出错误信息,关闭文件,并返回 false
    • 成功读取后,关闭文件并返回 true
  5. SetContent - 向文件写入数据

    • 打开文件以二进制模式写入。
    • 如果文件无法打开,输出错误信息并返回 false
    • 使用 write 函数将 body 字符串的内容写入文件。
    • 如果写入失败,输出错误信息,关闭文件,并返回 false
    • 成功写入后,关闭文件并返回 true
  6. CreateDirectory - 创建目录:

    • 首先检查目录是否存在,如果存在则直接返回 true
    • 使用 mkdir 函数创建目录,如果创建成功则返回 true
  7. 注意,这个类使用了C++标准库中的文件操作函数,如 accessstatifstreamofstreammkdir。这些函数需要包含相应的头文件,例如 <fstream>、<sys/stat.h> 和 <unistd.h>。
class FileUtil{private:std::string _name;//文件路径名称public:FileUtil(const std::string name):_name(name){}// 判断当前文件是否存在bool Exists(){//access的F_OK专门用于检测文件是否存在--- 存在则返回0int ret = access(_name.c_str(), F_OK);if (ret != 0) {std::cout << "文件不存在\n";return false;}return true;}// 获取文件大小size_t Size() {if (this->Exists() == false) {return 0;}struct stat st;//stat接口用于获取文件属性,其中 st_size就是文件大小成员int ret = stat(_name.c_str(), &st);if (ret != 0) {std::cout << "获取文件属性失败\n";return 0;}return st.st_size;}// 读取文件数据到body中bool GetContent(std::string *body) {std::ifstream ifs;ifs.open(_name, std::ios::binary);if (ifs.is_open() == false) {std::cout << "文件打开失败\n";return false;}size_t flen = this->Size();body->resize(flen);ifs.read(&(*body)[0], flen);if (ifs.good() == false) {std::cout << "读取文件内容失败\n";ifs.close();return false;}ifs.close();return true;}// 像文件写入数据bool SetContent(const std::string &body) {std::ofstream ofs;ofs.open(_name, std::ios::binary);if (ofs.is_open() == false) {std::cout << "文件打开失败\n";return false;}ofs.write(body.c_str(), body.size());if (ofs.good() == false) {std::cout << "写入文件内容失败\n";ofs.close();return false;}ofs.close();return true;}//针对目录时创建目录bool CreateDirectory() {if (this->Exists()) {return true;}mkdir(_name.c_str(), 0777);return true;}};
3.1.2 Json 实⽤⼯具类设计

功能:

  1. Serialize - 序列化函数:

    • 这个函数接受一个Json::Value类型的参数value和一个指向std::string的指针body
    • 使用Json::StreamWriterBuilder创建一个Json::StreamWriter对象,用于将Json::Value对象写入到字符串流中。
    • 通过write方法将value序列化到std::stringstream对象ss中。
    • 如果序列化失败(write方法返回非0值),则输出错误信息并返回false
    • 成功序列化后,将字符串流的内容赋值给*body,并返回true
  2. UnSerialize - 反序列化函数:

    • 这个函数接受一个std::string类型的参数body和一个指向Json::Value的指针value
    • 使用Json::CharReaderBuilder创建一个Json::CharReader对象,用于从字符串中解析JSON数据。
    • 使用parse方法尝试解析body中的JSON数据,并将结果存储在value指向的对象中。
    • 如果解析失败(parse方法返回false),则输出错误信息并返回false
    • 成功解析后,返回true
class JsonUtil{public:// 序列化static bool Serialize(const Json::Value &value, std::string *body) {Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss; int ret = sw->write(value, &ss);if (ret != 0) {std::cout << "序列化失败\n";return false;}   *body = ss.str();return true;}// 反序列化static bool UnSerialize(const std::string &body, Json::Value *value) {Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), value, &err);if (ret == false) {std::cout << "反序列化失败\n";return false;}return true;}};

3.2 服务端数据管理模块

3.2.1 视频数据表的设计
在视频共享点播系统中,视频数据和图⽚数据都存储在⽂件中,⽽我们需要在数据库中管理⽤⼾上传 的每个视频信息。只是完成⼀个简单的视频信息表
MySQL 创建数据库
create database aod_system;
使用aod_system 数据库
use aod_system;
建表
create table  tb_video(id int primary key auto_increment comment '视频ID',name varchar(32) comment '视频名称',info text comment '视频描述',video varchar(256) comment '视频⽂件url,加上静态资源根⽬录就是实际存储路径',image varchar(256) comment '封⾯图⽚⽂件url,加上静态资源根⽬录就是实际存储路径'
);
3.2.2 数据管理类设计
  • 数据管理模块负责统⼀对于数据库中数据的增删改查管理,其他所有模块要进⾏数据的操作都通过数 据管理模块完成。
  • 然⽽,数据库中有可能存在很多张表,每张表中数据⼜有不同,要进⾏的数据操也各不相同,因此咱 们将数据的操作分摊到每⼀张表上,为每⼀张表中的数据操作都设计⼀个类,通过类实例化的对象来 访问这张数据库表中的数据,这样的话当我们要访问哪张表的时候,使⽤哪个类实例化的对象即可。
  • 视频信息在接⼝之间的 传递因为字段数量可能很多,因此使⽤ Json::Value 对象进⾏传递

功能:

  1. 防止头文件重复包含:使用 #ifndef#define#endif 宏来防止头文件被重复包含。

  2. 包含头文件:包含自定义的 util.hpp 头文件和一些标准库头文件,如 <cstdlib><mutex><mysql/mysql.h>

  3. 命名空间:定义了一个名为 aod 的命名空间,用于封装相关的数据库操作。

  4. 宏定义:定义了一些宏,如数据库连接信息 HOSTUSERPASSNAME

  5. MysqlInit 函数:用于初始化MySQL连接,包括创建MySQL句柄、连接到服务器和设置字符集。

  6. MysqlDestroy 函数:用于销毁MySQL连接,关闭句柄。

  7. MysqlQuery 函数:用于执行SQL语句,并检查执行是否成功。

  8. TableVideo 类

    • 私有成员 _mysql:用于存储MySQL句柄。
    • 私有成员 _mutex:用于多线程同步,保证线程安全。
    • 构造函数:初始化MySQL句柄。
    • 析构函数:销毁MySQL句柄。
    • 成员函数 Insert:向数据库表 tb_video 插入视频信息。
    • 成员函数 Update:根据视频ID更新视频信息。
    • 成员函数 Delete:根据视频ID删除视频信息。
    • 成员函数 SelectAll:查询所有视频信息,并将结果存储在 Json::Value 类型的参数中。
    • 成员函数 SelectOne:根据视频ID查询单个视频信息。
    • 成员函数 SelectLike:根据名称关键字进行模糊匹配查询视频信息。
  9. 错误处理:在执行数据库操作时,如果遇到错误,会打印错误信息。

  10. JSON操作:使用 Json::Value 类型来处理JSON数据,这需要包含JSONCPP库的相关头文件。

  11. 线程安全:在 TableVideo 类中使用 std::mutex 来保证多线程环境下对数据库操作的线程安全。

  12. 资源管理:在 SelectAllSelectOneSelectLike 函数中,使用 mysql_store_result 保存查询结果,并在操作完成后使用 mysql_free_result 释放结果集。

#ifndef __MY_DATA__ //防止头文件重复包含
#define __MY_DATA__
#include "util.hpp"
#include <cstdlib>
#include <mutex>
#include <mysql/mysql.h>namespace aod{
#define HOST "127.0.0.1"
#define USER "root"
#define PASS "mima"
#define NAME "aod_system"//mysql的封装//初始化static MYSQL *MysqlInit() {//1初始化句柄MYSQL *mysql = mysql_init(NULL);if (mysql == NULL) {std::cout << "初始化 MySQL 实例失败!\n";return NULL;}//2 链接服务器if (mysql_real_connect(mysql, HOST, USER, PASS, NAME, 0, NULL, 0) == NULL) {std::cout << "连接MySQL服务器失败!\n";mysql_close(mysql);return NULL;}// 3设置客户端字符集mysql_set_character_set(mysql, "utf8");return mysql;}//销毁static void MysqlDestroy(MYSQL *mysql) {if (mysql != NULL) {mysql_close(mysql);}return;}//执行语句static bool MysqlQuery(MYSQL *mysql, const std::string &sql) {int ret = mysql_query(mysql, sql.c_str());if (ret != 0) {std::cout<<"执行"<<sql<<"语句失败"<<std::endl;std::cout<<"错误信息"<<mysql_errno(mysql) <<std::endl;return false;}return true;}class TableVideo{private:MYSQL *_mysql;		// ⼀个对象就是⼀个客⼾端,管理⼀张表std::mutex _mutex;	// 防备操作对象在多线程中使⽤存在的线程安全 问题public:// 完成mysql句柄初始化TableVideo() {_mysql = MysqlInit();if (_mysql == NULL) {exit(-1);}}// 释放msyql操作句柄~TableVideo() {MysqlDestroy(_mysql);}// 新增-传⼊视频信息bool Insert(const Json::Value &video) {//id name视频名 info视频简介 video视频路径 image图片路径std::string sql;sql.resize(4096 + video["info"].asString().size());//防止简介过长#define INSERT_VIDEO "insert tb_video values(null, '%s', '%s', '%s', '%s');"//可以对各个数据增加校验,去增加各种规则,比如视频名不能没有 等等等if (video["name"].asString().size() == 0) {return false;}//要完成的细致的话需要对各个数据进行校验,因为不校验直接用就有可能出问题sprintf(&sql[0], INSERT_VIDEO, video["name"].asCString(),video["info"].asCString(), video["video"].asCString(),video["image"].asCString());return MysqlQuery(_mysql, sql);}// 修改-传⼊视频id,和信息bool Update(int video_id, const Json::Value &video) {std::string sql;sql.resize(4096 + video["info"].asString().size());//防止简介过长#define UPDATE_VIDEO "update tb_video set name='%s', info='%s' where id=%d;"sprintf(&sql[0], UPDATE_VIDEO, video["name"].asCString(),video["info"].asCString(), video_id);return MysqlQuery(_mysql, sql);}// 删除-传⼊视频IDbool Delete(int video_id) {#define DELETE_VIDEO "delete from tb_video where id=%d;"char sql[1024] = {0};sprintf(sql, DELETE_VIDEO, video_id);return MysqlQuery(_mysql, sql);}// 查询所有--输出所有视频信息bool SelectAll(Json::Value *videos) {#define SELECTALL_VIDEO "select * from tb_video;"_mutex.lock();//-----lock start 保护查询与保存结果到本地的过程bool ret = MysqlQuery(_mysql, SELECTALL_VIDEO);if (ret == false) {_mutex.unlock();return false;}//保存查询结果集MYSQL_RES *res = mysql_store_result(_mysql);if (res == NULL) {std::cout << "MySQL存储结果失败!\n";_mutex.unlock();return false;}_mutex.unlock();//------lock end//查看结果有多少行,并传入vidows中int num_rows = mysql_num_rows(res);for (int i = 0; i < num_rows; i++) {MYSQL_ROW row = mysql_fetch_row(res);//取出一条结果Json::Value video;video["id"] = atoi(row[0]);video["name"] = row[1];video["info"] = row[2];video["video"] = row[3];video["image"] = row[4];videos->append(video);}mysql_free_result(res);//释放结果集return true;}// 查询单个-输⼊视频id, 输出信息bool SelectOne(int video_id, Json::Value *video) {#define SELECTONE_VIDEO "select * from tb_video where id=%d;"char sql[1024] = {0};sprintf(sql, SELECTONE_VIDEO, video_id);_mutex.lock();//-----lock start 保护查询与保存结果到本地的过程bool ret = MysqlQuery(_mysql, sql);if (ret == false) {_mutex.unlock();return false;}//保存查询结果集MYSQL_RES *res = mysql_store_result(_mysql);if (res == NULL) {std::cout << "MySQL存储结果失败\n";_mutex.unlock();return false;}_mutex.unlock();//------lock end//查看结果有多少行,并传入vidows中int num_rows = mysql_num_rows(res);if (num_rows != 1) {std::cout << "没有数据!\n";mysql_free_result(res);return false;}MYSQL_ROW row = mysql_fetch_row(res);//取出一条结果(*video)["id"] = video_id;(*video)["name"] = row[1];(*video)["info"] = row[2];(*video)["video"] = row[3];(*video)["image"] = row[4];mysql_free_result(res);//释放结果集return true;}// 模糊匹配 - 输⼊名称关键字,输出视频信息bool SelectLike(const std::string &key, Json::Value *videos){#define SELECTLIKE_VIDEO "select * from tb_video where name like '%%%s%%';"char sql[1024] = {0};sprintf(sql, SELECTLIKE_VIDEO, key.c_str());_mutex.lock();//-----lock start 保护查询与保存结果到本地的过程bool ret = MysqlQuery(_mysql, sql);if (ret == false) {_mutex.unlock();return false;}MYSQL_RES *res = mysql_store_result(_mysql);if (res == NULL) {std::cout << "MySQL存储结果失败!\n";_mutex.unlock();return false;}_mutex.unlock();//------lock endint num_rows = mysql_num_rows(res);for (int i = 0; i < num_rows; i++) {MYSQL_ROW row = mysql_fetch_row(res);Json::Value video;video["id"] = atoi(row[0]);video["name"] = row[1];video["info"] = row[2];video["video"] = row[3];video["image"] = row[4];videos->append(video);}mysql_free_result(res);return true;}};
}#endif

3.3 服务端业务处理模块

3.3.1 ⽹络通信接⼝设计

认识 rest设计风格

http://t.csdnimg.cn/wxhtmicon-default.png?t=N7T8http://t.csdnimg.cn/wxhtm

获取所有视频信息
请求:
GET /video HTTP/1.1
响应:
HTTP/1.1 200 OK
[{"info": "好电影","id": 1,"image": "/img/thumbs/mysql.png","name": "Mysql注意事项","video": "/video/movie.mp4",},{"info": "好电影","id": 2,"image": "/img/thumbs/linux.png","name": "Linux注意事项","video": "/video/movie.mp4",}
]
搜索指定关键字名称视频信息
请求:
GET /video?search="Mysql" HTTP/1.1
响应:
HTTP/1.1 200 OK
[{"info": "好电影","id": 1,"image": "/img/thumbs/mysql.png","name": "Mysql注意事项","video": "/video/movie.mp4",}
]
获取指定视频信息
请求:
GET /video/1 HTTP/1.1
响应:
HTTP/1.1 200 OK
[{"info": "好电影","id": 1,"image": "/img/thumbs/mysql.png","name": "Mysql注意事项","video": "/video/movie.mp4",}
]
删除指定视频信息
请求:
DELETE /video/1 HTTP/1.1
响应:
HTTP/1.1 200 OK
修改指定视频信息
请求:
PUT /video/1 HTTP/1.1
{"info": "这是⼀个⾮常好的教学视频,深⼊浅出,引⼈深思","id": 1,"image": "/img/thumbs/mysql.png","name": "Mysql注意事项","video": "/video/movie.mp4",
}
响应:
HTTP/1.1 200 OK
上传视频信息以及⽂件
因为上传视频信息的时候,会携带有视频⽂件和封⾯图⽚的⽂件上传,⽽这些⽂件数据都是⼆进制 的,⽤ json 不好传输,因此在这⾥使⽤传统的 http 上传⽂件请求格式,⽽并没有使⽤ restful ⻛格。
请求:
POST /video HTTP/1.1
Content-Type: multipart/form-data; boundary=----
WebKitFormBoundarydsrFiETIzKETHWkn
------WebKitFormBoundarydsrFiETIzKETHWkn
Content-Disposition: form-data; name="name"
Xhsell连接事项,也就是视频名称
------WebKitFormBoundarydsrFiETIzKETHWkn
Content-Disposition: form-data; name="info"
⼀部⾮常好看的视频的描述信息
------WebKitFormBoundarydsrFiETIzKETHWkn
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: text/plain
image封⾯图⽚数据
------WebKitFormBoundarydsrFiETIzKETHWkn
Content-Disposition: form-data; name="video"; filename="video.mp4"
Content-Type: text/plain
video视频数据
------WebKitFormBoundarydsrFiETIzKETHWkn
Content-Disposition: form-data; name="submit"
------WebKitFormBoundarydsrFiETIzKETHWkn--
响应:
HTTP/1.1 303 See Other
Location: "/"
3.3.2 业务处理模块类的设计

  • 业务处理模块负责与客⼾端进⾏⽹络通信,接收客⼾端的请求,然后根据请求信息,明确客⼾端端⽤ ⼾的意图,进⾏业务处理,并进⾏对应的结果响应。
  • 在视频共享点播系统中,业务处理主要包含两⼤功能:1、⽹络通信功能的实现;2、业务功能处理的实现
  • 其中⽹络通信功能的实现咱们借助 httplib 库即可⽅便的搭建 http 服务器完成。这也是咱们将⽹ 络通信模块与业务处理模块合并在⼀起完成的原因。
功能:

宏定义

  • WWWROOT:定义了静态资源的根目录。

  • VIDEO_ROOTIMAGE_ROOT:定义了视频和图片资源的相对路径。

全局变量

  • tb_video:一个TableVideo类型的指针,作为全局变量,用于在多线程中访问数据管理对象。

Server类

  • 私有成员变量_port:存储服务器监听的端口号。

  • 私有成员变量_srvhttplib::Server类型的实例,用于搭建HTTP服务器。

私有静态成员函数

  • Insert:处理视频和图片的上传请求,保存文件到指定目录,并更新数据库。

  • Delete:根据视频ID删除视频和图片文件以及数据库记录。

  • Update:根据视频ID更新视频信息。

  • SelectOne:根据视频ID查询单个视频信息。

  • SelectAll:查询所有视频信息或根据关键字进行模糊查询。

公有构造函数

  • Server(int port):接收端口号作为参数,初始化Server对象。

公有成员函数

  • RunModule:初始化数据管理模块,设置静态资源目录,建立请求与处理函数的映射关系,并启动服务器。

主要流程

  1. RunModule函数中,首先初始化TableVideo对象和所需的目录结构。

  2. 使用httplib::Server设置静态资源目录,并为不同的HTTP请求方法添加相应的处理函数。

  3. 启动服务器监听指定端口。

#include "data.hpp"
#include "httplib.h"namespace aod{
#define WWWROOT "./www"
#define VIDEO_ROOT "/video/"
#define IMAGE_ROOT "/image/"// 因为httplib基于多线程,因此数据管理对象需要在多线程中访问,为了便于访问定义全局变量TableVideo *tb_video = NULL;// 这⾥为了更加功能模块划分清晰⼀些,不使⽤lamda表达式完成,否则所有的功能实现集中到⼀个函数中太过庞⼤class Server{private:int _port;			  // 服务器的 监听端⼝httplib::Server _srv; // ⽤于搭建http服务器private:// 对应的业务处理接⼝static void Insert(const httplib::Request &req, httplib::Response &rsp) {if (req.has_file("name") == false ||req.has_file("info") == false ||req.has_file("video") == false ||req.has_file("image") == false) {rsp.status = 400;rsp.body = R"({"result":false, "reason":"上传的数据信息错误"})";rsp.set_header("Content-Type", "application/json");return ;}httplib::MultipartFormData name = req.get_file_value("name");//视频名称httplib::MultipartFormData info = req.get_file_value("info");//视频简介httplib::MultipartFormData video = req.get_file_value("video");//视频文件httplib::MultipartFormData image = req.get_file_value("image");//图片文件//MultipartFormData {name, content_type, filename, content}std::string video_name = name.content;std::string video_info = info.content;// ./www/image/白娘子a.jpgstd::string root = WWWROOT;std::string video_path = root + VIDEO_ROOT + video_name + video.filename;std::string image_path = root + IMAGE_ROOT + video_name + image.filename;if (FileUtil(video_path).SetContent(video.content) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"视频文件存储失败"})";rsp.set_header("Content-Type", "application/json");return ;}if (FileUtil(image_path).SetContent(image.content) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"图片文件存储失败"})";rsp.set_header("Content-Type", "application/json");return ;}Json::Value video_json;video_json["name"] = video_name;video_json["info"] = video_info;video_json["video"] = VIDEO_ROOT + video_name + video.filename;// /video/变形金刚robot.mp4video_json["image"] = IMAGE_ROOT + video_name + image.filename;// /video/变形金刚robot.mp4if (tb_video->Insert(video_json) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"数据库新增数据失败"})";rsp.set_header("Content-Type", "application/json");return ;}rsp.set_redirect("/index.html", 303);return ;}static void Delete(const httplib::Request &req, httplib::Response &rsp) {//1. 获取要删除 的视频IDint video_id = std::stoi(req.matches[1]);//2. 删除视频以及图片文件Json::Value video;if (tb_video->SelectOne(video_id, &video) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"不存在视频信息"})";rsp.set_header("Content-Type", "application/json");return ;}std::string root = WWWROOT;std::string video_path = root + video["video"].asString();std::string image_path = root + video["image"].asString();remove(video_path.c_str());remove(image_path.c_str());//3. 删除数据库信息if (tb_video->Delete(video_id) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"删除数据库信息失败"})";rsp.set_header("Content-Type", "application/json");return ;}return ;}static void Update(const httplib::Request &req, httplib::Response &rsp) {//1. 获取要修改的视频信息 1. 视频id, 2. 修改后的信息int video_id = std::stoi(req.matches[1]);Json::Value video;if (JsonUtil::UnSerialize(req.body, &video) == false) {rsp.status = 400;rsp.body = R"({"result":false, "reason":"新的视频信息格式解析失败"})";rsp.set_header("Content-Type", "application/json");return ;}//2. 修改数据库数据if (tb_video->Update(video_id, video) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"修改数据库信息失败"})";rsp.set_header("Content-Type", "application/json");return ;}return ;}static void SelectOne(const httplib::Request &req, httplib::Response &rsp) {//1. 获取视频的IDint video_id = std::stoi(req.matches[1]);//2. 在数据库中查询指定视频信息Json::Value video;if (tb_video->SelectOne(video_id, &video) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"查询数据库指定视频信息失败"})";rsp.set_header("Content-Type", "application/json");return ;}//3. 组织响应正文--json格式的字符串JsonUtil::Serialize(video, &rsp.body);rsp.set_header("Content-Type", "application/json");return ;}static void SelectAll(const httplib::Request &req, httplib::Response &rsp) {//分为 所有查询 和 模糊匹配// /video   &    /video?search="关键字"bool select_flag = true;//默认所有查询std::string search_key;if (req.has_param("search") == true) {select_flag = false;//模糊匹配search_key = req.get_param_value("search");}Json::Value videos;if (select_flag == true) {if (tb_video->SelectAll(&videos) == false){rsp.status = 500;rsp.body = R"({"result":false, "reason":"查询数据库所有视频信息失败"})";rsp.set_header("Content-Type", "application/json");return ;}}else {if (tb_video->SelectLike(search_key, &videos) == false) {rsp.status = 500;rsp.body = R"({"result":false, "reason":"查询数据库匹配视频信息失败"})";rsp.set_header("Content-Type", "application/json");return ;}}JsonUtil::Serialize(videos, &rsp.body);rsp.set_header("Content-Type", "application/json");return ;}public:Server(int port):_port(port){}// 建⽴请求与处理函数的映射关系,设置静态资源根⽬录,启动服务器bool RunModule() {//1. 初始化操作---初始化数据管理模块,创建指定的目录tb_video = new TableVideo();FileUtil(WWWROOT).CreateDirectory();std::string root = WWWROOT;std::string video_real_path = root + VIDEO_ROOT;// ./www/video/FileUtil(video_real_path).CreateDirectory();std::string image_real_path = root + IMAGE_ROOT;// ./www/image/FileUtil(image_real_path).CreateDirectory();//2. 搭建http服务器,开始运行//	1. 设置静态资源根目录_srv.set_mount_point("/", WWWROOT);//	2. 添加请求-处理函数映射关系_srv.Post("/video", Insert);_srv.Delete("/video/(\\d+)", Delete);_srv.Put("/video/(\\d+)", Update);_srv.Get("/video/(\\d+)", SelectOne);_srv.Get("/video", SelectAll);//	3. 启动服务器_srv.listen("0.0.0.0", _port);return true;}};
}
3.3.2 最终合并调试
#include "server.hpp"
void ServerTest()
{aod::Server server(9000);server.RunModule(); 
} 
int main() 
{ServerTest();return 0;
}

Makefile 代码

aod:aod.cpp util.hpp data.hpp server.hppg++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -ljsoncpp -lmysqlclient -lpthread

3.4 前端界⾯模块实现

3.4.1 前端视频展示⻚⾯
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content=""><meta name="author" content="OrcasThemes"><meta http-equiv="X-UA-Compatible" content="IE=Edge" /><title>Home</title><!-- Bootstrap core CSS --><link href="css/bootstrap.css" rel="stylesheet"><!-- Custom styles for this template --><link rel="stylesheet" href="css/screen.css"><link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><![endif]--><link rel="stylesheet" href="css/font-awesome.css"><!--[if lt IE 8]><link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection"><![endif]--><link href="css/lity.css" rel="stylesheet"><style>[v-cloak] {display: none;}</style><style>.search-block .form-inline {display: flex;align-items: center;}.search-block .input-group {width: 100%;/* 根据需要调整宽度,这里设置为100%是为了适应容器宽度 */margin: 0;/* 移除外边距 */}.search-block .form-control {border-radius: 0.25rem;/* 输入框圆角 */}.search-block .btn-outline-secondary {background-color: transparent;/* 按钮背景色 */border-color: #ccc;/* 按钮边框色 */}.search-block .btn-outline-secondary:hover {background-color: #e9ecef;/* 鼠标悬浮时的按钮背景色 */}.search-block .btn-outline-secondary:focus,.search-block .btn-outline-secondary:active {background-color: #dde2e6;/* 聚焦或按下时的按钮背景色 */}.search-block {display: flex;align-items: center;/* 垂直居中对齐 */}.search-block .input-group {width: auto;/* 根据需要调整,auto允许根据内容自适应宽度 */flex-grow: 1;/* 允许输入组占据剩余空间 */margin-bottom: 0;/* 移除底部边距 */}.search-block .form-control {border-radius: 0.25rem 0 0 0.25rem;/* 圆角只应用于输入框的左半部分 */}.search-block .btn-outline-secondary {border-radius: 0 0.25rem 0.25rem 0;/* 圆角只应用于按钮的右半部分 */border-left: none;/* 移除按钮左侧的边框,与输入框对齐 */}.search-block .form-inline {display: flex;align-items: center;width: auto;/* 可以设置为特定宽度或自动 */}.search-block .input-group {flex-grow: 1;/* 允许输入组根据需要增长 */}.search-block .form-control {border-radius: 0.25rem 0 0 0.25rem;/* 圆角 */}.search-block .btn-outline-secondary {margin-left: -1px;/* 与输入框边框对齐 */border-radius: 0 0.25rem 0.25rem 0;/* 圆角 */}.input-group-append {display: flex;align-items: center;}#searchButton {white-space: nowrap;/* 防止按钮内的文本换行 */}.fa-search {margin-right: 5px;/* 根据需要调整搜索图标和按钮文本之间的间距 */}</style><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css"integrity="sha512-+4zCK9kqIH5OTEOcHLSb8txb5KQ0U5x1T6BjrrAa4LObyz1W6+5HlE6cRqN58p9tJJq6TjW7nPEK67E41+w=="crossorigin="anonymous" />
</head><body><div id="myapp"><!-- HOME 1 --><div id="home1" class="container-fluid standard-bg"><!-- HEADER --><div class="row header-top"><div class="col-lg-3 col-md-6 col-sm-5 col-xs-8"><a class="main-logo" href="#"><img src="img/main-logo.png" class="main-logo img-responsive"alt="Muvee Reviews" title="Muvee Reviews"></a></div><div class="col-lg-6 hidden-md text-center hidden-sm hidden-xs"></div><div class="col-lg-3 col-md-6 col-sm-7 hidden-xs"><div class="right-box"><button type="button" class="access-btn" data-toggle="modal"data-target="#enquirypopup">新增视频</button></div></div></div><!-- MENU --><div class="row home-mega-menu "><div class="col-md-12"><nav class="navbar navbar-default"><div class="navbar-header"><button class="navbar-toggle" type="button" data-toggle="collapse"data-target=".js-navbar-collapse"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button></div><div class="collapse navbar-collapse js-navbar-collapse megabg dropshd "><ul class="nav navbar-nav"><li><a href="index.html">视频点播</a></li></ul><div class="search-block"><form class="form-inline d-flex w-100"><div class="input-group"><input type="text" id="searchInput" class="form-control" placeholder="Search"name="searchInput"></div></form></div></div><!-- /.nav-collapse --></nav></div></div><!-- CORE --><div class="row"><!-- SIDEBAR --><div class="col-lg-2 col-md-4 hidden-sm hidden-xs"></div><!-- HOME MAIN POSTS --><div class="col-lg-10 col-md-8"><section id="home-main"><h2 class="icon"><i class="fa fa-television" aria-hidden="true"></i>视频列表</h2><div class="row"><!-- ARTICLES --><div class="col-lg-9 col-md-12 col-sm-12"><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"><h4><a class="title" href="#" v-cloak>{{video.name}}</a></h4><span class="posts-txt" title="Posts from Channel"><iclass="fa fa-thumbs-up" aria-hidden="true"></i>20.895</span><div class="ratings"><i class="fa fa-star" aria-hidden="true"></i><i class="fa fa-star" aria-hidden="true"></i><i class="fa fa-star-half-o" aria-hidden="true"></i><i class="fa fa-star-o"></i><i class="fa fa-star-half"></i></div></div></div></article></div><div class="clearfix spacer"></div></div><!-- RIGHT ASIDE --><div class="col-lg-3 hidden-md col-sm-12 text-center top-sidebar"></div></div></section></div></div></div><!-- CHANNELS --><div id="channels-block" class="container-fluid channels-bg"></div><!-- BOTTOM BANNER --><div id="bottom-banner" class="container text-center"></div><!-- FOOTER --><div id="footer" class="container-fluid footer-background"><div class="container"><footer><!-- SECTION FOOTER --><div class="row"><!-- SOCIAL --><div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"><div class="row auto-clear"></div></div><!-- TAGS --><div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div><!-- POST --><div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div><!-- LINKS --><div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div></div><div class="row copyright-bottom text-center"><div class="col-md-12 text-center"><a href="" class="footer-logo" title="Video Magazine Bootstrap HTML5 template"><img src="img/footer-logo.png" class="img-responsive text-center"alt="Video Magazine Bootstrap HTML5 template"></a><p v-cloak>Copyright &copy; Author by {{author}}</p></div></div></footer></div></div><!-- MODAL --><div id="enquirypopup" class="modal fade in " role="dialog"><div class="modal-dialog"><!-- Modal content--><div class="modal-content row"><div class="modal-header custom-modal-header"><button type="button" class="close" data-dismiss="modal">×</button><h2 class="icon"><i class="fa fa-television" aria-hidden="true"></i>新增视频</h2></div><div class="modal-body"><form name="info_form" class="form-inline" action="/video" method="post"enctype="multipart/form-data"><div class="form-group col-sm-12"><input type="text" class="form-control" name="name" placeholder="输入视频名称"></div><div class="form-group col-sm-12"><input type="text" class="form-control" name="info" placeholder="输入视频简介"></div><div class="form-group col-sm-12"><input type="file" class="form-control" name="video" placeholder="选择视频文件"></div><div class="form-group col-sm-12"><input type="file" class="form-control" name="image" placeholder="选择封面图片"></div><div class="form-group col-sm-12"><button class="subscribe-btn pull-right" type="submit" title="Subscribe">上传</button></div></form></div></div></div></div></div>
</body><!-- JAVA SCRIPT -->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="js/jquery-1.12.1.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/lity.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script>$(document).ready(function () {$(".nav .dropdown").hover(function () {$(this).find(".dropdown-toggle").dropdown("toggle");});});
</script>
<script>// Vue 实例let app = new Vue({el: '#myapp',data: {author: "ZhaoYiLong",videos: [], // 用于存储视频数据的数组searchQuery: '' // 用于存储搜索关键字},methods: {// 获取所有视频的方法get_allvideos: function () {fetch('/video').then(response => {if (!response.ok) {throw new Error('Network response was not ok');}return response.json();}).then(data => {this.videos = data;}).catch(error => {console.error('There has been a problem with your fetch operation:', error);});},// 搜索视频的方法search_videos: function () {var url = '/video?search=' + encodeURIComponent(this.searchQuery);fetch(url).then(response => response.json()).then(data => {this.videos = data;}).catch(error => {console.error('There has been a problem with your fetch operation:', error);});}},// 在Vue实例创建后调用get_allvideos方法created: function () {this.get_allvideos();}});// 为搜索框添加键盘事件监听,允许使用回车键进行搜索document.getElementById('searchInput').addEventListener('keypress', function (event) {var key = event.which || event.keyCode;if (key === 13) { // 13是回车键的键码event.preventDefault(); // 阻止表单的默认提交行为app.searchQuery = document.getElementById('searchInput').value;app.search_videos();}});// 移除原有的搜索按钮点击事件监听,因为我们现在使用回车键进行搜索// document.getElementById('searchButton').removeEventListener('click', ...);// // 为搜索按钮添加点击事件// document.getElementById('searchButton').addEventListener('click', function (event) {// 	event.preventDefault();// 	app.searchQuery = document.getElementById('searchInput').value;// 	app.search_videos();// });
</script>
</body></html>
3.4.2 前端视频观看页面的实现
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="description" content=""><meta name="author" content="OrcasThemes"><meta http-equiv="X-UA-Compatible" content="IE=Edge" /><title></title><!-- Bootstrap core CSS --><link href="css/bootstrap.css" rel="stylesheet"><!-- Custom styles for this template --><link rel="stylesheet" href="css/screen.css"><link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><![endif]--><link rel="stylesheet" href="css/font-awesome.css"><!--[if lt IE 8]><link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection"><![endif]--><link href="css/lity.css" rel="stylesheet"></head><body><div id="myapp"><!-- SINGLE VIDEO --><div id="single-video" class="container-fluid standard-bg"><!-- HEADER --><div class="row header-top"><div class="col-lg-3 col-md-6 col-sm-5"><a class="main-logo" href="#"><img src="img/main-logo.png" class="main-logo" alt="Muvee Reviews" title="Muvee Reviews"></a></div><div class="col-lg-6 hidden-md text-center hidden-sm hidden-xs"></div><div class="col-lg-3 col-md-6 col-sm-7 hidden-xs"><div class="right-box"><button type="button" class="access-btn" v-on:click="delete_video()">视频删除</button><button type="button" class="access-btn" data-toggle="modal" data-target="#enquirypopup">视频修改</button></div></div></div><!-- MENU --><div class="row home-mega-menu "><div class="col-md-12"><nav class="navbar navbar-default"><div class="navbar-header"><button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".js-navbar-collapse"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button></div><div class="collapse navbar-collapse js-navbar-collapse megabg dropshd "><ul class="nav navbar-nav"><li><a href="index.html">视频点播</a></li></ul><div class="search-block"><form><input type="search" placeholder="Search"></form></div></div><!-- /.nav-collapse --></nav></div></div><!-- SINGLE VIDEO --><div class="row"><!-- SIDEBAR --><div class="col-lg-2 col-md-4 hidden-sm hidden-xs"></div><!-- SINGLE VIDEO -->	<div id="single-video-wrapper" class="col-lg-10 col-md-8"><div class="row"><!-- VIDEO SINGLE POST --><div class="col-lg-9 col-md-12 col-sm-12"><!-- POST L size --><article class="post-video"><!-- VIDEO INFO --><div class="video-info"><!-- 16:9 aspect ratio --><div class="embed-responsive embed-responsive-16by9 video-embed-box"><iframe v-bind:src="video.video"  class="embed-responsive-item"></iframe></div><div class="metabox"><span class="meta-i"><i class="fa fa-thumbs-up" aria-hidden="true"></i>20.895</span><span class="meta-i"><i class="fa fa-thumbs-down" aria-hidden="true"></i>3.981</span><span class="meta-i"><i class="fa fa-user"></i><a href="#" class="author" title="John Doe">John Doe</a></span><span class="meta-i"><i class="fa fa-clock-o"></i>March 16. 2017</span><span class="meta-i"><i class="fa fa-eye"></i>1,347,912 views</span><div class="ratings"><i class="fa fa-star" aria-hidden="true"></i><i class="fa fa-star" aria-hidden="true"></i><i class="fa fa-star-half-o" aria-hidden="true"></i><i class="fa fa-star-o"></i><i class="fa fa-star-half"></i></div></div></div><div class="clearfix spacer"></div><!-- DETAILS --><div class="video-content"><h2 class="title main-head-title">视频描述</h2><p>{{video.info}}</p></div><div class="clearfix spacer"></div><!-- MAIN ROLL ADVERTISE BOX --></article></div><!-- VIDEO SIDE BANNERS --><div class="col-lg-3 hidden-md hidden-sm"></div></div><div class="clearfix spacer"></div><div class="row"></div></div></div></div><!-- CHANNELS --><div id="channels-block" class="container-fluid channels-bg"><div class="container-fluid "><div class="col-md-12"><!-- CHANNELS --><section id="channels"></section><div class="clearfix"></div></div></div></div><!-- BOTTOM BANNER --><div id="bottom-banner" class="container text-center">	</div><!-- FOOTER --><div id="footer" class="container-fluid footer-background"><div class="container"><footer><!-- SECTION FOOTER --><div class="row"><!-- TAGS --><!-- POST --><!-- LINKS --></div><div class="row copyright-bottom text-center"><div class="col-md-12 text-center"><a href="" class="footer-logo" title="Video Magazine Bootstrap HTML5 template"><img src="img/footer-logo.png" class="img-responsive text-center" alt="Video Magazine Bootstrap HTML5 template"></a>	<p>Copyright &copy; Author by {{author}}</p></div></div></footer></div></div><!-- MODAL --><div id="enquirypopup" class="modal fade in " role="dialog"><div class="modal-dialog"><!-- Modal content--><div class="modal-content row"><div class="modal-header custom-modal-header"><button type="button" class="close" data-dismiss="modal">×</button><h2 class="icon"><i class="fa fa-television" aria-hidden="true"></i>视频信息修改</h2></div><div class="modal-body"><form name="info_form" class="form-inline" action="#" method="post"><div class="form-group col-sm-12"><input type="text" class="form-control" name="name" v-model="video.name"></div><div class="form-group col-sm-12"><input type="text" class="form-control" name="info" v-model="video.info"></div><div class="form-group col-sm-12"><button class="subscribe-btn pull-right"  title="Subscribe" v-on:click.prevent="update_video()">提交</button></div></form></div></div></div></div></div></body><!-- JAVA SCRIPT --><!-- jQuery (necessary for Bootstrap's JavaScript plugins) --><script src="js/jquery-1.12.1.min.js"></script><script src="js/bootstrap.min.js"></script><script src="js/lity.js"></script><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script>$(".nav .dropdown").hover(function() {$(this).find(".dropdown-toggle").dropdown("toggle");});</script><script>let app = new Vue({el: '#myapp',data: {author: "ZhaoYiLong",video: {}},methods: {get_param: function(name) {return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ""])[1].replace(/\+/g, '%20')) || null},get_video: function() {var id = this.get_param("id");$.ajax({url: "/video/" + id,type: "get",context: this,success : function(result, status, xhr){this.video = result;}})},update_video: function() {$.ajax({url: "/video/" + this.video.id,type: "put",data: JSON.stringify(this.video),context: this,success : function(result, status, xhr){alert("修改视频信息成功");window.location.reload();}})},delete_video: function() {$.ajax({url: "/video/" + this.video.id,type: "delete",context: this,success : function(result, status, xhr){alert("删除视频成功");window.location.href="/index.html";}})}}});app.get_video();</script>
</html>

四.总结

  • 项目名称:C++视频共享点播系统
  • 项目功能:用户通过前端浏览器访问服务器,获得展示与观看和操作的界面,最终实现视频的上传以及观看的删改查等基础管理功能
  • 开发环境:Ubuntu22.04云服务器 ,Vscode ,MySQL ,Makefile
  • 技术特点:http服务器的搭建,restful风格接口设计,Json序列化,线程池,html+css+js
  • 数据管理模块:基于 MYSQL 进⾏数据管理,封装数据管理类进⾏数据统⼀访问
  • 业务处理模块: 基于 HTTPLIB 搭建 HTTP 服务器,使⽤ restful ⻛格 进⾏接⼝设计处理客⼾ 端业务请求  
  • 前端界⾯模块:基于基础的 HTML+CSS+JS 完成基于简单模板前端界⾯的修改与功能完成。
  • 代码链接:赵一龙/C+++-+视频点播项目 - 码云 - 开源中国 (gitee.com)

相关文章:

【项目实战】C++视频共享点播系统

目录 一、项目介绍 1.1 对视频共享点播系统的认识 1.2服务端程序负责功能 1.3 服务端功能模块划分 1.4 项目界面演示 1.5预备知识 二.环境搭建 2.1 安装 Jsoncpp 库 2.1.1 使用jsoncpp 2.2 引入httplib库 2.2.1 安装Git&#xff08;如果你的系统尚未安装Git&#xf…...

Android逆向题解 攻防世界难度4- Android2.0

Jeb打开apk 关键代码在Native函数getResult IDA 打开 so 发现代码比较简单&#xff0c;可以直接静态分析。 输出字符串也就是flag 长度是15&#xff0c;然后分成三段&#xff0c;第一段是可以整除3&#xff0c;第二段是除3取余1&#xff0c;第三段是除3取余等于2&#xff1…...

P4155 [SCOI2015] 计划

[SCOI2015] 计划 - 洛谷 核心思路 注意到&#xff0c; 可推出&#xff0c; 表示 战士 走 步到达战士位置。 若可以走到且 r < 终点 则答案 然后再加上自己这个哨兵&#xff0c;和走回自己的一个哨兵即可。 AC 代码 #include<bits/stdc.h> using namespace std…...

今日(2024年8月12日)科技新闻

国内&#xff1a; 航空航天领域 我国成功发射卫星互联网高轨卫星。我国试验性冰川保护项目取得积极成效&#xff0c;被形容为“为冰川盖棉被”。2024西太平洋国际航次科考队起航&#xff0c;开启探秘深海海山之旅。我国首架固定翼海上专业搜救航空器正式列编。“祥云”as700载…...

CP AUTOSAR标准之ECUStateManager(AUTOSAR_SWS_ECUStateManager)(更新中……)

1 简介和功能概述 ECU管理器模块(如本文档中所述)是一个基本软件模块(参见[1]),用于管理ECU状态的常见方面。具体来说,ECU管理器模块: 初始化和取消初始化OS、SchM和BswM以及一些基本软件驱动模块。根据请求配置ECU进入休眠和关机状态。管理ECU上的所有唤醒事件ECU管理器模块…...

Java中的中介者模式:解耦复杂系统的有效策略

Java中的中介者模式&#xff1a;解耦复杂系统的有效策略 在软件开发中&#xff0c;随着系统规模的扩大和复杂度的增加&#xff0c;各组件之间的直接交互会导致代码的耦合性增高&#xff0c;从而影响系统的可维护性和可扩展性。为了应对这种复杂性&#xff0c;中介者模式&#…...

transformer(李宏毅老师系列)

自学参考&#xff1a; Transformer:Attention Is All You Need Transformer论文逐段精读 视频课 课件资料 笔记 一、引入 seq2seq&#xff1a;输入一个序列的向量作为input&#xff0c;output的长度由机器自己决定seq2seq model应用: 语音辨识 输入是声音讯号的一串vector 输出…...

XCode15.4真机运行调试

更新Xcode后&#xff0c;没有模拟器内容&#xff0c;而且真机也不显示&#xff0c;编译按钮无法点击&#xff0c;设备在管理运行目标中可见&#xff0c;但无法选中解决方案&#xff1a;下载iOS17.5模拟器&#xff0c;但最坑的是直接点击“Get”下载总是中断&#xff0c;且无法断…...

Google Mock 和 Google Test编写单元测试入门(环境配置、简单执行)

文章目录 环境的配置方法1&#xff1a;从源代码构建第一步&#xff1a;克隆库的源代码第二步&#xff1a;构建库 方法 2&#xff1a;使用 CMake 的 FetchContent示例 CMakeLists.txt 项目的创建项目结构CMakeLists.txt (根目录)main.cpp (示例程序)tests/CMakeLists.txt (测试部…...

shell外壳与Linux权限

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 文章目录 1.shell命令以及运行原理2. Linux权限的概念3.Linux权限管理3.1 文件访问者的分类…...

越混越好的项目经理做对了哪些事?现在知道还不晚

作为一名项目经理&#xff0c;你最害怕的是什么&#xff1f; 是做不完的项目&#xff1f;延迟的进度条&#xff1f;还是团队人心涣散&#xff1f; 很多人都知道&#xff0c;得人心者得天下&#xff0c;一个成功的领导者&#xff0c;一定是能做到让人心服口服的。如果失去了团…...

haproxy是什么?以及haproxy基础实验

目录 一、什么是负载均衡&#xff1f; 二、为什么要用haproxy&#xff1f; 三、haproxy的基本部署实验&#xff1a; 3.1 基本配置实验 环境准备&#xff1a; 详细步骤&#xff1a; 3.2 haproxy-多进程与多线程实验&#xff1a; 多进程&#xff1a; 多线程&#xff1a;…...

【向量数据库】向量数据库的构建和检索

1、使用 sentence-transformers 将文本编码为向量 安装 sentence-transformers&#xff1a; pip install -U sentence-transformers在 huggingface 下载 all-MiniLM-L6-v2 模型权重&#xff08;1_Pooling 是文件夹&#xff0c;里面包含一个 config.json 文件&#xff09;&…...

Mysql基础篇之DQL语言

Mysql基础篇之DQL语言 1. 基础查询特点语法格式闲言碎语 2. 条件查询语法格式条件表达式逻辑表达式模糊查询 3. 排序查询4. 常见函数单行函数1. 字符函数2. 数学函数3. 日期函数4. 流程控制函数5. 其他函数 分组函数 5. 分组查询分组函数语法格式特点 6. 多表连接查询分类SQL 七…...

python async

要使用 Python 的 async 特性编写一个代码&#xff0c;以交替使用两个 AI API 处理数据&#xff0c;您可以按照以下步骤进行。假设这两个 AI API 的调用是异步的&#xff0c;并且我们需要在两个 API 之间轮流处理一组数据。 import asyncio import aiohttp async def call_ap…...

利用QT和FFmpeg实现一个简单的视频播放器

在当今的多媒体世界中&#xff0c;视频播放已成为不可或缺的一部分。从简单的媒体播放器到复杂的视频编辑软件&#xff0c;视频解码和显示技术无处不在。本示例使用Qt和FFmpeg构建一个简单的视频播放器。利用ffmpeg解码视频&#xff0c;通过QWidget渲染解码后的图像&#xff0c…...

怎么用云手机进行TikTok矩阵运营

TikTok作为炙手可热的社交媒体巨头&#xff0c;已经吸引了亿万用户的目光。随着科技的飞速发展&#xff0c;云手机的出现为TikTok矩阵运营注入了新的活力。本文将深入探讨云手机在TikTok矩阵运营中的实际应用&#xff0c;并分享一系列高效策略与技巧。 &#xff08;1&#xff0…...

TCP/IP 协议及其协议号

协议号十六进制协议号协议介绍10x1ICMP (Internet Control Message Protocol)20x2IGMP (Internet Group Management Protocol) 30x3GGP (Gateway-to-Gateway Protocol) 40x4IPv4 (encapsulation) 50x5ST (Stream Protocol) 60x6TCP (Transm…...

【传知代码】机器情绪及抑郁症算法 四(论文复现)

在现代心理健康研究中&#xff0c;抑郁症一直是一个备受关注的课题。随着科学的进步&#xff0c;研究人员逐渐认识到&#xff0c;抑郁症的成因远不止单一因素&#xff0c;而是由复杂的生物学、心理学和社会环境因素交织而成的。最近&#xff0c;MSA&#xff08;综合性综合性模型…...

C#开启和关闭UAC功能

在开发软件或制作安装包时&#xff0c;有时会需要管理员权限 &#xff0c;但是又不想弹出UAC对话框。 可以编写一个小工具&#xff0c;检测UAC是否关闭。如果没有关闭&#xff0c;就自动关闭UAC。 实现比较简单&#xff0c; 找到注册表 计算机\HKEY_LOCAL_MACHINE\SOFTWARE…...

LVS的简单配置及对Mysql主从复制的补充

Day 22 LVS的配置 环境准备 DSN() 用来解析各主机的域名和ip地址&#xff0c;配置域名解析huajuan&#xff0c;负责管理其他主机 web1--->web1.tangpin.huajuan web2--->web2.tangpin.huajuan dns--->dns.tangpin.huajuan web1(192.168.2.200) 用nginx…...

七夕情人节特辑:程序员的浪漫惊喜,9个表白源码,甜蜜编程陪你过节

大家好呀&#x1f44b;&#xff0c;今天是中国的七夕情人节&#xff0c;一个充满浪漫与爱的日子。为了庆祝这个特别的节日&#xff0c;我为大家精心准备了9个表白专用的前端小项目。这些项目涵盖了“我爱你”网站、爱情表白网站和心形动画等&#xff0c;通过HTML、CSS和一点点J…...

Mask-Rcnn

一 、FPN层 FPN层的基本作用 基本网络架构 基本思想 将多个阶段特征图融合在一起&#xff0c;这就相当于既有了高层的语义特征&#xff0c;也有了低层的轮廓特征 二、RPN层 三、ROI Align层...

Python图像背景去除

目录 &#x1f381;库的导入 &#x1f380;库的安装 &#x1f381;rembg库去除背景 &#x1f381;效果 &#x1f381;文末彩蛋 今天来介绍一个特别有趣的python库&#xff0c;rembg库&#xff0c;全称是“Remove Background”的缩写&#xff0c;意为“去除背景”&#xff…...

【C语言篇】C语言常考及易错题整理DAY1

文章目录 C语言常考及易错题整理选择题全局、局部和静态变量#define与typedef转义字符操作符循环其他 编程题计算日期到天数转换柯尼希定理旋转数组的最小数字描述错误的集合整数转换密码检查 C语言常考及易错题整理 选择题 全局、局部和静态变量 执行下面程序&#xff0c;正…...

MySQL5.7之源码安装

文章目录 下载编译&打包初始化数据目录启动服务器更改/设置root密码 下载 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 推荐下载 All Operating Systems (Generic) (Architecture Independent), Compressed TAR ArchiveIncludes Boost Headers …...

【Linux学习 | 第3篇】Linux系统安装 jdk+Tomcat+MySQL+lrzsz

文章目录 Linux—day31. 软件安装方式2. 安装jdk3. 安装Tomcat3.1 安装步骤&#xff1a;3.2 防火墙操作3.3 停止Tomcat服务的方式 4. 安装MySQL5. 安装lrzsz5.1 操作步骤 Linux—day3 Linux系统中软件安装 1. 软件安装方式 二进制发布包安装&#xff1a;软件已经针对具体平台…...

python语言day5 MD5 json

md5&#xff1a; python提供了内置的md5加密功能&#xff0c;使用md5模拟一个小项目&#xff1a; 注册&#xff1a; 启动py程序&#xff0c;在控制台界面提示用户输入用户名及密码&#xff1b; 使用md5加密 密码&#xff1b; 创建txt文件记录输入的用户名 和密文。 登录&…...

【Python学习手册(第四版)】学习笔记19-函数的高级话题

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本文主要介绍函数相关的高级概念&#xff1a;递归函数、函数注解、lambda表达式函数&#xff0c;常用函数工具如map、filter、reduce&#xff0c;以及通用的函数设…...

Selenium + Python 自动化测试11(unittest组织用例)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了unittest基本使用方法。 本篇文章我们接着讲。一些概念和一些常用的构造测试集的方法。 1、基本概念 1&#xff09;Test Case 一个Test Case的实例就是一个测…...