0


【项目】视频点播系统

目录


一、项目介绍

1. 对视频点播系统的认识

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

2. 服务端功能模块划分

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

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

二、环境搭建

2.1 升级GCC

由于在该项目中会引入许多第三方库,比如

httplib

库,该库就会要求gcc编译器必须是较新的版本。如果使用老版本的编译器要么编译不通过,要么就会运行报错。因此我们需要对gcc进行升级,以下是升级至 gcc 7.3 的方法:

  • 查看当前gcc版本
gcc --version
  • 安装centos-release-scl
sudo yum install centos-release-scl-rh centos-release-scl
  • 安装devtoolset
sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c++

这里需要注意一下,如果想安装7.版本的,就改成devtoolset-7-gcc,以此类推。

  • 激活对应的devtoolset
source /opt/rh/devtoolset-7/enable

此时GCC就成功升级到了 7.3 版本。

需要注意的是scl命令启用只是临时的,退出 shell 或重启就会恢复原系统gcc版本。如果想要一启动shell就立即生效可以进行以下配置:

echo"source /opt/rh/devtoolset-7/enable">> ~/.bashrc

即把启动scl的命令添加到文件

.bashrc

中,每次启动shell就会执行该语句。

2.2 安装JsonCpp库

JSON 是一种轻量级的数据交换格式。它可以代表数字、字符串、值的有序序列和名称/值的集合对。

JsonCpp 是一个C++库,允许操作 JSON 值,包括字符串的序列化和反序列化。它还可以保存反序列化/序列化步骤中的现有注释,方便
用于存储用户输入文件的格式。

以下是安装JsonCpp的命令:

sudo yum install epel-release
sudo yum install jsoncpp-devel

安装好的JsonCpp存放在

/usr/include/jsoncpp/json

目录下:

2.3 引入httplib库

cpp-httplib

是个开源的库,是一个c++封装的http库,使用这个库可以在linux、windows平台下完成http客户端、http服务端的搭建,这是一个多线程“阻塞”HTTP 库。使用起来非常方便,只需要包含头文件

httplib.h

即可。源码地址

获取

httplib

库:

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

2.4 MySQL数据库及开发包安装

此次项目的开发我们使用的数据库是MariaDB,MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。

以下是安装MariaDB数据库的步骤:

  • 安装 MariaDB 服务
sudo yum install-y mariadb-server
  • 安装 MariaDB 命令行客户端
sudo yum install-y mariadb 
  • 安装 MariaDB C library
sudo yum install-y mariadb-libs

其实 MariaDB 的服务中包含了客户端和相关的C语言接口,因此只需要安装

mariadb-server

即可。

  • 安装 MariaDB 开发包
sudo yum install-y mariadb-devel

安装完成后,以下是启动数据库的命令:

  • 启动服务
systemctl start mariadb

执行该命令启动服务之后,如果重启或者关机了,下次还需要重新启动,可以执行下列指令设置开启自启。

  • 设置服务开机自启
systemctl enable mariadb
  • 查看服务状态
systemctl status mariadb

启动服务成功后,我们可以看到MariaDB 的状态是 active(running):

● mariadb.service - MariaDB database server
Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; vendor preset: disabled)
Active:

active (running)

since Wed 2023-03-01 20:25:02 CST; 1min 32s ago
Main PID: 7678 (mysqld_safe)
CGroup: /system.slice/mariadb.service
├─7678 /bin/sh /usr/bin/mysqld_safe --basedir=/usr
└─7844 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --log-error=/var/log/mariadb/mariadb.log --pid-file=/var/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql…

然后可以测试连接数据库:

  • 使用命令行客户端尝试连接
mysql -uroot

出现以下结果就代表连接成功了:

Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 5.5.68-MariaDB MariaDB Server

Copyright © 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.

MariaDB [(none)]>

  • 查看 mariadb 版本号
MariaDB [(none)]>select version();
+----------------+
| version()|
+----------------+
|5.5.68-MariaDB |
+----------------+
1 row inset(0.00 sec)

更改配置:

为了在使用数据库时支持中文,还需要进行以下配置。

  • /etc/my.cnf.d/client.cnf文件的 [client]下添加default-character-set = utf8

  • /etc/my.cnf.d/mysql-client.cnf文件下的[mysql]下添加default-character-set = utf8
  • /etc/my.cnf.d/server.cnf下的[mysqld]添加以下语句
collation-server = utf8_general_ci
init-connect ='SET NAMES utf8'
character-set-server = utf8
sql-mode = TRADITIONAL

三、第三方库的认识

3.1 认识JsonCpp

首先认识Json:

Json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。

例如:使用Json来表示张三同学的学生信息。

constchar* name1 ="张三";int age1 =18;float scores1[3]={60.0,59.5,61.0};constchar* name2 ="李四";int age2 =19;float scores2[3]={69.0,58.5,64.0};// Json这种数据交换格式就是将这样的多种数据对象封装成一个字符串:[{"姓名":"张三","年龄":18,"成绩":[860.0,59.5,61.0]},{"姓名":"李四","年龄":19,"成绩":[69.0,58.5,64.0]},]

Json可以封装的数据对象可以是:对象,数组,字符串,数字等等。

认识JsonCpp:
JsonCpp 库用于实现 Json 格式的序列化和反序列化,完成将多个数据对象组织成为 Json 格式字符串,以及将 Json
格式字符串解析得到多个数据对象的功能。

其中主要借助三个类以及对于的几个成员函数完成的。

Json 数据对象:

classJson::Value{
    Value &operator=(const Value &other);// Value重载了[]和=,因此所有的赋值和获取数据都可以通过
    Value&operator[](const std::string& key);// 简单的方式完成 val["姓名"] = "小明";
    Value&operator[](constchar* key);
    Value removeMember(constchar* key);// 移除元素const Value&operator[](ArrayIndex index)const;// val["成绩"][0]
    Value&append(const Value& value);// 添加数组元素val["成绩"].append(88);
    ArrayIndex size()const;// 获取数组元素个数 val["成绩"].size();
    std::string asString()const;// 转string string name = val["name"].asString();constchar*asCString()const;// 转char* char *name = val["name"].asCString();
    Int asInt()const;// 转int int age = val["age"].asInt();floatasFloat()const;// 转floatboolasBool()const;// 转 bool}

Json序列化类:

// 建议低版本使用classJSON_API Writer {virtual std::string write(const Value& root)=0;}classJSON_API FastWriter :public Writer {virtual std::string write(const Value& root);}classJSON_API StyledWriter :public Writer {virtual std::string write(const Value& root);}// 建议较高版本使用,如果用低版本接口可能报错classJSON_API StreamWriter {virtualintwrite(Value const& root, std::ostream* sout)=0;}classJSON_API StreamWriterBuilder :public StreamWriter::Factory {virtual StreamWriter*newStreamWriter()const;}

Json反序列化类:

// 低版本用起来更简单classJSON_API Reader {boolparse(const std::string& document, Value& root,bool collectComments =true);}// 高版本更推荐classJSON_API CharReader {virtualboolparse(charconst* beginDoc,charconst* endDoc,
    Value* root, std::string* errs)=0;}classJSON_API CharReaderBuilder :public CharReader::Factory {virtual CharReader*newCharReader()const;}

3.2 JsonCpp实现序列化

