文章目录
- 视频点播
- 技术栈与项目环境
- JsonCpp
- MariaDB
- httplib
- 工具类设计
- 文件类
- Json类
- 数据管理模块
- 视频信息管理(数据表设计)
- 数据管理类设计
- 网络通信接口设计
- 业务处理模块设计
- 前端界面
- 主页面
- 播放页面
- 项目总结
- 项目回顾
- 项目结构
- 关键技术点
- 总结
视频点播
允许用户通过浏览器访问视频网站,浏览多个线上视频,并允许点开一个视频进行观看。同时,也可以对视频进行增删改查。
项目链接
技术栈与项目环境
- 项目环境
系统:Ubuntu 20.04(Centos 7也行)
编辑器:visual studio code(vscode)
编译器:gcc、g++
编译脚本:Makefile
- 技术栈
C/C++、C++11、STL、jsoncpp、MariaDB、httplib
JsonCpp
JsonCpp
是一个开源的 C++ 库,用于解析和生成 JSON 数据。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人类阅读和编写,同时也易于机器解析和生成。JsonCpp 提供了简洁、易用的接口,方便 C++ 开发者处理 JSON 数据。以下是对 JsonCpp 主要特性的阐述:
主要特性
- 解析 JSON 数据:JsonCpp 可以将 JSON 格式的字符串解析为 C++ 中的对象(如
Json::Value
),使得开发者可以方便地访问 JSON 数据的各个字段。 - 生成 JSON 数据:JsonCpp 可以将 C++ 对象(如
Json::Value
)序列化为 JSON 格式的字符串,以便进行数据交换或存储。 - 易用性:JsonCpp 提供了友好的 API,易于上手使用。其设计直观,适合于初学者和有经验的开发者。
- 灵活性:JsonCpp 支持多种 JSON 数据类型,包括对象、数组、字符串、数字、布尔值和空值(null)。
- 健壮性:JsonCpp 经过大量测试,能够处理各种复杂的 JSON 数据,具有较高的健壮性。
🔺Value
类
Value 类用于表示 JSON 数据,可以是以下几种类型之一:
nullValue
:空值intValue
:整数uintValue
:无符号整数realValue
:浮点数stringValue
:字符串booleanValue
:布尔值arrayValue
:数组objectValue
:对象
🔺创建 Value
对象
可以通过不同的构造函数来创建 Value
对象。例如:
#include <json/json.h>
Json::Value nullValue; // 默认构造为 null
Json::Value intValue(42); // 整数
Json::Value stringValue("hello"); // 字符串
Json::Value boolValue(true); // 布尔值
// 创建数组和对象
Json::Value arrayValue(Json::arrayValue);
Json::Value objectValue(Json::objectValue);
🔺访问和修改 Value
对象
可以通过索引或键来访问和修改数组和对象:
// 数组
arrayValue.append("first element");
arrayValue.append(10);
// 对象
objectValue["key1"] = "value1";
objectValue["key2"] = 42;
🔺序列化(Serialization)
将 Value
对象转换为 JSON 字符串的过程称为序列化。可以使用 Json::StreamWriterBuilder
来完成这一任务:
std::string name ="小明";
int age =22;
float score[] = {99.9,100.0,98.5};
Json::Value val(Json::objectValue);
val["姓名"] = name;
val["年龄"] = age;
val["成绩"].append(score[0]);
val["成绩"].append(score[1]);
val["成绩"].append(score[2]);
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::stringstream ss;
int ret = sw->write(val,&ss);
if(ret!=0)
{
std::cerr<<"write failed!"<<std::endl;
}
std::cout<<ss.str()<<std::endl;
运行结果:
{
"姓名" : "小明",
"年龄" : 22,
"成绩" :
[
99.900001525878906,
100,
98.5
]
}
🔺反序列化(Deserialization)
将 JSON 字符串解析为 Value
对象的过程称为反序列化。可以使用 Json::CharReaderBuilder
来完成这一任务:
std::string str=R"({"姓名":"小明", "年龄":22, "成绩":[99.9, 100.0, 98.5]})";
Json::Value root;
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
bool ret = cr->parse(str.c_str(),str.c_str()+str.size(),&root,&err);
if(ret ==false)
{
std::cerr << "parse error"<<std::endl;
return;
}
std::cout<<root["姓名"].asString()<<std::endl;
std::cout<<root["年龄"].asInt()<<std::endl;
int sz=root["成绩"].size();
for(int i=0;i<sz;++i)
{
std::cout<<root["成绩"][i].asFloat()<<std::endl;
}
运行结果:
小明
22
99.9
100
98.5
MariaDB
MariaDB
是一个开源的关系型数据库管理系统 (RDBMS),是 MySQL 的一个分支。它由 MySQL 的原始开发者主导创建,目的是应对 Oracle 公司收购 MySQL 后对其未来发展的担忧。MariaDB 在保持免费使用的前提下,提供了多个相对于 MySQL 的增强功能。
数据库创建:
test_tb是数据库的名字。
create database if not exists test_db;
use test_db;
create table if not exists test_tb(
id int primary key auto_increment,
age int,
name varchar(32),
score decimal(4,2)
);
常用接口:
🔺初始化和连接
MYSQL *mysql_init(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向MYSQL结构的指针。如果为NULL
,函数将分配一个新的MYSQL结构;如果不为NULL
,函数将初始化传入的MYSQL结构。
- 功能: 初始化一个MYSQL对象。
- 返回值: 如果成功返回初始化的MYSQL对象,失败返回
NULL
。
- 参数:
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long client_flag)
- 参数:
MYSQL *mysql
: 一个指向已初始化MYSQL对象的指针。const char *host
: 数据库主机名或IP地址。const char *user
: 用户名。const char *passwd
: 密码。const char *db
: 要连接的数据库名。unsigned int port
: 端口号(通常是3306)。const char *unix_socket
: Unix socket文件的路径(用于Unix/Linux系统)。unsigned long client_flag
: 客户端标志位,用于指定各种连接选项(如:CLIENT_MULTI_STATEMENTS
)。
- 功能: 尝试建立到MySQL数据库的连接。
- 返回值: 如果成功返回
MYSQL
对象的指针,失败返回NULL
。
- 参数:
🔺查询和检索数据
int mysql_query(MYSQL *mysql, const char *query)
- 参数:
MYSQL *mysql
: 一个指向已连接MYSQL对象的指针。const char *query
: 要执行的SQL查询字符串。
- 功能: 执行一个SQL查询。
- 返回值: 成功返回0,失败返回非零。
- 参数:
MYSQL_RES *mysql_store_result(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向已连接MYSQL对象的指针。
- 功能: 检索完整的结果集。
- 返回值: 如果成功返回
MYSQL_RES
结果集指针,失败返回NULL
。
- 参数:
my_ulonglong mysql_num_rows(MYSQL_RES *result)
- 参数:
result
:MYSQL_RES
结构体指针,表示 MySQL 查询结果集的句柄。
- 功能: 当执行一个 SELECT 查询后,可以调用
mysql_num_rows
函数来获取返回的行数。 - 返回值:
my_ulonglong
: 一个无符号长整型 (unsigned long long
) 值,表示查询结果集中的行数。
- 参数:
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
- 参数:
MYSQL_RES *result
: 一个指向结果集的指针。
- 功能: 从结果集中获取下一行。
- 返回值: 如果成功返回
MYSQL_ROW
,没有更多数据时返回NULL
。
- 参数:
unsigned int mysql_num_fields(MYSQL_RES *result)
- 参数:
MYSQL_RES *result
: 一个指向结果集的指针。
- 功能: 获取结果集中每一行数据的列数。
- 返回值: 结果集中每一行数据的列数。
- 参数:
void mysql_free_result(MYSQL_RES *result)
- 参数:
MYSQL_RES *result
: 一个指向结果集的指针。
- 功能: 释放结果集使用的内存。
- 返回值: 无。
- 参数:
🔺关闭连接
void mysql_close(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向MYSQL对象的指针。
- 功能: 关闭与数据库的连接并释放相关资源。
- 返回值: 无。
- 参数:
🔺错误处理
const char *mysql_error(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向MYSQL对象的指针。
- 功能: 返回最近一次MySQL操作的错误消息。
- 返回值: 返回一个指向错误消息的字符串的指针。
- 参数:
🔺其他常用函数
my_ulonglong mysql_affected_rows(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向MYSQL对象的指针。
- 功能: 返回上一个查询所影响的行数。
- 返回值: 返回一个
my_ulonglong
类型的值,表示受影响的行数。
- 参数:
int mysql_select_db(MYSQL *mysql, const char *db)
- 参数:
MYSQL *mysql
: 一个指向MYSQL对象的指针。const char *db
: 要选择的数据库名。
- 功能: 选择一个数据库进行操作。
- 返回值: 成功返回0,失败返回非零。
- 参数:
int mysql_ping(MYSQL *mysql)
- 参数:
MYSQL *mysql
: 一个指向MYSQL对象的指针。
- 功能: 检查连接是否有效,如果连接已经断开则尝试重连。
- 返回值: 成功返回0,失败返回非零。
- 参数:
int mysql_set_character_set(MYSQL *mysql, const char *charset)
- 参数:
mysql
:MYSQL
结构体指针,表示与 MySQL 服务器的连接。charset
: 字符集名称,以字符串形式传入。例如,常见的字符集包括"utf8mb4"
、"latin1"
、"utf8"
等。
- 功能:该函数告知 MySQL 服务器,客户端希望用指定的字符集来处理数据。这样,当客户端向服务器发送数据时,服务器可以根据这个字符集进行正确的处理和存储。
- 返回值:成功返回0,失败返回非零。
- 参数:
🔺测试
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>
const char* host = "127.0.0.1";
const char* user="root";
const char* password="123456";
const char* db = "test_db";
int port =0;
const char* character="utf8mb4";
int insert(MYSQL* mysql){
char* sql = "insert into test_tb values(null,22,'小明',95);";
int ret = mysql_query(mysql,sql);
if(ret!=0)
{
printf("query \"%s\" failed:%s\n",sql,mysql_error(mysql));
return -1;
}
return 0;
}
int modfiy(MYSQL* mysql)
{
char* sql = "update test_tb set name='小刚' where id = 1";
int ret = mysql_query(mysql,sql);
if(ret!=0)
{
printf("query \"%s\" failed:%s\n",sql,mysql_error(mysql));
return -1;
}
return 0;
}
int delete(MYSQL* mysql)
{
char* sql = "delete from test_tb where id = 1";
int ret = mysql_query(mysql,sql);
if(ret!=0)
{
printf("query \"%s\" failed:%s\n",sql,mysql_error(mysql));
return -1;
}
return 0;
}
int get(MYSQL* mysql)
{
char* sql = "select * from test_tb";
int ret = mysql_query(mysql,sql);
if(ret!=0)
{
printf("query \"%s\" failed:%s\n",sql,mysql_error(mysql));
return -1;
}
//获取结果集到本地
MYSQL_RES* res=mysql_store_result(mysql);
if(res==NULL)
{
printf("store result failed:%s\n",mysql_error(mysql));
return -1;
}
//获取结果集的行数和列数
int row_num = mysql_num_rows(res);
int col_num = mysql_num_fields(res);
//遍历结果集,获取每一行数据
for(int i=0;i<row_num;++i)
{
MYSQL_ROW row = mysql_fetch_row(res);
//遍历每一行数据
for(int j=0;j<col_num;++j)
{
printf("%s\t",row[j]);
}
printf("\n");
}
//释放结果集
mysql_free_result(res);
return 0;
}
int main()
{
//初始化操作句柄
MYSQL*mysql= mysql_init(NULL);
if(mysql==NULL)
{
printf("mysql_init error\n");
}
//连接mysql服务器
if(mysql_real_connect(mysql,host,user,password,db,port,NULL,0)==NULL)
{
printf("connect mysql server failed!\n");
return -1;
}
//设置客户端字符集
mysql_set_character_set(mysql,character);
//插入数据
printf("insert:\n");
insert(mysql);
insert(mysql);
get(mysql);
//修改数据
printf("modify:\n");
modfiy(mysql);
get(mysql);
//删除数据
printf("delete:\n");
delete(mysql);
get(mysql);
//关闭mysql服务
mysql_close(mysql);
return 0;
}
运行结果:
insert:
1 22 小明 95.00
2 22 小明 95.00
modify:
1 22 小刚 95.00
2 22 小明 95.00
delete:
2 22 小明 95.00
httplib
C++ 的 httplib 是一个轻量级的 HTTP 库,用于构建简单的 HTTP 服务器和客户端应用程序。它的主要特点包括单头文件实现、易于使用和跨平台支持。
🔺特点
- 单头文件:该库只有一个头文件
httplib.h
,方便集成到项目中。 - 易用性:提供简洁的 API,使得构建 HTTP 客户端和服务器变得简单。
- 跨平台:支持 Windows、Linux 和 macOS。
在 C++ 的 httplib 库中,Request
和 Response
类是 HTTP 请求和响应的核心部分。理解它们的内部结构和用法对于有效地使用 httplib 构建 HTTP 服务器和客户端非常重要。以下是这两个类的详细解析。
🔺httplib::Request 类
httplib::Request
类代表一个 HTTP 请求,包含了请求方法、路径、头信息、查询参数、正文等。主要成员变量和方法如下:
成员变量
std::string method
:请求方法,如 “GET”, “POST”, “PUT” 等。std::string path
:请求路径。Headers headers
:请求头信息,是一个键值对的集合。Params params
:查询参数,是一个键值对的集合。std::string body
:请求的正文数据。std::string remote_addr
:客户端的 IP 地址。std::string version
:HTTP 版本,如 “HTTP/1.1”。std::string target
:请求的目标 URI,包括路径和查询参数。
常用方法
void set_header(const char *key, const char *val)
:设置请求头信息。std::string get_header_value(const char *key, size_t id = 0) const
:获取指定键的请求头信息。bool has_header(const char *key) const
:检查是否包含指定键的请求头。std::string get_param_value(const char *key, size_t id = 0) const
:获取指定键的查询参数。bool has_param(const char *key) const
:检查是否包含指定键的查询参数。
🔺httplib::Response 类
httplib::Response
类代表一个 HTTP 响应,包含了状态码、头信息、正文等。主要成员变量和方法如下:
成员变量
int status
:HTTP 状态码,如 200, 404, 500 等。Headers headers
:响应头信息,是一个键值对的集合。std::string body
:响应的正文数据。std::string version
:HTTP 版本,如 “HTTP/1.1”。
常用方法
void set_header(const char *key, const char *val)
:设置响应头信息。std::string get_header_value(const char *key, size_t id = 0) const
:获取指定键的响应头信息。bool has_header(const char *key) const
:检查是否包含指定键的响应头。void set_content(const char *s, size_t n, const char *content_type)
:设置响应正文数据及其 MIME 类型。void set_content(const std::string &s, const char *content_type)
:设置响应正文数据及其 MIME 类型。
🔺httplib::Server 类
httplib::Server
类提供了一种简单而有效的方式来构建 HTTP 服务器。以下是其主要成员变量和方法:
主要方法
-
路由设置:
void Get(const std::string &pattern, Handler handler)
:设置对GET
请求的处理函数。void Post(const std::string &pattern, Handler handler)
:设置对POST
请求的处理函数。void Put(const std::string &pattern, Handler handler)
:设置对PUT
请求的处理函数。void Delete(const std::string &pattern, Handler handler)
:设置对DELETE
请求的处理函数。void Options(const std::string &pattern, Handler handler)
:设置对OPTIONS
请求的处理函数。
Handler
是一个函数对象,通常为std::function<void(const Request&, Response&)>
类型。 -
服务器控制:
bool listen(const char *host, int port, int socket_flags = 0)
:启动服务器,监听指定的主机和端口。void stop()
:停止服务器。
-
中间件:
void set_logger(Logger logger)
:设置日志处理函数,用于记录请求和响应信息。void set_error_handler(ErrorHandler handler)
:设置错误处理函数,用于处理 HTTP 错误。
Logger
是一个函数对象,通常为std::function<void(const Request&, const Response&)>
类型。ErrorHandler
是一个函数对象,通常为std::function<void(const Request&, Response&, int status_code)>
类型。 -
静态文件服务:
void set_mount_point(const char *mount_point, const char *dir)
:设置静态文件服务的挂载点和目录。
通过这个方法,服务器可以将某个 URL 路径映射到本地文件系统中的一个目录,从而提供静态文件服务。当用户请求静态资源的时候,则在指定的根目录下找,找到之后直接进行响应,不需要用户在外部进行额外的处理函数。
多线程操作
httplib
库中内置了一个简单的 TaskQueue
类,用于管理任务队列。这使得 httplib
能够在多线程环境下运行服务器并处理多个请求。TaskQueue
的实现隐藏在库内部,但我们可以通过使用 Server
类的 new_task_queue
方法来启用多线程支持。
工作流程
- 接受请求数据
- 进行解析,得到Request结构
- 检测映射表,根据请求的方法和资源路径查询有没有对应的处理函数。
- 有,则调用,并且将Request和空Response对象传入。
- 没有,则检测是否是静态资源。如果存在该静态资源,则直接返回;否则返回404页面。
- 当处理函数执行完毕之后的到一个填充完毕的Response对象。
- 根据Response对象的数据,组织http响应发送给客户端。
- 短连接则直接关闭,长连接等待超时后关闭。
🔺测试
测试的目录结构
jia@jia-ubuntu:ClickVedio$ tree
.
├── cpp-httplib -> /home/jia/thirdpart/cpp-httplib-v0.7.15
├── jsoncpp_test.cc
├── Makefile
├── mysql_test.c
├── server.cpp
└── www
└── index.html
2 directories, 5 files
cpp-httplib -> /home/jia/thirdpart/cpp-httplib-v0.7.15实际上是一个软连接,指向下载的httplib第三方库cpp-httplib-v0.7.15
。可以使用如下指令进行创建:
ln -s /home/jia/thirdpart/cpp-httplib-v0.7.15 /home/jia/project/ClickVedio/cpp-httplib
其中index.html是一个测试用的前端的页面,在www目录之下。server.cpp是测试用的文件。
index.html
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type">
</head>
<body>
<h1>Hello friends</h1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="submit" value="上传">
</form>
</body>
</html>
server.cpp
#include "cpp-httplib/httplib.h"
void Hello(const httplib::Request&req, httplib::Response&rsp)
{
rsp.body="Hello friend";
rsp.status=200;//默认会自动添加
}
void Numbers(const httplib::Request&req, httplib::Response&rsp)
{
//matches:存放正则表达式匹配的规则数据
std::string num=req.matches[1];
rsp.set_content(num,"text/plain");//向rsp.body中添加数据,并且设置Content-Type类型
}
void Multipart(const httplib::Request&req, httplib::Response&rsp)
{
httplib::MultipartFormData file= req.get_file_value("file1");
std::cout<<file.filename<<std::endl;
std::cout<<file.content<<std::endl;
}
int main()
{
httplib::Server server;
//设置一个静态资源根目录
server.set_mount_point("/","./www");
//添加请求-处理函数映射信息
server.Get("/hi",Hello);
server.Get("/numbers/(\\d+)",Numbers);
server.Post("/multipart",Multipart);
//服务器开始监听
server.listen("0.0.0.0",9090);
return 0;
}
测试结果:
- 使用
hi
方法
- 使用
numbers
方法
- 访问
index.html
网页,上传文件
选择文件之后,点击上传。
上传文件之后,后端获取文件内容并输出。
工具类设计
文件类
视频点播涉及到文件上传,需要对上传的文件进行备份存储,因此需要首先设计封装文件操作类,简化对文件的多种操作。
util.hpp:
#ifndef __MY_UTIL__
#define __MY_UTIL__
#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <sys/stat.h>
namespace Util
{
class FileUtil
{
private:
std::string _name;//文件路径名
public:
FileUtil(const std::string name):_name(name){}
//判断文件是否存在
bool Exists();
//获取文件的大小
size_t Size();
//读取文件数据到*body中
bool GetContent(std::string *body);
//向文件写入数据
bool SetContent(const std::string& body);
//针对目录时创建目录
bool CreateDirectory();
};
}
#endif
- 判断文件是否存在
bool Exists()
{
int ret = access(_name.c_str(),F_OK);
if(ret!=0)
{
return false;
}
return true;
}
access()函数确定文件是否存在或者判断读写执行权限;确定文件夹是否存在即,检查某个文件的存取方式,比如说是只读方式、只写方式等。如果指定的存取方式有效,则函数返回0,否则函数返回-1。
函数原型:
int access(const char *path, int mode);
参数解析:
path:被检查权限的文件路径名。
mode:用于指定需要检查的权限类型,可以使用以下常量进行组合:
-
R_OK:检查读权限。
-
W_OK:检查写权限。
-
X_OK:检查执行权限。
-
F_OK:检查文件是否存在。
-
获取文件大小(属性)
size_t Size()
{
if(this->Exists()==false)
{
return 0;
}
//stat()获取指定文件名文件的属性
struct stat st;
int ret = stat(_name.c_str(),&st);
if(ret!=0)
{
return 0;
}
return st.st_size;
}
- 从文件中读取数据
bool GetContent(std::string *body)
{
std::ifstream ifs;
//二进制方式打开文件
ifs.open(_name,std::ios::binary);
if(ifs.is_open()==false)
{
std::cerr<<"open file failed"<<std::endl;
return false;
}
size_t flen=this->Size();
body->resize(flen);
//向body中写入数据
ifs.read(&(*body)[0],flen);
if(ifs.good()==false)
{
std::cerr<<"read file content failed!\n"<<std::endl;
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::cerr<<"open file failed"<<std::endl;
return false;
}
ofs.write(body.c_str(),body.size());
if(ofs.good()==false)
{
std::cerr<<"read file content failed!"<<std::endl;
ofs.close();
return false;
}
ofs.close();
return true;
}
- 针对目录时创建目录
bool CreateDirectory()
{
if(this->Exists())
{
return true;
}
int ret = mkdir(_name.c_str(),0777);
if(ret!=0)
{
std::cerr<<"mkdir failed!"<<std::endl;
return false;
}
return true;
}
- 测试
util_test.cc:
#include "util.hpp"
void FileTest()
{
Util::FileUtil("./www").CreateDirectory();
Util::FileUtil index_file("./www/index.html");
index_file.SetContent("<html></html>");
std::string body;
index_file.GetContent(&body);
std::cout<<"content:"<<body<<",size:"<<index_file.Size()<<std::endl;
}
int main()
{
FileTest();
return 0;
}
测试结果:
jia@jia-ubuntu:ClickVedio$ ./util_test
content:<html></html>,size:13
Json类
实现序列化以及反序列化。
#ifndef __MY_UTIL__
#define __MY_UTIL__
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <unistd.h>
#include <memory>
#include <sys/stat.h>
#include <jsoncpp/json/json.h>
namespace Util
{
class FileUtil
{
/.../
};
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::cerr<<"Serialize failed!"<<std::endl;
return false;
}
*body=ss.str();
return true;
}
static bool Deserialization(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::cerr << "Deserialization error"<<std::endl;
return false;
}
return true;
}
};
}
#endif
- 测试
void JsonTest()
{
std::string name ="小明";
int age =22;
float score[] = {99.9,100.0,98.5};
Json::Value val(Json::objectValue);
val["姓名"] = name;
val["年龄"] = age;
val["成绩"].append(score[0]);
val["成绩"].append(score[1]);
val["成绩"].append(score[2]);
std::string body;
Util::JsonUtil::Serialize(val,&body);
std::cout<<"Serialize:\n "<<body<<std::endl;
Json::Value val_(Json::objectValue);
Util::JsonUtil::Deserialization(body,&val_);
std::cout<<"Deserialization:\n"<<val_["姓名"].asString()<<std::endl;
std::cout<<val_["年龄"].asInt()<<std::endl;
int sz=val_["成绩"].size();
for(int i=0;i<sz;++i)
{
std::cout<<val_["成绩"][i].asFloat()<<std::endl;
}
}
测试结果:
jia@jia-ubuntu:ClickVedio$ ./util_test
Serialize:
{
"姓名" : "小明",
"年龄" : 22,
"成绩" :
[
99.900001525878906,
100,
98.5
]
}
Deserialization:
小明
22
99.9
100
98.5
数据管理模块
视频信息管理(数据表设计)
视频数据以及图片数据都存储在文件中,数据库中管理的时用户上传的视频信息:
- 视频ID
- 视频名称
- 视频描述信息
- 视频文件的url路径(相对于根目录的相对路径)
- 视频封面图片的URL路径(只是链接,加上相对根目录才是实际的存储路径)
下面是创建数据库以及相应的视频表的MySQL语句。
drop database if exists cv_system;
create database if not exists cv_system;
use cv_system;
create table if not exists 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路径(只是链接,加上相对根目录才是实际的存储路径)'
);
数据管理类设计
对数据表要进行的操作有:
- 新增视频信息
- 修改视频信息
- 删除视频信息
- 获取全部的视频信息
- 获取某个明确指定的视频信息
- 获取一个模糊指定的所有视频信息
视频信息由于字段较多,因此使用Json::Value
对象进行传递。
同时,满足多线程需求,添加锁。
🔺data.hpp:
#pragma once
#include "util.hpp"
#include <mysql/mysql.h>
#include <mutex>
namespace Util
{
static MYSQL* MysqlInit();
static void MysqlDestory();
static bool MysqlQuery(MYSQL *mysql, const std::string &sql);
class TableVideo{
private:
MYSQL *_mysql;
std::mutex _mutex;
public:
TableVideo();
~TableVideo();
bool Insert(const Json::Value &video);
bool Update(int video_id, const Json::Value &video);
bool Delete(int video_id);
bool SelectAll(Json::Value *videos);
bool SelectOne(int video_id, Json::Value *video);
bool SelectLike(const std::string &key, Json::Value *videos);
};
} // namespace Util
- 数据库的初始化、删除、以及执行操作
const char* host = "127.0.0.1";
const char* user="root";
const char* password="123456";
const char* db = "cv_system";
int port =0;
const char* character="utf8mb4";
static MYSQL* MysqlInit()
{
//初始化操作句柄
MYSQL*mysql= mysql_init(NULL);
if(mysql==NULL)
{
std::cout<<"init mysql instance error"<<std::endl;
return nullptr;
}
//连接mysql服务器
if(mysql_real_connect(mysql,host,user,password,db,port,NULL,0)==NULL)
{
std::cout<<"connect mysql server failed!"<<std::endl;
mysql_close(mysql);
return nullptr;
}
//设置客户端字符集
mysql_set_character_set(mysql,character);
return mysql;
}
static void MysqlDestory(MYSQL *mysql)
{
if(mysql != nullptr)
{
mysql_close(mysql);
}
}
static bool MysqlQuery(MYSQL *mysql, const std::string &sql)
{
int ret = mysql_query(mysql,sql.c_str());
if(ret!=0)
{
std::cout<<sql<<" : "<<mysql_error(mysql)<<std::endl;
return false;
}
return true;
}
- 新增视频信息
//视频的内容: id name info video image
bool Insert(const Json::Value &video)
{
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().length()==0)
{
return false;
}
sprintf(&sql[0],INSERT_VIDEO, video["name"].asCString(),video["info"].asCString(),
video["video"].asCString(),video["image"].asCString());
return MysqlQuery(_mysql,sql);
}
- 修改视频信息
//允许修改视频的名字以及简介
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;"
if(video["name"].asString().length()==0)
{
return false;
}
sprintf(&sql[0],UPDATE_VIDEO, video["name"].asCString(),video["info"].asCString(),
video_id);
return MysqlQuery(_mysql,sql);
}
- 删除视频信息
bool Delete(int video_id)
{
#define DELETE_VIDEO "delete from tb_video where id='%d';"
std::string sql;
sql.resize(1024);
sprintf(&sql[0],DELETE_VIDEO,video_id);
return MysqlQuery(_mysql,sql);
}
- 获取全部的视频信息
bool SelectAll(Json::Value *videos)
{
#define SELECTALL_VIDEO "select * from tb_video"
_mutex.lock();//查询与结果保存到本地的过程保证原子性
bool ret = MysqlQuery(_mysql,SELECTALL_VIDEO);
if(ret == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
if(res==nullptr){
std::cout<<"mysql store result failed!\n";
_mutex.unlock();
return false;
}
_mutex.unlock();
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;
}
- 获取某个明确指定的视频信息
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();//查询与结果保存到本地的过程保证原子性
bool ret = MysqlQuery(_mysql,sql);
if(ret == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
if(res==nullptr){
std::cout<<"mysql store result failed!\n";
_mutex.unlock();
return false;
}
_mutex.unlock();
int num_rows = mysql_num_rows(res);
if(num_rows!=1){
std::cout<<"have no data!"<<std::endl;
mysql_free_result(res);
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
(*video)["id"] = atoi(row[0]);
(*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();//查询与结果保存到本地的过程保证原子性
bool ret = MysqlQuery(_mysql,sql);
if(ret == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
if(res==nullptr){
std::cout<<"mysql store result failed!\n";
_mutex.unlock();
return false;
}
_mutex.unlock();
int num_rows = mysql_num_rows(res);
if(num_rows!=1){
std::cout<<"have no data!"<<std::endl;
mysql_free_result(res);
return false;
}
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;
}
🔺测试程序
#include "data.hpp"
void DataTest()
{
Util::TableVideo tb_video;
Json::Value video1,video2,video3;
Json::Value videos;
Json::Value video;
std::string body;
//插入视频1
video1["name"] = "黑袍纠察队";
video1["info"] = "和谐友爱,阳光向上";
video1["video"] = "/video/hero.mp4";
video1["image"] = "video/hero.jpg";
tb_video.Insert(video1);
//插入视频2
video2["name"] = "蜘蛛侠";
video2["info"] = "保护城市,打击罪恶";
video2["video"] = "/video/spider.mp4";
video2["image"] = "video/spider.jpg";
tb_video.Insert(video2);
//查询所有的视频
tb_video.SelectAll(&videos);
Util::JsonUtil::Serialize(videos,&body);
std::cout<<body<<std::endl;
videos.clear();
//将视频1修改成视频3
video3["name"] = "黑袍纠察队";
video3["info"] = "精彩纷呈";
video3["video"] = "/video/hero.mp4";
video3["image"] = "video/hero.jpg";
tb_video.Update(1,video3);
//查询一个视频1
tb_video.SelectOne(1,&video);
Util::JsonUtil::Serialize(video,&body);
std::cout<<body<<std::endl;
//查询所有视频名字中带有侠字的
tb_video.SelectLike("侠",&videos);
Util::JsonUtil::Serialize(videos,&body);
std::cout<<body<<std::endl;
videos.clear();
//删除视频序号为1的视频
tb_video.Delete(1);
tb_video.SelectAll(&videos);
Util::JsonUtil::Serialize(videos,&body);
std::cout<<body<<std::endl;
videos.clear();
}
int main()
{
DataTest();
return 0;
}
🔺测试结果
jia@jia-ubuntu:ClickVedio$ ./util_test
[
{
"id" : 1,
"image" : "video/hero.jpg",
"info" : "和谐友爱,阳光向上",
"name" : "黑袍纠察队",
"video" : "/video/hero.mp4"
},
{
"id" : 2,
"image" : "video/spider.jpg",
"info" : "保护城市,打击罪恶",
"name" : "蜘蛛侠",
"video" : "/video/spider.mp4"
}
]
{
"id" : 1,
"image" : "video/hero.jpg",
"info" : "精彩纷呈",
"name" : "黑袍纠察队",
"video" : "/video/hero.mp4"
}
[
{
"id" : 2,
"image" : "video/spider.jpg",
"info" : "保护城市,打击罪恶",
"name" : "蜘蛛侠",
"video" : "/video/spider.mp4"
}
]
[
{
"id" : 2,
"image" : "video/spider.jpg",
"info" : "保护城市,打击罪恶",
"name" : "蜘蛛侠",
"video" : "/video/spider.mp4"
}
]
网络通信接口设计
网络通信接口设计建立在http协议之上,http协议其实就是一种数据格式,是一个TCP传输,在应用层采用的一种数据特定格式。网络通信接口设计其实就是定义好,什么样的请求就是一个查询请求,什么样的请求就是一个删除请求…服务端所提供的的功能:新增视频,删除视频,修改视频,查询所有视频信息,查询单个视频,模糊匹配
借助restful风格进行接口设计
restful风格其实是建立在http协议上的,在其中定义了使用GET方法表示查询,使用POST方法表示新增,使用PUT方法表示修改,使用DELETE方法表示删除。并且正文资源数据采用JSON、XML数据格式。
REST
是 Representational State Transfer的缩写,一个架构符合REST原则,就称它为RESTful
架构- RESTful架构可以充分的利用HTTP 协议的各种功能,是HTTP 协议的最佳实践,正文通常采用JSON格式
- RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好restful使用五种HTTP 方法,对应CRUD(增删改查)操作
- GET表示查询获取
- POST对应新增
- PUT对应修改
- DELETE 对应删除
业务处理模块设计
业务处理模块负责与客户端进行网络通信,接收客户端的请求,然后根据请求信息,明确客户端端用户的意图,进行业务处理,并进行对应的结果响应。
在视频共享点播系统中,业务处理主要包含两大功能:
- 网络通信功能的实现
- 业务功能处理的实现
其中网络通信功能的实现借助httplib库即可方便的搭建http服务器完成。这也是将网络通信模块与业务处理模块合并在一起完成的原因。
而业务处理模块所要完成的业务功能主要有:
- 客户端的视频数据和信息上传
- 客户端的视频列表展示(视频信息查询)
- 客户端的视频观看请求(视频数据的获取)
- 客户端的视频其他管理(修改,删除)功能
🔺server.hpp
#include "data.hpp"
#include "cpp-httplib/httplib.h"
namespace Util {
#define WwW ROOT "./www"
#define VIDEO_ROOT "./video/"
#define IMAGE_ROOT "./image/"
//因为httplib基于多线程,因此数据管理对象需要在多线程中访问,为了便于访问定义全局变量
TableVideo *tablevideo= NULL;
//这里为了更加功能模块划分清晰一些,不使用1amda表达式完成,否则所有的功能实现集中到一个函数中太过庞大
class Server
{
private:
int _port;//服务器的监听端口
httplib::Server _srv;//用于搭建http服务器
private:
//对应的业务处理接口
static void Insert(const httplib::Request ®, httplib::Response &rsp);
static void Update(const httplib::Request ®, httplib::Response &rsp);
static void Delete(const httplib::Request ®, httplib::Response &rsp);
static void GetOne(const httplib::Request ®, httplib::Response &rsp);
static void GetAll(const httplib::Request ®, httplib::Response &rsp);
public:
Server(int port):_port(port){};
bool RunModule();//建立请求与处理函数的映射关系,设置静态资源根目录,启动服务器
};
};
- 新增视频
static void Insert(const httplib::Request &req, httplib::Response &rsp)
{
if(req.has_file("name") == false ||
req.has_file("info") == false ||
req.has_file("info") == 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");
std::string video_name = name.content;
std::string video_info = info.content;
std::string video_path = WwW_ROOT;
video_path+=VIDEO_ROOT;
video_path += video_name + video.filename;
std::string image_path = WwW_ROOT;
image_path += IMAGE_ROOT;
image_path += 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_json["image"] = IMAGE_ROOT + video_name + image.filename;
if(tablevideo->Insert(video_json)==false)
{
rsp.status=500;
rsp.body = R"({"result":false,"reason":"数据库新增失败"})";
rsp.set_header("Content-Type","application/json");
return;
}
}
- 更新视频
static void Update(const httplib::Request &req, httplib::Response &rsp)
{
//1.获取要修改的视频信息
std::string videoIdStr = req.matches[1];
int video_id = atoi(videoIdStr.c_str());
Json::Value video;
if(JsonUtil::Deserialization(req.body,&video)==false)
{
rsp.status=500;
rsp.body = R"({"result":false,"reason":"更新视频信息解析失败"})";
rsp.set_header("Content-Type","application/json");
return;
}
//2.修改数据库信息
if(tablevideo->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 Delete(const httplib::Request &req, httplib::Response &rsp)
{
//1.获取要删除的视频ID
std::string videoIdStr = req.matches[1];
int video_id = atoi(videoIdStr.c_str());
//2.删除视频文件
Json::Value video;
bool res = tablevideo->SelectOne(video_id,&video);
if(res == false)
{
rsp.status=500;
rsp.body = R"({"result":false,"reason":"不存在视频信息"})";
rsp.set_header("Content-Type","application/json");
return;
}
std::string root = WwW_ROOT;
std::string video_path = root + video["video"].asString();
std::string image_path = root + video["image"].asString();
remove(video_path.c_str());
//3.删除封面图片文件
remove(image_path.c_str());
//4.删除数据库信息
res = tablevideo->Delete(video_id);
if(res == false)
{
rsp.status=500;
rsp.body = R"({"result":false,"reason":"删除数据库信息失败"})";
rsp.set_header("Content-Type","application/json");
return;
}
return;
}
- 查询单个视频
static void GetOne(const httplib::Request &req, httplib::Response &rsp)
{
//1.获取视频的ID
std::string videoIdStr = req.matches[1];
int video_id = atoi(videoIdStr.c_str());
//2.在数据库中查询指定的视频信息
Json::Value video;
bool res = tablevideo->SelectOne(video_id,&video);
if(res == 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 GetAll(const httplib::Request &req, httplib::Response &rsp)
{
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){
if(tablevideo->SelectAll(&videos)==false){
rsp.status=500;
rsp.body = R"({"result":false,"reason":"查询所有的视频失败"})";
rsp.set_header("Content-Type","application/json");
return;
}
}else{
if(tablevideo->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;
}
- 业务结合网络通信
//建立请求与处理函数的映射关系,设置静态资源根目录,启动服务器
bool RunModule()
{
//1.完成资源的初始化--数据管理模块,创建指定的目录
tablevideo = new TableVideo();
FileUtil(WwW_ROOT).CreateDirectory();
std::string video_real_path = WwW_ROOT;
video_real_path += VIDEO_ROOT;
FileUtil(video_real_path).CreateDirectory();
std::string image_real_path = WwW_ROOT;
image_real_path += IMAGE_ROOT;
FileUtil(image_real_path).CreateDirectory();
//2.搭建http服务器
//2.1设置静态资源根目录
_srv.set_mount_point("/",WwW_ROOT);
//2.2添加请求处理函数映射关系
_srv.Post("/video",Insert);
_srv.Delete("/video/(\\d+)",Delete);
_srv.Put("/video/(\\d+)",Update);
_srv.Get("/video/(\\d+)",GetOne);
_srv.Get("/video",GetAll);
//2.3启动服务器
_srv.listen("0.0.0.0",_port);
return true;
}
🔺测试-使用postman模拟前端的多种请求
- 新增两个视频
- 查看文件目录中是否有新增视频
jia@jia-ubuntu:www$ tree ./
./
├── image
│ ├── 小狗image.jpg
│ └── dogimage.jpg
├── index.html
└── video
├── 小狗video.mp4
└── dogvideo.mp4
2 directories, 5 files
- 查询数据库中的所有视频信息
- 查询单个视频
- 模糊查询视频
- 修改单个指定视频
- 删除单个指定视频
前端界面
主页面
主页面所要实现的功能
- 展示所有视频
- 允许用户点击视频,跳转到视频视频播放页面。
- 允许用户在主页上传新视频
获取所有视频
<script>
var app = new Vue({
el: '#myapp',
data: {
author: 'Jia',
videos: []
},
methods: {
get_allvideos: function () {
$.ajax({
type: "get",
url: "/video",
context: this,
success: function (result, status, xhr) {
this.videos = result;
}
})
}
}
})
app.get_allvideos();
</script>
展示视频列表并允许用户点击视频,跳转到视频视频播放页面
<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, index) in videos" :key="video.id">
<!-- 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 v-cloak>{{ formatDuration(videoDurations[index])}}</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>
</div>
</div>
</article>
</div>
<div class="clearfix spacer"></div>
</div>
</div>
</section>
允许用户在主页上传新视频
<div id="addvideo" 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>
播放页面
- 播放视频
- 修改视频
- 删除视频
完成上述功能所需要的函数
<script>
var app = new Vue({
el: '#myapp',
data: {
author: 'Jia',
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({
type: "get",
url: "/video/" + id,
context: this,
success: function (result, status, xhr) {
this.video = result;
}
});
},
update_video: function () {
$.ajax({
type: "put",
url: "/video/" + this.video.id,
data: JSON.stringify(this.video),
context: this,
success: function (result, status, xhr) {
alert("修改视频成功");
window.location.reload();
}
});
},
delete_video: function () {
$.ajax({
type: "delete",
url: "/video/" + this.video.id,
context: this,
success: function (result, status, xhr) {
alert("删除视频成功");
window.location.href="/index.html";
}
});
}
},
mounted() {
this.get_video(); // 在 Vue 实例挂载后调用 get_video
},
watch: {
video(newVal) {
// 强制重新加载视频
this.$nextTick(() => {
var videoElement = document.querySelector('video');
if (videoElement) {
videoElement.load();
}
});
}
}
});
</script>
播放视频
<!-- 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-10 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">
<video controls class="embed-responsive-item">
<source v-bind:src="video.video" type="video/mp4">
</video>
</div>
</div>
<div class="clearfix spacer"></div>
<!-- DETAILS -->
<div class="video-content">
<h2 class="title main-head-title">视频描述</h2>
<p v-cloak>{{video.info}}</p>
</div>
<div class="clearfix spacer"></div>
</article>
</div>
<!-- VIDEO SIDE BANNERS -->
<div class="col-lg-2 hidden-md hidden-sm">
</div>
</div>
<div class="clearfix spacer"></div>
<div class="row">
</div>
</div>
</div>
修改视频
<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="put">
<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>
删除视频
<button type="button" class="access-btn" v-on:click="delete_video()">删除视频</button>
删除视频直接调用删除函数就可以。
项目总结
项目回顾
本项目是一个视频点播系统,使用C/C++语言进行开发,结合多种技术栈实现了从视频上传、存储到播放的全流程管理。项目运行在Ubuntu 20.04或者CentOS 7系统上,使用MariaDB作为数据库进行视频数据的存储和管理,使用jsoncpp库进行JSON数据的解析和生成,使用httplib库搭建HTTP服务器实现前后端交互。
项目结构
项目主要由以下几个模块组成:
- 视频上传模块:负责视频文件的上传,接受前端上传的文件并保存到服务器指定目录。
- 视频信息管理模块:负责视频信息的存储与管理,包括视频名称、简介、文件路径等,使用MariaDB进行存储。
- 视频播放模块:提供视频播放功能,支持在线播放。
- 前后端交互模块:通过httplib库搭建HTTP服务器,处理前端的请求并返回相应的数据。
关键技术点
- 视频上传:
- 使用HTTP POST方法上传视频文件。
- 后端接收文件并保存到服务器指定目录。
- 在数据库中记录视频的相关信息。
- 视频信息存储与管理:
- 使用MariaDB存储视频的元信息,如视频名称、简介、文件路径等。
- 设计数据库表结构以支持视频信息的存储和查询。
- 视频播放:
- 提供视频文件的HTTP访问路径,前端通过video标签实现在线播放。
- 支持获取视频文件的元信息,如视频时长。
- 前后端交互:
- 使用httplib库搭建HTTP服务器。
- 处理前端的请求,如获取视频列表、上传视频等。
- 返回JSON格式的数据,使用jsoncpp库进行JSON数据的解析和生成。
总结
本项目通过C/C++语言结合多种库和工具实现了一个功能完整的视频点播系统。项目采用模块化设计,便于维护和扩展。通过MariaDB存储视频信息,使用httplib搭建HTTP服务器,实现了前后端的高效交互。在开发过程中,使用Makefile进行编译和构建,确保项目的可移植性和可维护性。
项目在实现过程中充分利用了C++11的特性和STL,提高了代码的简洁性和效率。通过jsoncpp库处理JSON数据,通过httplib库实现HTTP通信,极大简化了开发难度。在实际应用中,可以根据需要对各模块进行进一步优化和扩展,以满足不同场景的需求。