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

news2024/11/25 2:44:43

目录

一、项目介绍

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专门用于检测文件是否存在--- 存在则返回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 实⽤⼯具类设计

功能:

  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);
			}
			// 删除-传⼊视频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/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.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 最终合并调试
#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.hpp
	g++ -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"><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 &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)

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

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

相关文章

基于Hadoop的共享单车分布式存储与计算

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究目的和意义国内外研究现状总体研究思路数据可视化每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 共享单车的普及带…

Elasticsearch:使用 ES|QL 进行地理空间搜索

作者&#xff1a;来自 Elastic Craig Taverner 多年来&#xff0c;Elasticsearch 一直具有强大的地理空间搜索和分析功能&#xff0c;但其 API 与典型的 GIS 用户习惯的 API 截然不同。在过去的一年中&#xff0c;我们添加了 ES|QL 查询语言&#xff0c;这是一种管道查询语言&a…

React原理之React整体渲染流程

前置知识&#xff1a;深度优先搜索(DFS)、Fiber 节点 在上一篇 React原理篇之 React 整体架构解读中&#xff0c;提到了 Fiber 架构中的几个核心概念&#xff1a; Scheduler&#xff08;调度器&#xff09;&#xff1a;根据任务的优先级安排任务执行顺序。Reconciler&#xff…

CUDA-MODE 第一课课后实战(下)

我的课程笔记&#xff0c;欢迎关注&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode CUDA-MODE 第一课课后实战&#xff08;下&#xff09; Nsight Compute Profile结果分析 继续对Nsight Compute的Profile结果进行分析&#xff0…

PyQT 串口改动每次点开时更新串口信息

class MainWindow(QWidget, Ui_Form):def __init__(self):super().__init__(parentNone)self.setupUi(self)self.comboBox.installEventFilter(self) # 加载事件过滤器self.comboBox.addItems(get_ports())def eventFilter(self, obj, event): # 定义事件过滤器if isinstance(o…

前端容器化部署:解决重启容器时的静态资源丢失问题

文章目录 什么是前端容器化&#xff1f;重启容器时静态资源丢失的问题解决静态资源丢失的方案1. 使用持久化卷创建和挂载卷使用Docker Compose定义卷 2. 使用对象存储将静态资源上传到对象存储 3. 使用构建时持久化使用CI/CD管道 4. 使用动态加载和缓存使用浏览器缓存使用服务端…

Java 8日期时间API革新:从Date到LocalDate、LocalTime与LocalDateTime的转型与优势解析

文章目录 前言一、基础介绍1.Date2.LocalDate3.LocalTime4.LocalDateTime 二、区别三、推荐场景四、推荐原因总结 前言 在Java的发展历程中&#xff0c;日期和时间的处理一直是开发者们关注的焦点。从早期的java.util.Date类到java.util.Calendar接口&#xff0c;虽然为日期时间…

Linux从0到1——进程池

Linux从0到1——进程池 1. 进程池的概念2. 进程池实现思路3. 进程池的代码实现3.1 创建管道&#xff0c;创建子进程3.2 封装任务3.3 Work接口3.4 发送任务3.5 回收资源&#xff0c;关闭管道&#xff08;重点&#xff09;3.6 改造CreatChannels接口 4. 完整代码 1. 进程池的概念…

数据结构面试-核心概念-问题理解

目录 1.数据结构及其分类 2.时间复杂度与空间复杂度及大O表示法 3.循环队列及其判断队空和队满的方法 4.栈与队列在计算机系统中的应用 5.串的模式匹配算法 6.线索二叉树、二叉搜索树、平衡二叉树 7.哈夫曼树与哈夫曼编码 8.DFS与BFS 9.最小生成树及其构建算法 10.最短…

谭晓生解读:AI如何重塑网络安全的未来?

导语 | 当前&#xff0c;对网络安全而言&#xff0c;每一次新的信息技术浪潮都蕴含着巨大机会&#xff0c;同时也意味着巨大的挑战。大模型的发展&#xff0c;是带来了更大的AI安全风险&#xff0c;还是将赋能提升网络安全呢&#xff1f;网络安全正在迎来一场重大范式转移&…