#include<iostream>#include<memory>#include<sstream>#include<string>#include<jsoncpp/json/json.h>intmain(){constchar* name ="张三";int age =18;float scores[3]={60.0,59.5,61.0};
    Json::Value value;
    value["姓名"]= name;
    value["年龄"]= age;
    value["成绩"].append(scores[0]);
    value["成绩"].append(scores[1]);
    value["成绩"].append(scores[2]);
    
    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 <<"write falied!"<< std::endl;return-1;}
    
    std::cout << ss.str()<< std::endl;return0;}

运行结果:

3.3 JsonCpp实现反序列化

#include<iostream>#include<memory>#include<sstream>#include<string>#include<jsoncpp/json/json.h>intmain(){
    std::string str =R"({"姓名":"李四", "年龄":19, "成绩":[69.0, 58.5, 64.0]})";
    Json::Value root;
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader>cr(crb.newCharReader());

    std::string err;
    cr->parse(str.c_str(), str.c_str()+ str.size(),&root,&err);

    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;for(auto it = root["成绩"].begin(); it != root["成绩"].end();++it)
        std::cout << it->asString()<< std::endl;return0;}

运行结果:

3.4 认识MySQL数据库的API

MySQL C语言API就是用C语言编写的MySQL编程接口,使用这些接口函数可以实现对MySQL数据库的查询等操作。以下是MySQL的C语言API接口。

  1. MySQL句柄初始化
MYSQL*mysql_init(MYSQL* mysql);

【说明】

  • 参数为空则动态申请句柄空间进行初始化,如果调用成功则返回MySQL句柄,失败则返回NULL

句柄是什么?

句柄(handle)是C++程序设计中经常提及的一个术语。它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念。句柄一般是指获取另一个对象的方法——一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系。

在C++中,要访问一个对象,通常可以建立一个指向对象的指针。但是在很多具体的应用中,直接用指针代表对象并不是一个好的解决方案。因此引入了句柄的概念。

  1. 连接MySQL服务器
MYSQL*mysql_real_connect(MYSQL* mysql,constchar* host,constchar* user,constchar* passwd,constchar* db,unsignedint port,constchar* unix_socket,unsignedlong client_flag);

【参数说明】

  • mysql:初始化完成的句柄
  • host:连接的MySQL服务器的地址
  • user:连接的数据库的用户名
  • passwd:该用户连接数据库的密码
  • db:默认选择的数据库名称
  • port:连接的MySQL服务器的端口,默认0是3306端口
  • unix_socket:通信管道文件或者socket文件,通常置为NULL
  • client_flag:客户端的标志位,通常置为0

【返回值】

  • 连接成功返回MySQL句柄,失败则返回NULL
  1. 设置当前客户端的字符集
intmysql_set_character_set(MYSQL* mysql,constchar* csname);

【参数说明】

  • mysql:初始化完成的句柄
  • csname:字符集名称,通常为utf8

【返回值】

  • 调用成功返回0,失败则返回非0
  1. 选择操作的数据库
intmysql_select_db(MYSQL* mysql,constchar* db);

【参数说明】

  • mysql:初始化完成的句柄
  • db:要进行操作的数据库名称

【返回值】

  • 调用成功返回0,失败则返回非0
  1. 执行SQL语句
intmysql_query(MYSQL* mysql,constchar* stmt_str);

【参数说明】

  • mysql:初始化完成的句柄
  • stmt_str:要执行的sql语句

【返回值】

  • 调用成功返回0,失败则返回非0
  1. 保存查询结果到本地
MYSQL_RES*mysql_store_result(MYSQL* mysql);

【参数说明】

  • mysql:初始化完成的句柄

【返回值】

  • 调用成功返回结果集的首地址,失败则返回NULL
  1. 获取结果集中的行数
uint64_tmysql_num_rows(MYSQL_RES* result);

【参数说明】

  • result:保存到本地的结果集

【返回值】

  • 返回结果集中数据的条数,也是是行数。
  1. 获取每一条结果集的列数
unsignedintmysql_num_fields(MYSQL_RES* result);

【参数说明】

  • result:保存到本地的结果集

【返回值】

  • 返回结果每一条集中数据的列数
  1. 遍历结果集
MYSQL_ROW mysql_fecth_row(MYSQL_RES* result);

【参数说明】

  • result:保存到本地的结果集

【返回值】

  • MYSQL_ROW实际上是一个char** 类型的二级指针将每一条数据表示成了字符串指针数组。比如row[0]表示第0行,row[1]表示第一行。
  • 并且这个接口会保存当前读取的结果集位置,每次获取的都是下一条数据。
  1. 释放结果集
voidmysql_free_result(MYSQL_RES* result);

【说明】

  • result是保存到本地的结果集,无返回值。

注意一定要释放结果集,否则会造成内存泄漏。

  1. 关闭数据库客户端的连接,销毁MySQL句柄
voidmysql_close(MYSQL* mysql);
  1. 获取MySQL接口中执行错误的原因
constchar*mysql(MYSQL* mysql);

【说明】

  • 该函数会返回执行sql语句失败的原因。

3.5 使用MySQL的API实现对数据的增删改查

  1. 创建一个测试所用数据库和表
createdatabaseifnotexists test_db;use test_db;createtableifnotexists test_tb(
    id intprimarykeyauto_increment,
    age int,
    name varchar(32),
    score decimal(4,2));

创建成功使用

desc test_tb;

语句查看对

test_tb

的描述:

对数据库进行增删改查的代码样例:

  • 向数据库里面增加数据
#include<stdio.h>#include<unistd.h>#include<string.h>#include<mysql/mysql.h>//添加数据intadd(MYSQL* mysql){constchar* sql ="insert into test_tb values (null, 18, '张三', 61.5)";int ret =mysql_query(mysql, sql);if(ret !=0){printf("query %s failed, error massage: %s\n", sql,mysql_error(mysql));return-1;}return0;}intmain(){//初始化操作句柄
    MYSQL* mysql =mysql_init(NULL);if(NULL== mysql){printf("init mysql handle failed!\n");return-1;}//连接mysql服务器if(mysql_real_connect(mysql,"127.0.0.1","root","","test_db",0,NULL,0)==NULL){printf("mysql connect failed!\n");return-1;}//设置客户端字符集mysql_set_character_set(mysql,"utf8");add(mysql);//关闭句柄mysql_close(mysql);return0;}

编译指令:

gcc -o mysql_test mysql_test.c -L/usr/lib64/mysql -lmysqlclient

注意在进行编译的时候需要使用

-L

指定

mysqlclient

动态库所在的路径,因为它不是之间存在于

/usr/lib64/

路径下的。

运行结果:

再次执行:

  • 修改数据库中的数据
//修改intmod(MYSQL* mysql){constchar* sql ="update test_tb set name = '李四' where id = 2";int ret =mysql_query(mysql, sql);if(ret !=0){printf("query %s failed, error massage: %s\n", sql,mysql_error(mysql));return-1;}return0;}

编译执行后的结果:

  • 删除数据库中的数据
//删除intdel(MYSQL* mysql){constchar* sql ="delete from test_tb where name='张三'";int ret =mysql_query(mysql, sql);if(ret !=0){printf("query %s failed, error massage: %s\n", sql,mysql_error(mysql));return-1;}return0;}

