目录
[1.1 对视频共享点播系统的认识](#1.1 对视频共享点播系统的认识)
[1.3 服务端功能模块划分](#1.3 服务端功能模块划分)
[1.4 项目界面演示](#1.4 项目界面演示)
[2.1 安装 Jsoncpp 库](#2.1 安装 Jsoncpp 库)
[2.1.1 使用jsoncpp](#2.1.1 使用jsoncpp)
[2.2 引入httplib库](#2.2 引入httplib库)
[2.2.1 安装Git(如果你的系统尚未安装Git)](#2.2.1 安装Git(如果你的系统尚未安装Git))
[2.2.2 克隆仓库](#2.2.2 克隆仓库)
[2.2.3 使用httplib](#2.2.3 使用httplib)
[2.3 Mysql 数据库及开发包安装](#2.3 Mysql 数据库及开发包安装)
[2.3.1 安装MySQL服务器](#2.3.1 安装MySQL服务器)
[2.3.2 安装MySQL客户端](#2.3.2 安装MySQL客户端)
[2.3.3 安全配置(安装后建议执行)](#2.3.3 安全配置(安装后建议执行))
[2.3.4 启动、停止、重启 登录 MySQL服务](#2.3.4 启动、停止、重启 登录 MySQL服务)
[3.1.2 Json 实⽤⼯具类设计](#3.1.2 Json 实⽤⼯具类设计)
[3.2 服务端数据管理模块](#3.2 服务端数据管理模块)
[3.2.1 视频数据表的设计](#3.2.1 视频数据表的设计)
[3.2.2 数据管理类设计](#3.2.2 数据管理类设计)
[3.3 服务端业务处理模块](#3.3 服务端业务处理模块)
[3.3.1 ⽹络通信接⼝设计](#3.3.1 ⽹络通信接⼝设计)
[3.3.2 业务处理模块类的设计](#3.3.2 业务处理模块类的设计)
[3.3.2 最终合并调试](#3.3.2 最终合并调试)
[3.4 前端界⾯模块实现](#3.4 前端界⾯模块实现)
[3.4.1 前端视频展示⻚⾯](#3.4.1 前端视频展示⻚⾯)
[3.4.2 前端视频观看页面的实现](#3.4.2 前端视频观看页面的实现)
一、项目介绍
1.1 对视频共享点播系统的认识
- 搭建视频共享点播服务器,可以让所有人通过浏览器访问服务器,实现视频的上传观看,以及管理并播放的功能。
- 主要是完成服务器端的程序业务功能的实现以及前端访问界面 html 的编写,能够支持客户端浏览器针对服务器上的所有视频进行操作。
1.2服务端程序负责功能
- 针对客⼾端上传的视频⽂件以及封⾯图⽚进⾏备份存储。
- 针对客⼾端上传的视频完成增删改查功能
- ⽀持客⼾端浏览器进⾏视频的观看功能
1.3 服务端功能模块划分
该视频点播系统基本上包含四个模块:数据管理、网络通信、业务处理、前端界面。
- 数据管理模块:负责针对客户端上传的视频信息进行管理。
- 网络通信模块:搭建网络通信服务器,实现与客户端通信。
- 业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。
- 前端界面模块:前端浏览器上视频共享点播的各个 html 页面,在页面中支持增删改查以及观看功能。
1.4 项目界面演示
项目整体有俩个页面,分别为主界面和观看界面
主界面主要 用于 视频展示、视频上传、视频搜索
观看界面主要用于 视频观看、视频信息修改、视频删除
以下为项目页面功能展示
1.5预备知识
认识 JsonCpp
http://t.csdnimg.cn/lsz2ohttp://t.csdnimg.cn/lsz2o
认识 MySQL数据库的API
http://t.csdnimg.cn/hpxg1http://t.csdnimg.cn/hpxg1
认识 httplib
http://t.csdnimg.cn/SvQJzhttp://t.csdnimg.cn/SvQJz
二.环境搭建
我的服务器Ubuntu22.04
2.1 安装 Jsoncpp 库
cpp
sudo apt update
sudo apt -y install libjsoncpp25
2.1.1 使用jsoncpp
cpp
#include <jsoncpp/json/json.h>
2.2 引入httplib库
2.2.1 安装Git(如果你的系统尚未安装Git)
cpp
sudo apt update
sudo apt install git
2.2.2 克隆仓库
打开终端,使用
cd
命令切换到你想要存放cpp-httplib
库的目录,然后运行以下命令来克隆仓库:
cpp
git clone https://github.com/yhirose/cpp-httplib.git
2.2.3 使用httplib
直接在你的C++代码中包含
httplib.h
头文件。你可以将其复制到你的项目中合适的位置,然后在源文件中包含它
cpp
#include "httplib.h"
2.3 Mysql 数据库及开发包安装
2.3.1 安装MySQL服务器
cpp
sudo apt update //更新本地包数据库
sudo apt install mysql-server//安装最新版本的MySQL
2.3.2 安装MySQL客户端
cpp
sudo apt install mysql-client
2.3.3 安全配置(安装后建议执行)
cpp
sudo mysql_secure_installation//按照提示设置root用户的密码,移除匿名用户,禁止root用户远程登录等
sudo systemctl status mysql //确认MySQL服务状态
2.3.4 启动、停止、重启 登录 MySQL服务
cpp
sudo systemctl start mysql //启动服务
sudo systemctl stop mysql //停止服务
sudo systemctl restart mysql//重启服务
mysql -u root -p 使用root用户登录
三.项⽬实现
3.1服务端⼯具类实现
3.1.1⽂件实⽤⼯具类设计
在视频点播系统中因为涉及到⽂件上传,需要对上传的⽂件进⾏备份存储,因此⾸先设计封装⽂件操作类,这个类封装完毕之后,则在任意模块中对⽂件进⾏操作时都将变的简单化
功能:
构造函数
FileUtil(const std::string name)
:
- 接收一个字符串参数
name
,表示文件的路径和名称,并将这个值赋给私有成员变量_name
。Exists - 检查文件是否存在:
- 使用
access
函数和F_OK
标志来检查文件是否存在。- 如果
access
函数返回非0值,表示文件不存在,函数输出错误信息并返回false
。- 如果文件存在,返回
true
。Size - 获取文件大小:
- 首先检查文件是否存在,如果不存在则返回0。
- 使用
stat
函数获取文件的属性,并将文件大小存储在st.st_size
中。- 如果
stat
函数返回非0值,表示获取文件属性失败,函数输出错误信息并返回0。- 成功获取属性后,返回文件大小。
GetContent - 读取文件内容:
- 打开文件以二进制模式读取。
- 如果文件无法打开,输出错误信息并返回
false
。- 使用
Size
函数获取文件大小,并根据大小调整body
字符串的容量。- 读取文件内容到
body
字符串中。- 如果读取失败,输出错误信息,关闭文件,并返回
false
。- 成功读取后,关闭文件并返回
true
。SetContent - 向文件写入数据:
- 打开文件以二进制模式写入。
- 如果文件无法打开,输出错误信息并返回
false
。- 使用
write
函数将body
字符串的内容写入文件。- 如果写入失败,输出错误信息,关闭文件,并返回
false
。- 成功写入后,关闭文件并返回
true
。CreateDirectory - 创建目录:
- 首先检查目录是否存在,如果存在则直接返回
true
。- 使用
mkdir
函数创建目录,如果创建成功则返回true
。注意,这个类使用了C++标准库中的文件操作函数,如
access
、stat
、ifstream
、ofstream
和mkdir
。这些函数需要包含相应的头文件,例如<fstream>
、<sys/stat.h> 和 <unistd.h>。
cpp
class FileUtil{
private:
std::string _name;//文件路径名称
public:
FileUtil(const std::string name):_name(name){}
// 判断当前文件是否存在
bool Exists(){
//access的F_OK专门用于检测文件是否存在--- 存在则返回0
int 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 实⽤⼯具类设计
功能:
Serialize - 序列化函数:
- 这个函数接受一个
Json::Value
类型的参数value
和一个指向std::string
的指针body
。- 使用
Json::StreamWriterBuilder
创建一个Json::StreamWriter
对象,用于将Json::Value
对象写入到字符串流中。- 通过
write
方法将value
序列化到std::stringstream
对象ss
中。- 如果序列化失败(
write
方法返回非0值),则输出错误信息并返回false
。- 成功序列化后,将字符串流的内容赋值给
*body
,并返回true
。UnSerialize - 反序列化函数:
- 这个函数接受一个
std::string
类型的参数body
和一个指向Json::Value
的指针value
。- 使用
Json::CharReaderBuilder
创建一个Json::CharReader
对象,用于从字符串中解析JSON数据。- 使用
parse
方法尝试解析body
中的JSON数据,并将结果存储在value
指向的对象中。- 如果解析失败(
parse
方法返回false
),则输出错误信息并返回false
。- 成功解析后,返回
true
。
cpp
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 视频数据表的设计
在视频共享点播系统中,视频数据和图⽚数据都存储在⽂件中,⽽我们需要在数据库中管理⽤⼾上传 的每个视频信息。只是完成⼀个简单的视频信息表
cpp
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 对象进⾏传递
功能:
防止头文件重复包含 :使用
#ifndef
、#define
和#endif
宏来防止头文件被重复包含。包含头文件 :包含自定义的
util.hpp
头文件和一些标准库头文件,如<cstdlib>
、<mutex>
和<mysql/mysql.h>
。命名空间 :定义了一个名为
aod
的命名空间,用于封装相关的数据库操作。宏定义 :定义了一些宏,如数据库连接信息
HOST
、USER
、PASS
和NAME
。MysqlInit 函数:用于初始化MySQL连接,包括创建MySQL句柄、连接到服务器和设置字符集。
MysqlDestroy 函数:用于销毁MySQL连接,关闭句柄。
MysqlQuery 函数:用于执行SQL语句,并检查执行是否成功。
TableVideo 类:
- 私有成员
_mysql
:用于存储MySQL句柄。- 私有成员
_mutex
:用于多线程同步,保证线程安全。- 构造函数:初始化MySQL句柄。
- 析构函数:销毁MySQL句柄。
- 成员函数
Insert
:向数据库表tb_video
插入视频信息。- 成员函数
Update
:根据视频ID更新视频信息。- 成员函数
Delete
:根据视频ID删除视频信息。- 成员函数
SelectAll
:查询所有视频信息,并将结果存储在Json::Value
类型的参数中。- 成员函数
SelectOne
:根据视频ID查询单个视频信息。- 成员函数
SelectLike
:根据名称关键字进行模糊匹配查询视频信息。错误处理:在执行数据库操作时,如果遇到错误,会打印错误信息。
JSON操作 :使用
Json::Value
类型来处理JSON数据,这需要包含JSONCPP库的相关头文件。线程安全 :在
TableVideo
类中使用std::mutex
来保证多线程环境下对数据库操作的线程安全。资源管理 :在
SelectAll
、SelectOne
和SelectLike
函数中,使用mysql_store_result
保存查询结果,并在操作完成后使用mysql_free_result
释放结果集。
cpp
#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);
}
// 删除-传⼊视频ID
bool 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 end
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;
}
};
}
#endif
3.3 服务端业务处理模块
3.3.1 ⽹络通信接⼝设计
认识 rest设计风格
http://t.csdnimg.cn/wxhtmhttp://t.csdnimg.cn/wxhtm
获取所有视频信息
cpp
请求:
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",
}
]
搜索指定关键字名称视频信息
cpp
请求:
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",
}
]
获取指定视频信息
cpp
请求:
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",
}
]
删除指定视频信息
cpp
请求:
DELETE /video/1 HTTP/1.1
响应:
HTTP/1.1 200 OK
修改指定视频信息
cpp
请求:
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 ⻛格。
cpp
请求:
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_ROOT
和IMAGE_ROOT
:定义了视频和图片资源的相对路径。全局变量
tb_video
:一个TableVideo
类型的指针,作为全局变量,用于在多线程中访问数据管理对象。Server类
私有成员变量
_port
:存储服务器监听的端口号。私有成员变量
_srv
:httplib::Server
类型的实例,用于搭建HTTP服务器。私有静态成员函数
Insert
:处理视频和图片的上传请求,保存文件到指定目录,并更新数据库。
Delete
:根据视频ID删除视频和图片文件以及数据库记录。
Update
:根据视频ID更新视频信息。
SelectOne
:根据视频ID查询单个视频信息。
SelectAll
:查询所有视频信息或根据关键字进行模糊查询。公有构造函数
Server(int port)
:接收端口号作为参数,初始化Server
对象。公有成员函数
RunModule
:初始化数据管理模块,设置静态资源目录,建立请求与处理函数的映射关系,并启动服务器。主要流程
在
RunModule
函数中,首先初始化TableVideo
对象和所需的目录结构。使用
httplib::Server
设置静态资源目录,并为不同的HTTP请求方法添加相应的处理函数。启动服务器监听指定端口。
cpp
#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.jpg
std::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.mp4
video_json["image"] = IMAGE_ROOT + video_name + image.filename;// /video/变形金刚robot.mp4
if (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. 获取要删除 的视频ID
int 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. 获取视频的ID
int 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 最终合并调试
cpp
#include "server.hpp"
void ServerTest()
{
aod::Server server(9000);
server.RunModule();
}
int main()
{
ServerTest();
return 0;
}
Makefile 代码
cpp
aod:aod.cpp util.hpp data.hpp server.hpp
g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -ljsoncpp -lmysqlclient -lpthread
3.4 前端界⾯模块实现
3.4.1 前端视频展示⻚⾯
html
<!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"><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">
<h4>
<a class="title" href="#" v-cloak>{{video.name}}</a>
</h4>
<span class="posts-txt" title="Posts from Channel"><i
class="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 © 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 前端视频观看页面的实现
html
<!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 © 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)