视频点播项目

19 篇文章 2 订阅
2 篇文章 0 订阅

视频点播

允许用户通过浏览器访问视频网站,浏览多个线上视频,并允许点开一个视频进行观看。同时,也可以对视频进行增删改查。

项目链接

技术栈与项目环境

  • 项目环境

系统: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 主要特性的阐述:

主要特性

  1. 解析 JSON 数据:JsonCpp 可以将 JSON 格式的字符串解析为 C++ 中的对象(如 Json::Value),使得开发者可以方便地访问 JSON 数据的各个字段。
  2. 生成 JSON 数据:JsonCpp 可以将 C++ 对象(如 Json::Value)序列化为 JSON 格式的字符串,以便进行数据交换或存储。
  3. 易用性:JsonCpp 提供了友好的 API,易于上手使用。其设计直观,适合于初学者和有经验的开发者。
  4. 灵活性:JsonCpp 支持多种 JSON 数据类型,包括对象、数组、字符串、数字、布尔值和空值(null)。
  5. 健壮性: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)
);

常用接口:

🔺初始化和连接

  1. MYSQL *mysql_init(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL结构的指针。如果为NULL,函数将分配一个新的MYSQL结构;如果不为NULL,函数将初始化传入的MYSQL结构。
    • 功能: 初始化一个MYSQL对象。
    • 返回值: 如果成功返回初始化的MYSQL对象,失败返回NULL
  2. 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

🔺查询和检索数据

  1. int mysql_query(MYSQL *mysql, const char *query)
    • 参数:
      • MYSQL *mysql: 一个指向已连接MYSQL对象的指针。
      • const char *query: 要执行的SQL查询字符串。
    • 功能: 执行一个SQL查询。
    • 返回值: 成功返回0,失败返回非零。
  2. MYSQL_RES *mysql_store_result(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向已连接MYSQL对象的指针。
    • 功能: 检索完整的结果集。
    • 返回值: 如果成功返回MYSQL_RES结果集指针,失败返回NULL
  3. my_ulonglong mysql_num_rows(MYSQL_RES *result)
    • 参数:
      • result: MYSQL_RES 结构体指针,表示 MySQL 查询结果集的句柄。
    • 功能: 当执行一个 SELECT 查询后,可以调用 mysql_num_rows 函数来获取返回的行数。
    • 返回值my_ulonglong: 一个无符号长整型 (unsigned long long) 值,表示查询结果集中的行数。
  4. MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
    • 参数:
      • MYSQL_RES *result: 一个指向结果集的指针。
    • 功能: 从结果集中获取下一行。
    • 返回值: 如果成功返回MYSQL_ROW,没有更多数据时返回NULL
  5. unsigned int mysql_num_fields(MYSQL_RES *result)
    • 参数:
      • MYSQL_RES *result: 一个指向结果集的指针。
    • 功能: 获取结果集中每一行数据的列数。
    • 返回值: 结果集中每一行数据的列数。
  6. void mysql_free_result(MYSQL_RES *result)
    • 参数:
      • MYSQL_RES *result: 一个指向结果集的指针。
    • 功能: 释放结果集使用的内存。
    • 返回值: 无。

🔺关闭连接

  1. void mysql_close(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL对象的指针。
    • 功能: 关闭与数据库的连接并释放相关资源。
    • 返回值: 无。

🔺错误处理

  1. const char *mysql_error(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL对象的指针。
    • 功能: 返回最近一次MySQL操作的错误消息。
    • 返回值: 返回一个指向错误消息的字符串的指针。

🔺其他常用函数

  1. my_ulonglong mysql_affected_rows(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL对象的指针。
    • 功能: 返回上一个查询所影响的行数。
    • 返回值: 返回一个my_ulonglong类型的值,表示受影响的行数。
  2. int mysql_select_db(MYSQL *mysql, const char *db)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL对象的指针。
      • const char *db: 要选择的数据库名。
    • 功能: 选择一个数据库进行操作。
    • 返回值: 成功返回0,失败返回非零。
  3. int mysql_ping(MYSQL *mysql)
    • 参数:
      • MYSQL *mysql: 一个指向MYSQL对象的指针。
    • 功能: 检查连接是否有效,如果连接已经断开则尝试重连。
    • 返回值: 成功返回0,失败返回非零。
  4. 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 服务器和客户端应用程序。它的主要特点包括单头文件实现、易于使用和跨平台支持。

🔺特点

  1. 单头文件:该库只有一个头文件httplib.h,方便集成到项目中。
  2. 易用性:提供简洁的 API,使得构建 HTTP 客户端和服务器变得简单。
  3. 跨平台:支持 Windows、Linux 和 macOS。

在 C++ 的 httplib 库中,RequestResponse 类是 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 方法来启用多线程支持。

工作流程

  1. 接受请求数据
  2. 进行解析,得到Request结构
  3. 检测映射表,根据请求的方法和资源路径查询有没有对应的处理函数。
    • 有,则调用,并且将Request和空Response对象传入。
    • 没有,则检测是否是静态资源。如果存在该静态资源,则直接返回;否则返回404页面。
  4. 当处理函数执行完毕之后的到一个填充完毕的Response对象。
  5. 根据Response对象的数据,组织http响应发送给客户端。
  6. 短连接则直接关闭,长连接等待超时后关闭。

🔺测试

测试的目录结构

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方法

image-20240614215312668

  • 使用numbers方法

image-20240614215407981

  • 访问index.html网页,上传文件

image-20240614215608869

选择文件之后,点击上传。

image-20240614215641803

上传文件之后,后端获取文件内容并输出。

image-20240614215724832

工具类设计

文件类

视频点播涉及到文件上传,需要对上传的文件进行备份存储,因此需要首先设计封装文件操作类,简化对文件的多种操作。

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路径(只是链接,加上相对根目录才是实际的存储路径)'
);

数据管理类设计

对数据表要进行的操作有:

  1. 新增视频信息
  2. 修改视频信息
  3. 删除视频信息
  4. 获取全部的视频信息
  5. 获取某个明确指定的视频信息
  6. 获取一个模糊指定的所有视频信息

视频信息由于字段较多,因此使用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(增删改查)操作
  1. GET表示查询获取
  2. POST对应新增
  3. PUT对应修改
  4. DELETE 对应删除

业务处理模块设计

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

在视频共享点播系统中,业务处理主要包含两大功能:

  1. 网络通信功能的实现
  2. 业务功能处理的实现

其中网络通信功能的实现借助httplib库即可方便的搭建http服务器完成。这也是将网络通信模块与业务处理模块合并在一起完成的原因。

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

  1. 客户端的视频数据和信息上传
  2. 客户端的视频列表展示(视频信息查询)
  3. 客户端的视频观看请求(视频数据的获取)
  4. 客户端的视频其他管理(修改,删除)功能

🔺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 &reg, httplib::Response &rsp);
        static void Update(const httplib::Request &reg, httplib::Response &rsp);
        static void Delete(const httplib::Request &reg, httplib::Response &rsp);
        static void GetOne(const httplib::Request &reg, httplib::Response &rsp);
        static void GetAll(const httplib::Request &reg, 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模拟前端的多种请求

  • 新增两个视频

image-20240718164002502

image-20240718164012007

  • 查看文件目录中是否有新增视频
jia@jia-ubuntu:www$ tree ./
./
├── image
│   ├── 小狗image.jpg
│   └── dogimage.jpg
├── index.html
└── video
    ├── 小狗video.mp4
    └── dogvideo.mp4

2 directories, 5 files
  • 查询数据库中的所有视频信息

image-20240718164440557

  • 查询单个视频

image-20240718164531327

  • 模糊查询视频

image-20240718165525561

  • 修改单个指定视频

image-20240718165614946

image-20240718165714573

  • 删除单个指定视频

image-20240718165813840

image-20240718165838841

前端界面

主页面

主页面所要实现的功能

  1. 展示所有视频
  2. 允许用户点击视频,跳转到视频视频播放页面。
  3. 允许用户在主页上传新视频

获取所有视频

<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>

展示视频列表并允许用户点击视频,跳转到视频视频播放页面

img

<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>

允许用户在主页上传新视频

img

<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>

播放页面

  1. 播放视频
  2. 修改视频
  3. 删除视频

完成上述功能所需要的函数

<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>

播放视频

img

<!-- 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>

修改视频

img

<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服务器实现前后端交互。

项目结构

项目主要由以下几个模块组成:

  1. 视频上传模块:负责视频文件的上传,接受前端上传的文件并保存到服务器指定目录。
  2. 视频信息管理模块:负责视频信息的存储与管理,包括视频名称、简介、文件路径等,使用MariaDB进行存储。
  3. 视频播放模块:提供视频播放功能,支持在线播放。
  4. 前后端交互模块:通过httplib库搭建HTTP服务器,处理前端的请求并返回相应的数据。

关键技术点

  1. 视频上传
    • 使用HTTP POST方法上传视频文件。
    • 后端接收文件并保存到服务器指定目录。
    • 在数据库中记录视频的相关信息。
  2. 视频信息存储与管理
    • 使用MariaDB存储视频的元信息,如视频名称、简介、文件路径等。
    • 设计数据库表结构以支持视频信息的存储和查询。
  3. 视频播放
    • 提供视频文件的HTTP访问路径,前端通过video标签实现在线播放。
    • 支持获取视频文件的元信息,如视频时长。
  4. 前后端交互
    • 使用httplib库搭建HTTP服务器。
    • 处理前端的请求,如获取视频列表、上传视频等。
    • 返回JSON格式的数据,使用jsoncpp库进行JSON数据的解析和生成。

总结

本项目通过C/C++语言结合多种库和工具实现了一个功能完整的视频点播系统。项目采用模块化设计,便于维护和扩展。通过MariaDB存储视频信息,使用httplib搭建HTTP服务器,实现了前后端的高效交互。在开发过程中,使用Makefile进行编译和构建,确保项目的可移植性和可维护性。

项目在实现过程中充分利用了C++11的特性和STL,提高了代码的简洁性和效率。通过jsoncpp库处理JSON数据,通过httplib库实现HTTP通信,极大简化了开发难度。在实际应用中,可以根据需要对各模块进行进一步优化和扩展,以满足不同场景的需求。

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值