编译执行后的结果:

  • 查询数据库中的数据
//查询intget(MYSQL* mysql){constchar* sql ="select * from test_tb";int ret =mysql_query(mysql, sql);if(ret !=0){printf("query %s failed, error massage: %s\n", sql,mysql_error(mysql));return-1;}//保存结果集到本地
    MYSQL_RES* result =mysql_store_result(mysql);if(NULL== result){printf("mysql store result failed! error message: %s\n",mysql_error(mysql));return-1;}//获取结果集的行数int row =mysql_num_rows(result);//获取结果集的列数int col =mysql_num_fields(result);printf("%10s%10s%10s%10s\n","ID","姓名","年龄","成绩");for(int i =0; i < row;++i){//获取当前行的数据
        MYSQL_ROW row_data =mysql_fetch_row(result);for(int j =0; j < col;++j){printf("%10s", row_data[j]);}printf("\n");}//释放结果集mysql_free_result(result);return0;}

编译执行的结果:

以上就是使用MySQL的C语言API对数据库进行增删改查的存在。

3.6 认识httplib库

httplib

库是一个基于

C++11

的跨平台的

HTTP/HTTPS

库,它的安装非常简单,只需要将

httplib.h

包含在代码中即可。

httplib

库实际上是用于搭建一个简单的

HTTP

服务器或者客户端的库,使用这种第三方网络库,可以帮助我们省去自己搭建服务器或者客户端的时间,把更多的精力投入到具体的业务处理当中,提高开发的效率。

以下是对

httplib

库实现的简单剖析,该库中主要包含四个类:发送请求

Request

类,响应数据

Response

类,服务端

Server类

,客户端

Client

类。

发送请求

Request

类的组成:

namespace httplib
{structMultipartFormData{
    std::string name;
    std::string content;
    std::string filename;
    std::string content_type;};using MultipartFormDataItems = std::vector<MultipartFormData>;structRequest{
        std::string method;//存放请求方法
        std::string path;//存放请求资源路径
        Headers headers;//存放头部字段的键值对map
        std::string body;//存放请求正文// for server
        std::string version;//存放协议版本
        Params params;//存放url中查询字符串 key=val&key=val的 键值对map
        MultipartFormDataMap files;//存放文件上传时,正文中的文件信息
        Ranges ranges;boolhas_header(constchar*key)const;//判断是否有某个头部字段
        std::string get_header_value(constchar*key, size_t id =0)const;//获取头部字段值voidset_header(constchar*key,constchar*val);//设置头部字段boolhas_file(constchar*key)const;//文件上传中判断是否有某个文件的信息
        MultipartFormData get_file_value(constchar*key)const;//获取指定的文件信息};}

响应数据

Response

类:

namespace httplib
{structResponse{
        std::string version;//存放协议版本int status =-1;//存放响应状态码
        std::string reason;
        Headers headers;//存放响应头部字段键值对的map
        std::string body;//存放响应正文
        std::string location;// Redirect location重定向位置voidset_header(constchar*key,constchar*val);//添加头部字段到headers中voidset_content(const std::string &s,constchar*content_type);//添加正文到body中voidset_redirect(const std::string &url,int status =302);//设置全套的重定向信息};}

服务端

Server类

namespace httplib
{classServer{using Handler = std::function<void(const Request &, Response &)>;//函数指针类型using Handlers = std::vector<std::pair<std::regex, Handler>>;//存放请求-处理函数映射
        std::function<TaskQueue *(void)> new_task_queue;//线程池
        Server &Get(const std::string &pattern, Handler handler);//添加指定GET方法的处理映射
        Server &Post(const std::string &pattern, Handler handler);
        Server &Put(const std::string &pattern, Handler handler);
        Server &Patch(const std::string &pattern, Handler handler);
        Server &Delete(const std::string &pattern, Handler handler);
        Server &Options(const std::string &pattern, Handler handler);boollisten(constchar*host,int port,int socket_flags =0);//开始服务器监听boolset_mount_point(const std::string &mount_point,const std::string &dir,
        Headers headers =Headers());//设置http服务器静态资源根目录};}

客户端

Client

类:

namespace httplib
{classClient{//创建clientClient(host,port);Get()Post()Put()...};}

3.7 使用httplib库搭建简单的服务器

前端代码样例:

<html><head><metacontent="text/html; charset=utf-8"http-equiv="content-type"/></head><body><h1>Hello HTTP</h1><formaction="/multipart"method="post"enctype="multipart/form-data"><inputtype="file"name="file1"><inputtype="submit"value="上传"></form></body></html>

这段代码是一个简单的HTML页面,包括一个标题"Hello HTTP"以及一个表单,允许用户上传文件。

在表单中,使用了HTTP POST方法并将

enctype

属性设置为

multipart/form-data

。这是因为表单旨在上传文件,需要这种类型的编码。文件输入字段使用

input

标签创建,类型属性设置为

file

,名称属性设置为

file1

。最后,使用

input

标签创建一个提交按钮,类型属性设置为

submit

,值属性设置为"上传"。

当用户单击提交按钮时,将拟定的表单数据,包括上传的文件,一起发送到表单标签中指定的服务器的文件中,即

/multipart

以下是使用

httplib

库实现的简单服务端代码:

#include"./httplib.h"#include<iostream>usingnamespace httplib;voidHandler(const Request &req, Response& rsp){
    rsp.body ="Hello World!";
    rsp.status =200;//可以忽略,httplib默认会加上一个200的状态码}voidNumbers(const Request &req, Response& rsp){//matches: 存放正则表达式匹配的规则数据 /numbers/123 matches[0] = "/numbers/123", matches[1] = "123"
    std::string num = req.matches[1];
    rsp.set_content(num,"text/plain");
    rsp.status =200;}voidMultipart(const Request &req, Response& rsp){if(req.has_file("file1")==false){
        rsp.status =400;return;}
    MultipartFormData file =req.get_file_value("file1");
    std::cout << file.filename << std::endl;//区域文件名称
    std::cout << file.content << std::endl;//区域文件内容}intmain(){
    Server server;//设置一个静态资源根目录---为当前的www目录
    server.set_mount_point("/","./www");//添加请求---处理函数映射信息
    server.Get("/hi", Handler);//正则表达式中:\d表示数字,+表示匹配前面的字符一次或多次,()表示单独捕捉数据 /numbers/123
    server.Get("/numbers/(\\d+)", Numbers);
    
    server.Post("/multipart", Multipart);
    
    server.listen("0.0.0.0",8080);return0;}

其中各个函数的作用:

  • Handler:回调函数,用于处理服务器的/hi路径的GET请求。当客户端向服务器发起GET请求并且请求路径是/hi时,该函数将被调用。并将响应的正文设置成 " Hello World!",响应状态设置为200。
  • Multipart:回调函数,用于处理服务器的/numbers/xxx路径的GET请求,其中xxx表示数字。当客户端向服务器发起GET请求并且请求路径是/numbers/xxx时,该函数将被调用。并且从请求对象的matches属性中提取数字并将其作为响应的正文,然后将响应状态码设置为200。
  • Multipart:回调函数,用于处理服务器的/multipart路径的POST请求。当客户端向服务器发起POST请求并且请求路径是/multipart时会调用该函数。该函数会检查请求是否包含file1的文件,如果不存在,则将响应状态码设置为400。如果文件存在则将文件名和文件内容输出到控制台。
  • 在主函数中,服务器被创建并配置三个请求处理函数:HandlerNumbersMultipart,并且指定服务端静态资源根目录为./www。然后监听8080的端口号等待客户端连接。

四、服务端工具类的实现

4.1 文件工具类的设计

在视频点播系统中会涉及到文件的上传,需要对上传的文件进行备份存储,因此首先设计封装一个文件操作类,将这个类封装完成后,则在任意模块中对文件进行操作时都将得到简化。

该类主要涉及到的功能是:获取文件的大小、判断文件是否存在、向文件中写入数据、从文件中读取数据、针对目标文件创建目录。具体实现如以下代码:

#include<iostream>#include<fstream>#include<string>#include<unistd.h>#include<sys/stat.h>namespace aod
{classFileUtil{private:
          std::string _name;//文件路径名称public:FileUtil(const std::string& name):_name(name){}//判断文件是否存在boolExists(){int ret =access(_name.c_str(), F_OK);//access的第一个参数是文件名,第二个参数如果传入F_OK用于判断文件是否存在if(ret !=0){
                  std::cout <<"file is not exist!"<< std::endl;returnfalse;}returntrue;}//获取文件大小
          size_t Size(){if(this->Exists()==false){return0;}//stat接口用于获取文件属性,结构体struct stat中的st_size就是文件大小structstat st;int ret =stat(_name.c_str(),&st);if(ret !=0){
                  std::cout <<"get file size failed!"<< std::endl;return0;}return st.st_size;}//获取文件内容boolGetContent(std::string* content){
              std::ifstream ifs;
              ifs.open(_name.c_str(), std::ios::binary);if(ifs.is_open()==false){
                  std::cout <<"open file failed!"<< std::endl;returnfalse;}
              size_t flen =this->Size();
              content->resize(flen);
              ifs.read(&(*content)[0], flen);if(ifs.good()==false){
                  std::cout <<"read file content failed!"<< std::endl;returnfalse;}

              ifs.close();returntrue;}//向文件中写入内容boolSetContent(const std::string& content){
              std::ofstream ofs;
              ofs.open(_name.c_str(), std::ios::binary);if(ofs.is_open()==false){
                  std::cout <<"open file failed"<< std::endl;returnfalse;}
              
              ofs.write(content.c_str(), content.size());if(ofs.good()==false){
                  std::cout <<"write file content failed!"<< std::endl;returnfalse;}

              ofs.close();returntrue;}//根据文件名称创建目录boolCreateDirectory(){if(this->Exists())returntrue;int ret =mkdir(_name.c_str(),0777);if(ret !=0){
                std::cout <<"create directory failed!"<< std::endl;returnfalse;}returntrue;}};}#endif

