文章目录
- 视频点播
- 技术栈与项目环境
- 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.html2 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;//插入视频1video1["name"] = "黑袍纠察队";video1["info"] = "和谐友爱,阳光向上";video1["video"] = "/video/hero.mp4";video1["image"] = "video/hero.jpg";tb_video.Insert(video1);//插入视频2video2["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修改成视频3video3["name"] = "黑袍纠察队";video3["info"] = "精彩纷呈";video3["video"] = "/video/hero.mp4";video3["image"] = "video/hero.jpg";tb_video.Update(1,video3);//查询一个视频1tb_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.获取要删除的视频IDstd::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.获取视频的IDstd::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.mp42 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"><iclass="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通信,极大简化了开发难度。在实际应用中,可以根据需要对各模块进行进一步优化和扩展,以满足不同场景的需求。