【网络编程】TCP通信基础模型实现

tcpSer.c #include <myhead.h> #define SER_IP "192.168.119.143" // 设置IP地址 #define SER_PORT 6666 // 设置端口号 int main(int argc, const char *argv[]) {// 1.创建socketint serfd socket(AF_INET, SOCK_STREAM, 0);// 参数1表示ipv4// 参数2表…

基于redis的zset实现排行榜

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.sun-club-subject集成redis1.sun-club-domain引入依赖2.sun-…

Linux 内核源码分析---内核 Netlink 套接字

linux 内核一直存在的一个严重问题就是内核态和用户态的交互的问题&#xff0c;对于这个问题内核大佬们一直在研究各种方法&#xff0c;想让内核和用户态交互能够安全高效的进行。如系统调用&#xff0c;proc&#xff0c;sysfs 等内存文件系统&#xff0c;但是这些方式一般都比…

从今年的计算机视觉比赛看风向

记第一次参加CV比赛的经历-长三角&#xff08;芜湖&#xff09;人工智能视觉算法大赛-CSDN博客 去年参赛的记录里说了&#xff1a; 最近&#xff0c;同样的由芜湖举办的比赛又上线了&#xff0c;果然&#xff1a; 2023年是这些赛题&#xff0c;典型的CV&#xff1a; 今年变成…

如何高效记录并整理编程学习笔记?一个好的笔记软件往往可以达到事半功倍的学习效果 φ(* ̄0 ̄)

在编程学习的旅程中&#xff0c;良好的笔记习惯不仅是知识积累的基石&#xff0c;更是提升学习效率、巩固学习成果的关键。选择合适的笔记工具&#xff0c;并掌握其高效使用方法&#xff0c;对于每一位编程学习者而言都至关重要。本文将从笔记工具的选择角度出发&#xff0c;探…

Linux 中断机制(一)之中断和异常

目录 一、什么是中断1、概述2、中断的分类 二、中断和异常1、中断和异常2、中断的上下部3、异常4、APIC5、中断描述符表 三、软件实现 一、什么是中断 1、概述 中断&#xff08;interrupt&#xff09;是指在 CPU 正常运行期间&#xff0c; 由外部或内部事件引起的一种机制。 …

Miracast ——随时随地在Wi-Fi®设备上分享高清内容

Miracast 是一种无线显示技术&#xff0c;由 Wi-Fi 联盟开发&#xff0c;允许设备之间通过无线方式分享多媒体内容。 Wi-Fi CERTIFIED Miracast™支持在Miracast设备之间无缝显示多媒体内容。Miracast使用户能够通过无线连接在Wi-Fi设备之间分享多媒体内容&#xff0c;包括高分…

六西格玛绿带培训对企业有什么帮助?

六西格玛&#xff0c;这一源自摩托罗拉、风靡全球的管理哲学和方法论&#xff0c;以其严谨的数据分析、持续改进的流程优化理念&#xff0c;帮助无数企业实现了从“好”到“卓越”的跨越。而六西格玛绿带&#xff0c;作为这一体系中的中坚力量&#xff0c;是连接高层管理者与一…

Linux--C语言之分支结构

文章目录 一、分支结构&#xff08;一&#xff09;概念&#xff08;二&#xff09;条件构建1.关系表达式&#xff1a;2.逻辑表达式&#xff1a;3.常量/变量&#xff1a;值是否非0&#xff0c;取值&#xff08;0|1&#xff09; &#xff08;三&#xff09;选择结构的形式1.单分支…

QT容器组

目录 容器组 Group BoX&#xff08;组&#xff09; Scroll Area&#xff08;组滑动&#xff09; Tool Box&#xff08;分页显示&#xff09; Tab Widget&#xff08;也是分页显示&#xff09; Stacked widget&#xff08;也是分页&#xff09; Frame&#xff08;就一个框…