【说明】

  1. 在判断文件是否存在时使用的接口是access,调用成功返回 0 ,失败则返回 -1 ,其定义如下:
#include<unistd.h>intaccess(constchar*path,int amode);

其中

path

是文件路径名称,

amode

用于指定

access

函数的作用,其取值如下:

F_OK 值为0,判断文件是否存在
 
X_OK 值为1,判断对文件是可执行权限
 
W_OK 值为2,判断对文件是否有写权限
 
R_OK 值为4,判断对文件是否有读权限
 
注:后三种可以使用或“|”的方式,一起使用,如W_OK|R_OK
  1. 在获取文件大小的函数中使用了一个接口stat,其功能是获取文件的属性,调用成功返回 0 ,失败则返回 - 1,定义如下:
#include<sys/stat.h>intstat(constchar*path,structstat*buf);

其中

path

代表文件的路径,

struct stat

是一个描述文件的结构体,其定义如下:

structstat{
    dev_t     st_dev;/* ID of device containing file */
    ino_t     st_ino;/* inode number */
    mode_t    st_mode;/* protection */
    nlink_t   st_nlink;/* number of hard links */
    uid_t     st_uid;/* user ID of owner */
    gid_t     st_gid;/* group ID of owner */
    dev_t     st_rdev;/* device ID (if special file) */
    off_t     st_size;/* total size, in bytes */
    blksize_t st_blksize;/* blocksize for file system I/O */
    blkcnt_t  st_blocks;/* number of 512B blocks allocated */
    time_t    st_atime;/* time of last access */
    time_t    st_mtime;/* time of last modification */
    time_t    st_ctime;/* time of last status change */};
  1. 在创建文件目录时使用的函数是mkdir,调用成功返回 0, 失败则返回 -1,其定义如下:
#include<sys/stat.h>intmkdir(constchar*path, mode_t mode);

其中

path

表示要创建的文件名,

mode

表示赋予给新创建的文件权限。

4.2 Json 工具类的设计

Json工具类包含的功能有两个,一个是将

Json::Value

对象序列化成为一个字符串,另一个是将字符串反序列化成为

Json::Value

对象。具体实现代码如下:

//Json工具类classJsonUtil{public://序列化staticboolSerialize(const Json::Value& root, std::string* str){
              Json::StreamWriterBuilder swb;
              std::unique_ptr<Json::StreamWriter>sw(swb.newStreamWriter());
              
              std::stringstream ss;int ret = sw->write(root,&ss);if(ret !=0){
                  std::cout <<"Serialize failed!"<< std::endl;returnfalse;}*str = ss.str();returntrue;}//反序列化staticboolDeserialize(const std::string& str, 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::cout <<"Deserialize failed! error message: "<< err << std::endl;returnfalse;}returntrue;}};

五、数据管理模块的实现

5.1 视频数据表的设计

在视频点播系统中,视频数据和图片数据都存储在文件中,所有需要使用数据库来管理用户上传的每个视频的属性信息。这里只需要创建一个简单的视频信息表即可,其属性如下:

  • 视频ID
  • 视频名称
  • 视频描述信息
  • 视频文件的URL路径 (加上静态资源根目录就是实际存储路径)
  • 数据封面图片的URL路径 (加上静态资源根目录就是实际存储路径)

数据库的创建代码如下:

dropdatabaseifexists aod_system;createdatabaseifnotexists aod_system;use aod_system;createtableifnotexists tb_video(
    id intprimarykeyauto_incrementcomment'视频ID',
    name varchar(32)comment'视频名称',
    info textcomment'视频描述',
    video varchar(256)comment'视频文件url,加上静态资源根目录就是实际存储路径',
    image varchar(256)comment'封面图片文件url,加上静态资源根目录就是实际存储路径');

创建成功后:

5.2 数据管理类的设计

数据管理模块负责统一对数据库中数据的增删改查管理,其他所有的模块要进行对数据的操作都要通过数据管理模块来完成。

然而,数据库中可能存在多张表,每张表的数据又不相同,进行的数据操作也不同。因此,就需要为每张表中的数据操作都设计一个数据管理类,通过类的实例化对象来管理这张表中的数据。由于在视频点播系统中只涉及一张表,因此只设计一个类即可,该类包含的数据库操作有:新增、修改、删除、查询所有数据、查询单个数据、模糊匹配。

由于视频信息在接口之间的传递字段数量可能很多,因此使用

Json::Value

对象进行传递。以下是具体代码的实现:

#ifndef__MY_DATA__ //防止头文件重复包含#define__MY_DATA__#include"util.hpp"#include<cstdlib>#include<mutex>#include<mysql/mysql.h>namespace aod
{#defineHSOT"127.0.0.1"#defineUSER"root"#definePASSWD""#defineDBNAME"aod_system"//MYSQL句柄初始化static MYSQL*MySQLInit(){
        MYSQL* mysql =mysql_init(NULL);if(NULL== mysql){
            std::cout <<"init mysql instance failed!"<< std::endl;returnNULL;}//连接数据库服务器if(mysql_real_connect(mysql, HSOT, USER, PASSWD, DBNAME,0,NULL,0)==NULL){
            std::cout <<"connect mysql server failed!"<< std::endl;mysql_close(mysql);returnNULL;}mysql_set_character_set(mysql,"utf8");return mysql;}//释放MYSQL句柄staticvoidMySQLDestroy(MYSQL* mysql){if(mysql !=NULL){mysql_close(mysql);}return;}//执行sql语句staticboolMySQLQuery(MYSQL* mysql,const std::string& sql){int ret =mysql_query(mysql, sql.c_str());if(ret !=0){
            std::cout << sql << std::endl;
            std::cout <<"query sql failed!"<< std::endl;returnfalse;}returntrue;}classTableVideo{private:
            MYSQL* _mysql;//MYSQL句柄
            std::mutex _mutex;//解决操作对象在多线程中操作这张表的线程安全问题public://完成对mysql句柄的初始化TableVideo(){
                _mysql =MySQLInit();if(NULL== _mysql){exit(-1);}}//释放mysql句柄~TableVideo(){MySQLDestroy(_mysql);}//新增---传入视频信息boolInsert(const Json::Value& video){//id name info video image
                std::string sql;
                sql.resize(4096+ video["info"].asString().size());//防止视频简介内容过长#defineINSERT_VIDEO"insert tb_video values(null, '%s', '%s', '%s', '%s');"if(video["name"].asString().size()==0|| video["info"].asString().size()==0|| video["video"].asString().size()==0|| video["image"].asString().size()==0){
                    std::cout <<"新增视频信息有误!"<< std::endl;returnfalse;}sprintf(&sql[0], INSERT_VIDEO, video["name"].asCString(), video["info"].asCString(), video["video"].asCString(), video["image"].asCString());returnMySQLQuery(_mysql, sql);}//修改---传入视频id和信息boolUpdate(int video_id,const Json::Value& video){
                std::string sql;
                sql.resize(4096+ video["info"].asString().size());//防止视频简介内容过长#defineUPDATE_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);returnMySQLQuery(_mysql, sql);}//删除---传入视频idboolDelete(int video_id){

                std::string sql;
                sql.resize(1024);#defineDELETE_VIDEO"delete from tb_video where id=%d;"sprintf(&sql[0], DELETE_VIDEO, video_id);returnMySQLQuery(_mysql, sql);}//查询并输出所有视频信息boolSelectAll(Json::Value* videos){#defineSELECT_ALL"select * from tb_video;"
                
                _mutex.lock();// 在多线程中,保护查询与保存结果到本地的过程bool ret =MySQLQuery(_mysql, SELECT_ALL);if(false== ret){
                    std::cout <<"select all failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                MYSQL_RES *res =mysql_store_result(_mysql);if(NULL== res){
                    std::cout <<"store result failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                _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);//释放结果集returntrue;}//传入id,查询单个视频信息boolSelectOne(int video_id, Json::Value* video){
              
              std::string sql;
              sql.resize(1024);#defineSELECT_ONE"select * from tb_video where id=%d;"sprintf(&sql[0], SELECT_ONE, video_id);
                
                _mutex.lock();// 在多线程中,保护查询与保存结果到本地的过程bool ret =MySQLQuery(_mysql, sql);if(false== ret){
                    std::cout <<"select all failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                MYSQL_RES *res =mysql_store_result(_mysql);if(NULL== res){
                    std::cout <<"store result failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                _mutex.unlock();// 解锁int num_rows =mysql_num_rows(res);if(num_rows !=1){
                    std::cout <<"data is not exits!"<< std::endl;mysql_free_result(res);returnfalse;}

              
                 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);//释放结果集returntrue;}//模糊匹配---输入关键字boolSelectLike(const std::string& key, Json::Value* videos){
              std::string sql;
              sql.resize(1024);#defineSELECT_LIKE"select * from tb_video where name like '%%%s%%';"sprintf(&sql[0], SELECT_LIKE, key.c_str());
                
                _mutex.lock();// 在多线程中,保护查询与保存结果到本地的过程bool ret =MySQLQuery(_mysql, SELECT_ALL);if(false== ret){
                    std::cout <<"select all failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                MYSQL_RES *res =mysql_store_result(_mysql);if(NULL== res){
                    std::cout <<"store result failed!"<< std::endl;
                    _mutex.unlock();returnfalse;}
                
                _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);//释放结果集returntrue;}};}#endif

六、网络通信模块 — 网络通信接口设计

首先要明确的是:

  • 网络通信接口设计其实就是定义好:什么样的请求是一个查询请求、什么样的请求是一个删除请求 、、、、、、
  • 服务端提高的功能包括:新增视频、删除视频、修改视频、查询所有视频、查询单个视频、模糊匹配查询。

因此,要让不同的功能对应到不同的接口,在网络通信接口的设计中,就借助了

REST

设计风格来设计网络接口。

6.1 REST 设计风格

REST

Representational State Transfer

的缩写,中文名叫表现层状态转换。是Roy Thomas Fielding博士于2000年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件或程序在网络(例如互联网)中互相传递信息。

REST

是基于

HTTP

协议之上而确定的一组约束和属性,可以充分利用

HTTP

协议的各种功能,是

HTTP

协议的最佳实践。

RESTful API

是一种软件架构风格,可以让软件更加的清晰、简介、富有层次感、提高可维护性。

匹配于

REST

这种架构风格的网络服务,允许客户端发出以

URL

访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质。

REST

风格中定义了:

  • GET方法:表示查询
  • POST方法:表示新增
  • PUT方法:表示修改
  • DELETE方法:表示删除
  • 资源正文数据采用JsonXML数据格式

6.2 REST 风格下 CRUD 操作的 HTTP 格式

获取所有视频信息:

请求:
GET /video HTTP/1.1
Connection: keep-alive
......

响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......

[
    {
        "id": 1,
        "name": "电影1",
        "info": "xxxxxx",
        "video": "/video/movie1.mp4",
        "image": "/img/thumbs/movie1.png",
    },
    {
        "id": 2,
        "name": "电影2",
        "info": "xxxxxx",
        "video": "/video/movie2.mp4",
        "image": "/img/thumbs/movie2.png",
    }
]

搜索关键字获取视频信息:

请求:
GET /video?search="电影1" HTTP/1.1
Connection: keep-alive
......

响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......

[
    {
        "id": 1,
        "name": "电影1",
        "info": "xxxxxx",
        "video": "/video/movie1.mp4",
        "image": "/img/thumbs/movie1.png",
    }
]

获取指定视频信息:

请求:
GET /video/1 HTTP/1.1
Connection: keep-alive
......

响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......

[
    {
        "id": 1,
        "name": "电影1",
        "info": "xxxxxx",
        "video": "/video/movie1.mp4",
        "image": "/img/thumbs/movie1.png",
    }
]

删除指定视频信息:

请求:
DELETE /video/1 HTTP/1.1
Connection: keep-alive
......

响应:
HTTP/1.1 200 OK
......

修改指定视频信息:

请求:
PUT /video/1 HTTP/1.1
Connection: keep-alive
......

响应:
HTTP/1.1 200 OK
......

上传视频信息:

因为在上传视频信息的时候,会携带有视频文件、封面图片文件的上传,而这些文件数据都是二进制的,所以使用

Json

格式就不再合适了。因此在上传视频的时候就使用

HTTP

协议默认的上传文件请求格式,而不使用

REST

风格。

请求:
PSOT /video HTTP/1.1
Content-Type: video/form-data; boundary="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
Content-Length: xxx
......

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="name"
name(视频的名称)

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="info"
info(视频的描述)

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="video"; filename="video.mp4"
Content-Type: text/plain
video视频数据

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: text/plain
image封面图片数据

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="submit"

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

响应:
HTTP/1.1 303 See Other
Location: "/"

七、业务处理模块的实现

7.1 业务处理模块类的设计

业务处理模块负责与客户端进行网络通信,接收客户端的请求,然后根据请求信息,明确客户端用户的意图进行业务处理,并返回相应的处理结果给客户端。

由于在实现网络通信相关功能使用的是

httplib

库,大大减小了开发成本,因此这里将网络通信模块和业务处理模块合并在同一个类中。因此在视频点播系统中,业务处理模块主要包含两大功能:网络通信功能和业务处理功能

业务处理模块主要完成的功能有:

  • 客户端的视频数据和信息的上传
  • 客户端的视频列表的展示
  • 客户端的观看视频请求
  • 客户端的视频管理(修改、删除)

代码框架如下:

#ifndef__MY_SERVER__#define__MY_SERVER__#include"data.hpp"#include"httplib.h"namespace aod
{#defineWWW_ROOT"./www"//资源根目录#defineVIDEO_ROOT"/video/"//视频目录#defineIMAGE_ROOT"/image/"//图片目录// 因为 httplib 是基于多线程,因此数据管理模块需要在多线程被访问,为了便于访问定义全局变量
    aod::TableVideo * table_video =NULL;classServer{private:int _port;//服务器监听的端口号
            httplib::Server _server;//用于搭建HTTP服务器public://业务处理接口//新增staticvoidInsert(const httplib::Request& req, httplib::Response& rsp);//修改staticvoidUpdate(const httplib::Request& req, httplib::Response& rsp);//删除staticvoidDelete(const httplib::Request& req, httplib::Response& rsp);//查询单个staticvoidSelectOne(const httplib::Request& req, httplib::Response& rsp);//查询所有或者模糊匹配staticvoidSelectAll(const httplib::Request& req, httplib::Response& rsp);public:Server(int port):_port(port){}//建立请求与处理函数之间的映射关系,设置静态资源根目录,启动服务器boolRunModule();};}#endif

**

RunModule

的实现:**

boolRunModule(){//1. 初始化---初始化数据管理模块、创建指定的目录
    table_video =newTableVideo();if(aod::FileUtil(WWW_ROOT).CreateDirectory()==false){
        std::cout <<"create directory: "<< WWW_ROOT <<" failed!"<< std::endl;returnfalse;}

    std::string video_real_path = std::string(WWW_ROOT)+ std::string(VIDEO_ROOT);// ./www/video/
    std::string image_real_path = std::string(WWW_ROOT)+ std::string(IMAGE_ROOT);// ./www/image/if(aod::FileUtil(video_real_path).CreateDirectory()==false){
        std::cout <<"create directory: "<< video_real_path <<" failed!"<< std::endl;returnfalse;}if(aod::FileUtil(image_real_path).CreateDirectory()==false){
        std::cout <<"create directory: "<< image_real_path <<" failed!"<< std::endl;returnfalse;}// 2. 搭建HTTP服务器,开始运行// 2.1 设置静态资源根目录
    _server.set_mount_point("/", WWW_ROOT);// 2.2 建立请求与处理函数之间的映射关系
    _server.Post("/video", Insert);
    _server.Delete("/video/(\\d+)", Delete);
    _server.Put("/video/(\\d+)", Update);
    _server.Get("/video/(\\d+)", SelectOne);
    _server.Get("/video", SelectAll);// 3. 启动服务器
    _server.listen("0.0.0.0", _port);returntrue;}

**

Insert

的实现:**

staticvoidInsert(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/video/视频1.mp4
    
    std::string root = WWW_ROOT;
    std::string video_path = root + VIDEO_ROOT + video_name + video.filename;
    std::string image_path = root + IMAGE_ROOT + video_name + image.filename;if(aod::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(aod::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/视频1video.mp4
    video_json["image"]= IMAGE_ROOT + video_name + image.filename;// /image/视频1image.jpgif(table_video->Insert(video_json)==false){
        rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
        rsp.body =R"({"result":false, "reason":"数据库新增数据失败"})";
        rsp.set_header("Content-Type","application/json");return;}return;}

**

Update

的实现:**

staticvoidUpdate(const httplib::Request& req, httplib::Response& rsp){// 1. 获取要修改的视频id和修改后的视频信息
     std::string num = req.matches[1];int video_id =atoi(num.c_str());

     Json::Value video;if(aod::JsonUtil::Deserialize(req.body,&video)==false){
         rsp.status =400;//客户端错误,请求包含语法错误或无法完成请求
         rsp.body =R"({"result":false, "reason":"新的视频信息解析失败"})";
         rsp.set_header("Content-Type","application/json");return;}// 2. 修改数据库视频信息if(table_video->Update(video_id, video)==false){
         rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
         rsp.body =R"({"result":false, "reason":"修改数据库中的视频信息失败"})";
         rsp.set_header("Content-Type","application/json");return;}return;}

**

Delete

的实现:**

staticvoidDelete(const httplib::Request& req, httplib::Response& rsp){//1. 获取要删除的视频id//matches: 存放正则表达式匹配的规则数据 /numbers/123 matches[0] = "/numbers/123", matches[1] = "123"
    std::string num = req.matches[1];int video_id =atoi(num.c_str());//2. 删除视频文件和图片文件
    Json::Value video;if(table_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 = WWW_ROOT;//视频文件存放路径
    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(table_video->Delete(video_id)==false){
        rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
        rsp.body =R"({"result":false, "reason":"删除数据库视频信息失败"})";
        rsp.set_header("Content-Type","application/json");return;}return;}

**

SelectOne

的实现:**

staticvoidSelectOne(const httplib::Request& req, httplib::Response& rsp){//1. 获取要删除的视频id
    std::string num = req.matches[1];int video_id =atoi(num.c_str());//2. 在数据库中查询指定视频信息
    Json::Value video;if(table_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格式的字符串if(aod::JsonUtil::Serialize(video,&rsp.body)==false){
        rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
        rsp.body =R"({"result":false, "reason":"序列化正文失败"})";
        rsp.set_header("Content-Type","application/json");return;}
    rsp.set_header("Content-Type","application/json");return;}

**

SelectAll

的实现:**

staticvoidSelectAll(const httplib::Request& req, httplib::Response& rsp){//存在两种可能: /video  和  /video?search="关键字"// 1. 判断查询类型bool select_flag =true;//默认查询所有
    std::string search_key;if(req.has_param("search")==true){
        select_flag =false;//模糊匹配
        search_key = req.get_param_value("search");}//2. 查询视频信息 
    Json::Value videos;if(select_flag ==true){if(table_video->SelectAll(&videos)==false){
            rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
            rsp.body =R"({"result":false, "reason":"查询数据库所有视频信息失败"})";
            rsp.set_header("Content-Type","application/json");return;}}else{if(table_video->SelectLike(search_key,&videos)==false){
            rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
            rsp.body =R"({"result":false, "reason":"模糊匹配查询数据库视频信息失败"})";
            rsp.set_header("Content-Type","application/json");return;}}//3. 组织响应正文if(aod::JsonUtil::Serialize(videos,&rsp.body)==false){

        rsp.status =500;//服务器错误,服务器在处理请求的过程中发生了错误
        rsp.body =R"({"result":false, "reason":"序列化正文失败"})";
        rsp.set_header("Content-Type","application/json");return;}
    
    rsp.set_header("Content-Type","application/json");return;}

【注意】

SelectAll

函数中将查询所有视频和模糊匹配两个功能包含在一起的,因为在

httplib

库中的

Resuest

类中有一个

has_param

函数,可用于判断请求中是否含义

search

关键字。利用

has_param

函数就可判断出此次查询请求是查询所有还是通过关键字查询。

7.2 综合调试

调试代码:

#include"server.hpp"intmain(){
    aod::Server server(9090);
    server.RunModule();return0;}

服务器的功能测试借助一个工具

Postman

完成。Postman下载地址

八、前端界面的实现

8.1 前端视频展示界面的实现

<!DOCTYPEhtml><htmllang="en"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1"><metaname="description"content=""><metaname="author"content="OrcasThemes"><metahttp-equiv="X-UA-Compatible"content="IE=Edge"/><title>Home</title><!-- Bootstrap core CSS --><linkhref="css/bootstrap.css"rel="stylesheet"><!-- Custom styles for this template --><linkrel="stylesheet"href="css/screen.css"><linkrel="stylesheet"href="css/animation.css"><!--[if IE 7]>

<![endif]--><linkrel="stylesheet"href="css/font-awesome.css"><!--[if lt IE 8]>
<link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection">
<![endif]--><linkhref="css/lity.css"rel="stylesheet"><style>[v-cloak]{display: none;}</style></head><body><divid="myapp"><!-- HOME 1 --><divid="home1"class="container-fluid standard-bg"><!-- HEADER --><divclass="row header-top"><divclass="col-lg-3 col-md-6 col-sm-5 col-xs-8"><aclass="main-logo"href="#"><imgsrc="img/main-logo.png"class="main-logo img-responsive"alt="Muvee Reviews"title="Muvee Reviews"></a></div><divclass="col-lg-6 hidden-md text-center hidden-sm hidden-xs"></div><divclass="col-lg-3 col-md-6 col-sm-7 hidden-xs"><divclass="right-box"><buttontype="button"class="access-btn"data-toggle="modal"data-target="#enquirypopup">新增视频</button></div></div></div><!-- MENU --><divclass="row home-mega-menu "><divclass="col-md-12"><navclass="navbar navbar-default"><divclass="navbar-header"><buttonclass="navbar-toggle"type="button"data-toggle="collapse"data-target=".js-navbar-collapse"><spanclass="sr-only">Toggle navigation</span><spanclass="icon-bar"></span><spanclass="icon-bar"></span><spanclass="icon-bar"></span></button></div><divclass="collapse navbar-collapse js-navbar-collapse megabg dropshd "><ulclass="nav navbar-nav"><li><ahref="index.html">视频点播</a></li></ul><divclass="search-block"><form><inputtype="search"placeholder="Search"></form></div></div><!-- /.nav-collapse --></nav></div></div><!-- CORE --><divclass="row"><!-- SIDEBAR --><divclass="col-lg-2 col-md-4 hidden-sm hidden-xs"></div><!-- HOME MAIN POSTS --><divclass="col-lg-10 col-md-8"><sectionid="home-main"><h2class="icon"><iclass="fa fa-television"aria-hidden="true"></i>视频列表</h2><divclass="row"><!-- ARTICLES --><divclass="col-lg-9 col-md-12 col-sm-12"><divclass="row auto-clear"><articleclass="col-lg-3 col-md-6 col-sm-4"v-for="video in videos"><!-- POST L size --><divclass="post post-medium"><divclass="thumbr"><aclass="afterglow post-thumb"v-bind:href="'/video.html?id='+video.id"target="_blank"><spanclass="play-btn-border"title="Play"><iclass="fa fa-play-circle headline-round"aria-hidden="true"></i></span><divclass="cactus-note ct-time font-size-1"><span></span></div><imgclass="img-responsive"v-bind:src="video.image"alt="#"v-cloak></a></div><divclass="infor"><h4><aclass="title"href="#"v-cloak>{{video.name}}</video></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><divclass="clearfix spacer"></div></div><!-- RIGHT ASIDE --><divclass="col-lg-3 hidden-md col-sm-12 text-center top-sidebar"></div></div></section></div></div></div><!-- CHANNELS --><divid="channels-block"class="container-fluid channels-bg"></div><!-- BOTTOM BANNER --><divid="bottom-banner"class="container text-center"></div><!-- FOOTER --><divid="footer"class="container-fluid footer-background"><divclass="container"><footer><!-- SECTION FOOTER --><divclass="row"><!-- SOCIAL --><divclass="col-lg-3 col-md-3 col-sm-6 col-xs-12"><divclass="row auto-clear"></div></div><!-- TAGS --><divclass="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div><!-- POST --><divclass="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div><!-- LINKS --><divclass="col-lg-3 col-md-3 col-sm-6 col-xs-12"></div></div><divclass="row copyright-bottom text-center"><divclass="col-md-12 text-center"><ahref=""class="footer-logo"title="Video Magazine Bootstrap HTML5 template"><imgsrc="img/footer-logo.png"class="img-responsive text-center"alt="Video Magazine Bootstrap HTML5 template"></a><pv-cloak>Copyright &copy; Author by {{author}}</p></div></div></footer></div></div><!-- MODAL --><divid="enquirypopup"class="modal fade in "role="dialog"><divclass="modal-dialog"><!-- Modal content--><divclass="modal-content row"><divclass="modal-header custom-modal-header"><buttontype="button"class="close"data-dismiss="modal">×</button><h2class="icon"><iclass="fa fa-television"aria-hidden="true"></i>新增视频</h2></div><divclass="modal-body"><formname="info_form"class="form-inline"action="/video"method="post"enctype="multipart/form-data"><divclass="form-group col-sm-12"><inputtype="text"class="form-control"name="name"placeholder="输入视频名称"></div><divclass="form-group col-sm-12"><inputtype="text"class="form-control"name="info"placeholder="输入视频简介"></div><divclass="form-group col-sm-12"><inputtype="file"class="form-control"name="video"placeholder="选择视频文件"></div><divclass="form-group col-sm-12"><inputtype="file"class="form-control"name="image"placeholder="选择封面图片"></div><divclass="form-group col-sm-12"><buttonclass="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) --><scriptsrc="js/jquery-1.12.1.min.js"></script><scriptsrc="js/bootstrap.min.js"></script><scriptsrc="js/lity.js"></script><scriptsrc="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 =newVue({el:'#myapp',data:{author:"Lihaifei",videos:[]},methods:{get_allvideos:function(){
                $.ajax({url:"/video",type:"get",context:this,// 将vue传入ajax作为this对象success:function(result, status, xhr){//请求成功后的处理函数this.videos = result;// alert("获取结果成功!");}})}}});

    app.get_allvideos();</script></html>

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

<!DOCTYPEhtml><htmllang="en"><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1"><metaname="description"content=""><metaname="author"content="OrcasThemes"><metahttp-equiv="X-UA-Compatible"content="IE=Edge"/><title></title><!-- Bootstrap core CSS --><linkhref="css/bootstrap.css"rel="stylesheet"><!-- Custom styles for this template --><linkrel="stylesheet"href="css/screen.css"><linkrel="stylesheet"href="css/animation.css"><!--[if IE 7]>
      
      <![endif]--><linkrel="stylesheet"href="css/font-awesome.css"><!--[if lt IE 8]>
      <link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection">
      <![endif]--><linkhref="css/lity.css"rel="stylesheet"><style>[v-cloak]{display: none;}</style></head><body><divid="myapp"><!-- SINGLE VIDEO --><divid="single-video"class="container-fluid standard-bg"><!-- HEADER --><divclass="row header-top"><divclass="col-lg-3 col-md-6 col-sm-5"><aclass="main-logo"href="#"><imgsrc="img/main-logo.png"class="main-logo"alt="Muvee Reviews"title="Muvee Reviews"></a></div><divclass="col-lg-6 hidden-md text-center hidden-sm hidden-xs"></div><divclass="col-lg-3 col-md-6 col-sm-7 hidden-xs"><divclass="right-box"><buttontype="button"class="access-btn"data-toggle="modal"v-on:click="delete_video()">视频删除</button><buttontype="button"class="access-btn"data-toggle="modal"data-target="#enquirypopup">视频修改</button></div></div></div><!-- MENU --><divclass="row home-mega-menu "><divclass="col-md-12"><navclass="navbar navbar-default"><divclass="navbar-header"><buttonclass="navbar-toggle"type="button"data-toggle="collapse"data-target=".js-navbar-collapse"><spanclass="sr-only">Toggle navigation</span><spanclass="icon-bar"></span><spanclass="icon-bar"></span><spanclass="icon-bar"></span></button></div><divclass="collapse navbar-collapse js-navbar-collapse megabg dropshd "><ulclass="nav navbar-nav"><li><ahref="index.html">视频点播</a></li></ul><divclass="search-block"><form><inputtype="search"placeholder="Search"></form></div></div><!-- /.nav-collapse --></nav></div></div><!-- SINGLE VIDEO --><divclass="row"><!-- SIDEBAR --><divclass="col-lg-2 col-md-4 hidden-sm hidden-xs"></div><!-- SINGLE VIDEO --><divid="single-video-wrapper"class="col-lg-10 col-md-8"><divclass="row"><!-- VIDEO SINGLE POST --><divclass="col-lg-9 col-md-12 col-sm-12"><!-- POST L size --><articleclass="post-video"><!-- VIDEO INFO --><divclass="video-info"><!-- 16:9 aspect ratio --><divclass="embed-responsive embed-responsive-16by9 video-embed-box"><iframev-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><divclass="clearfix spacer"></div><!-- DETAILS --><divclass="video-content"><h2class="title main-head-title">视频描述</h2><pv-cloak>{{video.info}}</p></div><divclass="clearfix spacer"></div></article></div><!-- VIDEO SIDE BANNERS --><divclass="col-lg-3 hidden-md hidden-sm"></div></div><divclass="clearfix spacer"></div><divclass="row"></div></div></div></div><!-- CHANNELS --><divid="channels-block"class="container-fluid channels-bg"><divclass="container-fluid "><divclass="col-md-12"><divclass="clearfix"></div></div></div></div><!-- FOOTER --><divid="footer"class="container-fluid footer-background"><divclass="container"><footer><divclass="row copyright-bottom text-center"><divclass="col-md-12 text-center"><ahref=""class="footer-logo"title="Video Magazine Bootstrap HTML5 template"><imgsrc="img/footer-logo.png"class="img-responsive text-center"alt="Video Magazine Bootstrap HTML5 template"></a><pv-cloak>Copyright &copy; Author by {{author}}</p></div></div></footer></div></div><!-- MODAL --><divid="enquirypopup"class="modal fade in "role="dialog"><divclass="modal-dialog"><!-- Modal content--><divclass="modal-content row"><divclass="modal-header custom-modal-header"><buttontype="button"class="close"data-dismiss="modal">×</button><h2class="icon"><iclass="fa fa-television"aria-hidden="true"></i>视频信息修改</h2></div><divclass="modal-body"><formname="info_form"class="form-inline"action="#"method="post"><divclass="form-group col-sm-12"><inputtype="text"class="form-control"name="name"v-model="video.name"></div><divclass="form-group col-sm-12"><inputtype="text"class="form-control"name="info"v-model="video.info"></div><divclass="form-group col-sm-12"><buttonclass="subscribe-btn pull-right"type="submit"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) --><scriptsrc="js/jquery-1.12.1.min.js"></script><scriptsrc="js/bootstrap.min.js"></script><scriptsrc="js/lity.js"></script><scriptsrc="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 =newVue({el:'#myapp',data:{author:"Lihaifei",video:{}},methods:{get_param:function(name){returndecodeURIComponent((newRegExp('[?|&]'+ 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,// 将vue传入ajax作为this对象success:function(result, status, xhr){//请求成功后的处理函数this.video = result;// alert("获取结果成功!");}})},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,data:JSON.stringify(this.video),context:this,success:function(result, status, xhr){alert("删除视频成功!");
                  window.location.href="/index.html";}})}}});

   app.get_video();</script></html>

九、项目总结

  • 项目名称:视频共享点播系统
  • 项目功能:搭建一个共享点播系统,服务器支持用户通过前端浏览器访问服务器,获取展示与观看和操作的界面,最 终实现视频的上传以及观看和删改查等基础管理功能。
  • 开发环境及工具: centos7.6vimg++gdbmakefilevscode等。
  • 技术特点: HTTP 服务器搭建, RESTful 风格接口设计, Json 序列化,线程池, HTML+CSS+JS 基础。
  • 项目模块: 1.数据管理模块:基于 MYSQL 进行数据管理,封装数据管理类进行数据统一访问 2.业务处理模块:基于 HTTPLIB 搭建 HTTP 服务器,使用 restful风格 进行接口设计处理客户端业务请求 3.前端界面模块:基于基础的 HTML+CSS+JS 完成基于简单模板前端界面的修改与功能完成。
  • 项目扩展方向: 1.添加用户管理以及视频分类管理 2.添加视频的评论,打分功能。 3.服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理
  • 源码:https://gitee.com/LiHaiHei/project/tree/master/VOD/source
标签: linux c++

本文转载自: https://blog.csdn.net/qq_61635026/article/details/129271042
版权归原作者 李 ~ 所有, 如有侵权,请联系我们删除。

“【项目】视频点播系统”的评论:

还没有评论