视频点播项目

文章目录

  • 视频点播
  • 技术栈与项目环境
    • JsonCpp
    • MariaDB
    • httplib
  • 工具类设计
    • 文件类
    • Json类
  • 数据管理模块
    • 视频信息管理(数据表设计)
    • 数据管理类设计
  • 网络通信接口设计
  • 业务处理模块设计
  • 前端界面
    • 主页面
    • 播放页面
  • 项目总结
    • 项目回顾
    • 项目结构
    • 关键技术点
    • 总结

视频点播

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

项目链接

技术栈与项目环境

  • 项目环境

系统:Ubuntu 20.04(Centos 7也行)

编辑器:visual studio code(vscode)

编译器:gcc、g++

编译脚本:Makefile

  • 技术栈

C/C++、C++11、STL、jsoncpp、MariaDB、httplib

JsonCpp

JsonCpp 是一个开源的 C++ 库,用于解析和生成 JSON 数据。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人类阅读和编写,同时也易于机器解析和生成。JsonCpp 提供了简洁、易用的接口,方便 C++ 开发者处理 JSON 数据。以下是对 JsonCpp 主要特性的阐述:

主要特性

  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.html2 directories, 5 files

cpp-httplib -> /home/jia/thirdpart/cpp-httplib-v0.7.15实际上是一个软连接,指向下载的httplib第三方库cpp-httplib-v0.7.15。可以使用如下指令进行创建:

ln -s /home/jia/thirdpart/cpp-httplib-v0.7.15 /home/jia/project/ClickVedio/cpp-httplib

其中index.html是一个测试用的前端的页面,在www目录之下。server.cpp是测试用的文件。

index.html

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

server.cpp

#include "cpp-httplib/httplib.h"
void Hello(const httplib::Request&req, httplib::Response&rsp)
{rsp.body="Hello friend";rsp.status=200;//默认会自动添加
}
void Numbers(const httplib::Request&req, httplib::Response&rsp)
{//matches:存放正则表达式匹配的规则数据std::string num=req.matches[1];rsp.set_content(num,"text/plain");//向rsp.body中添加数据,并且设置Content-Type类型
}
void Multipart(const httplib::Request&req, httplib::Response&rsp)
{httplib::MultipartFormData file= req.get_file_value("file1");std::cout<<file.filename<<std::endl;std::cout<<file.content<<std::endl;
}
int main()
{httplib::Server server;//设置一个静态资源根目录server.set_mount_point("/","./www");//添加请求-处理函数映射信息server.Get("/hi",Hello);server.Get("/numbers/(\\d+)",Numbers);server.Post("/multipart",Multipart);//服务器开始监听server.listen("0.0.0.0",9090);return 0;
}

测试结果:

  • 使用hi方法

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;//插入视频1video1["name"] = "黑袍纠察队";video1["info"] = "和谐友爱,阳光向上";video1["video"] = "/video/hero.mp4";video1["image"] = "video/hero.jpg";tb_video.Insert(video1);//插入视频2video2["name"] = "蜘蛛侠";video2["info"] = "保护城市,打击罪恶";video2["video"] = "/video/spider.mp4";video2["image"] = "video/spider.jpg";tb_video.Insert(video2);//查询所有的视频tb_video.SelectAll(&videos);Util::JsonUtil::Serialize(videos,&body);std::cout<<body<<std::endl;videos.clear();//将视频1修改成视频3video3["name"] = "黑袍纠察队";video3["info"] = "精彩纷呈";video3["video"] = "/video/hero.mp4";video3["image"] = "video/hero.jpg";tb_video.Update(1,video3);//查询一个视频1tb_video.SelectOne(1,&video);Util::JsonUtil::Serialize(video,&body);std::cout<<body<<std::endl;//查询所有视频名字中带有侠字的tb_video.SelectLike("侠",&videos);Util::JsonUtil::Serialize(videos,&body);std::cout<<body<<std::endl;videos.clear();//删除视频序号为1的视频tb_video.Delete(1);tb_video.SelectAll(&videos);Util::JsonUtil::Serialize(videos,&body);std::cout<<body<<std::endl;videos.clear();
}
int main()
{DataTest();return 0;
}

🔺测试结果

jia@jia-ubuntu:ClickVedio$ ./util_test 
[{"id" : 1,"image" : "video/hero.jpg","info" : "和谐友爱,阳光向上","name" : "黑袍纠察队","video" : "/video/hero.mp4"},{"id" : 2,"image" : "video/spider.jpg","info" : "保护城市,打击罪恶","name" : "蜘蛛侠","video" : "/video/spider.mp4"}
]
{"id" : 1,"image" : "video/hero.jpg","info" : "精彩纷呈","name" : "黑袍纠察队","video" : "/video/hero.mp4"
}
[{"id" : 2,"image" : "video/spider.jpg","info" : "保护城市,打击罪恶","name" : "蜘蛛侠","video" : "/video/spider.mp4"}
]
[{"id" : 2,"image" : "video/spider.jpg","info" : "保护城市,打击罪恶","name" : "蜘蛛侠","video" : "/video/spider.mp4"}
]

网络通信接口设计

网络通信接口设计建立在http协议之上,http协议其实就是一种数据格式,是一个TCP传输,在应用层采用的一种数据特定格式。网络通信接口设计其实就是定义好,什么样的请求就是一个查询请求,什么样的请求就是一个删除请求…服务端所提供的的功能:新增视频,删除视频,修改视频,查询所有视频信息,查询单个视频,模糊匹配

借助restful风格进行接口设计
restful风格其实是建立在http协议上的,在其中定义了使用GET方法表示查询,使用POST方法表示新增,使用PUT方法表示修改,使用DELETE方法表示删除。并且正文资源数据采用JSON、XML数据格式。

  • REST 是 Representational State Transfer的缩写,一个架构符合REST原则,就称它为RESTful架构
  • RESTful架构可以充分的利用HTTP 协议的各种功能,是HTTP 协议的最佳实践,正文通常采用JSON格式
  • RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好restful使用五种HTTP 方法,对应CRUD(增删改查)操作
  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.获取要删除的视频IDstd::string videoIdStr = req.matches[1];int video_id = atoi(videoIdStr.c_str());//2.删除视频文件Json::Value video;bool res = tablevideo->SelectOne(video_id,&video);if(res == false){rsp.status=500;rsp.body = R"({"result":false,"reason":"不存在视频信息"})";rsp.set_header("Content-Type","application/json");return;}std::string root = WwW_ROOT;std::string video_path = root + video["video"].asString();std::string image_path = root + video["image"].asString();remove(video_path.c_str());//3.删除封面图片文件remove(image_path.c_str());//4.删除数据库信息 res = tablevideo->Delete(video_id);if(res == false){rsp.status=500;rsp.body = R"({"result":false,"reason":"删除数据库信息失败"})";rsp.set_header("Content-Type","application/json");return;}return;           
}
  • 查询单个视频
static void GetOne(const httplib::Request &req, httplib::Response &rsp)
{//1.获取视频的IDstd::string videoIdStr = req.matches[1];int video_id = atoi(videoIdStr.c_str());//2.在数据库中查询指定的视频信息Json::Value video;bool res = tablevideo->SelectOne(video_id,&video);if(res == false){rsp.status=500;rsp.body = R"({"result":false,"reason":"查询指定的视频失败"})";rsp.set_header("Content-Type","application/json");return;}//3.组织响应正文--json格式的字符串JsonUtil::Serialize(video,&rsp.body);rsp.set_header("Content-Type","application/json");return;
}
  • 查询所有视频
static void GetAll(const httplib::Request &req, httplib::Response &rsp)
{bool select_flag = true;//默认所有查询std::string search_key;if(req.has_param("search")==true){select_flag=false;search_key=req.get_param_value("search");}Json::Value videos;if(select_flag){if(tablevideo->SelectAll(&videos)==false){rsp.status=500;rsp.body = R"({"result":false,"reason":"查询所有的视频失败"})";rsp.set_header("Content-Type","application/json");return;}}else{if(tablevideo->SelectLike(search_key,&videos)==false){rsp.status=500;rsp.body = R"({"result":false,"reason":"查询模糊指定的视频失败"})";rsp.set_header("Content-Type","application/json");return;}}JsonUtil::Serialize(videos,&rsp.body);rsp.set_header("Content-Type","application/json");return;
}
  • 业务结合网络通信
//建立请求与处理函数的映射关系,设置静态资源根目录,启动服务器
bool RunModule()
{//1.完成资源的初始化--数据管理模块,创建指定的目录tablevideo = new TableVideo();FileUtil(WwW_ROOT).CreateDirectory();std::string video_real_path = WwW_ROOT;video_real_path += VIDEO_ROOT;FileUtil(video_real_path).CreateDirectory();std::string image_real_path = WwW_ROOT;image_real_path += IMAGE_ROOT;FileUtil(image_real_path).CreateDirectory();//2.搭建http服务器//2.1设置静态资源根目录_srv.set_mount_point("/",WwW_ROOT);//2.2添加请求处理函数映射关系_srv.Post("/video",Insert);_srv.Delete("/video/(\\d+)",Delete);_srv.Put("/video/(\\d+)",Update);_srv.Get("/video/(\\d+)",GetOne);_srv.Get("/video",GetAll);//2.3启动服务器_srv.listen("0.0.0.0",_port);return true;
}

🔺测试-使用postman模拟前端的多种请求

  • 新增两个视频

image-20240718164002502

image-20240718164012007

  • 查看文件目录中是否有新增视频
jia@jia-ubuntu:www$ tree ./
./
├── image
│   ├── 小狗image.jpg
│   └── dogimage.jpg
├── index.html
└── video├── 小狗video.mp4└── dogvideo.mp42 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"><iclass="fa fa-play-circle headline-round"aria-hidden="true"></i></span><div class="cactus-note ct-time font-size-1"><span v-cloak>{{ formatDuration(videoDurations[index])}}</span></div><img class="img-responsive" v-bind:src="video.image" alt="#"v-cloak></a></div><div class="infor"><h4><a class="title" href="#" v-cloak>{{video.name}}</a></h4></div></div></article></div><div class="clearfix spacer"></div></div></div>
</section>

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

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通信,极大简化了开发难度。在实际应用中,可以根据需要对各模块进行进一步优化和扩展,以满足不同场景的需求。

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

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

相关文章

亚马逊自发货erp,虚拟自动化发货功能以及1688订单采购

亚马逊自发货erp自动化功能&#xff0c;自动同步订单&#xff0c;1688订单同步。 大家好&#xff0c;今天分享一个非常实用并且节省时间的功能&#xff1a;自动化发货以及1688同步订单。 首先来看下自动化发货功能怎么操作。 →要在商品信息里面添加商品信息&#xff0c;上传…

风灵月影修改器未检测到游戏怎么回事?解决方法分享

当风灵月影修改器未检测到游戏进程时&#xff0c;可能是由以下几个原因导致的&#xff1a; 1. 游戏未启动&#xff1a; 最直接的原因就是游戏本身没有被启动&#xff0c;或者游戏还未完全加载完成&#xff0c;处于启动过程中的某个阶段&#xff0c;此时修改器可能检测不到游戏…

【总结】逻辑运算在Z3中运用+CTF习题

国际赛IrisCTF在前几天举办&#xff0c;遇到了一道有意思的题目&#xff0c;特来总结。 题目 附件如下&#xff1a;&#x1f4ce;babyrevjohnson.tar 解题过程 关键main函数分析如下&#xff1a; int __fastcall main(int argc, const char **argv, const char**envp){int v4…

关于adcoder和codeforce 如何安装翻译插件

首先在扩展当中下载插件篡改猴 其次&#xff0c;点击获取新的脚本 最后搜索 atcoder better 和 codeforce better 安装即可

【Spring Boot】网页五子棋项目实现,手把手带你全盘解析(长达两万3千字的干货,坐好了,要发车了......)

目录 网页五子棋项目一、项目核心流程二、 登录模块2.1 前端输入用户信息2.2 后端进行数据库查询用户信息 三、 游戏大厅模块3.1 前端通过Ajax请求用户数据&#xff0c;后端从Session中拿取并从数据库中查询后返回3.2 前后端建立WebSocket连接&#xff0c;并进行判断&#xff0…

如何处理 PostgreSQL 中死锁的情况?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 如何处理 PostgreSQL 中死锁的情况&#xff1f;一、认识死锁二、死锁的症状三、死锁的检测四、预防死锁…

【MySQL】:想学好数据库,不知道这些还想咋学

客户端—服务器 客户端是一个“客户端—服务器”结构的程序 C&#xff08;client&#xff09;—S&#xff08;server&#xff09; 客户端和服务器是两个独立的程序&#xff0c;这两个程序之间通过“网络”进行通信&#xff08;相当于是两种角色&#xff09; 客户端 主动发起网…

Java语言程序设计——篇六(1)

字符串 概述创建String类对象     字符串基本操作实战演练 字符串查找字符串转换为数组字符串比较实战演练 字符串的拆分与组合 概述 字符串 用一对双引号“”括起来的字符序列。Java语言中&#xff0c;字符串常量或变量均用类实现。 字符串有两大类&#xff1a; 1&…

设计模式学习[2]---策略模式+简单工厂回顾

文章目录 前言1.简单工厂模式回顾2.策略模式3.策略模式简单工厂的结合总结 前言 上一篇讲到简单工厂模式。 在我的理解中工厂的存在就是&#xff0c;为了实例化对象。根据不同条件实例化不同的对象的作用。 这篇博客写的策略模式&#xff0c;可以说是把这个根据不同情况实例化…

pyinstaller 打包基于PyQt5和PaddleOCR的项目为.exe

简介&#xff1a; 最近做了一个小项目&#xff0c;是基于PyQt5和PaddleOCR的。需要将其打包为.exe&#xff0c;然后打包过程中遇到了很多问题&#xff0c;也看了很多教程&#xff0c;方法千奇百怪的&#xff0c;最后也是一步一步给试出来了。记录一下&#xff0c;防止以后忘记…

华为路由器SSH登录实验

概念 SSH全称安全外壳&#xff08;Secure Shell&#xff09;协议&#xff0c;这个协议的目的就是为了取代缺乏机密性保障的远程管理协议&#xff0c;SSH基于TCP协议的加密通道&#xff0c;让客户端使用服务器的RSA公钥来验证SSHv2服务器的身份。 创建密钥对 在充当SSH服务器的…

C语言随机数的生成相关案例

随机数的方式&#xff1a; 1、设置种子&#xff1a;srand(初始值) 2、获取随机数&#xff1a;rand(); 引导案例&#xff1a; 通过for循环简单生成10个随机数 #include<stdio.h> #include<stdlib.h> //添加包含随机数的库函数 int main() {srand(1); …

嵌入式人工智能(15-基于树莓派4B的电机控制-直流电机TB6612)

电机是传动以及控制系统的重要组成部分&#xff0c;现在的电机已从过去简单的传动向复杂的控制转移&#xff0c;尤其是对电机的速度、位置、转矩的精确控制&#xff0c;本系列将介绍如何使用树莓派驱动并控制3种最为常见的控制电机&#xff1a;直流电机&#xff08;风扇&#x…

大语言模型推理优化--键值缓存--Key-value Cache

文章目录 一、生成式预训练语言模型 GPT 模型结构二、FastServe 框架三、Key-value Cache1.大模型推理的冗余计算2.Self Attention3.KV Cache 一、生成式预训练语言模型 GPT 模型结构 目前&#xff0c;深度神经网络推理服务系统已经有一些工作针对生成式预训练语言模型 GPT 的独…

安全防御---防火墙综合实验3

安全防御—防火墙综合实验3 一、实验拓扑图 二、实验要求 12&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW3&#xff0c;生产区和办公区的流量走FW1 13&#xff0c;办公区…

Ubuntu22.04安装OMNeT++

一、官网地址及安装指南 官网地址&#xff1a;OMNeT Discrete Event Simulator 官网安装指南&#xff08;V6.0.3&#xff09;&#xff1a;https://doc.omnetpp.org/omnetpp/InstallGuide.pdf 官网下载地址&#xff1a;OMNeT Downloads 旧版本下载地址&#xff1a;OMNeT Old…

【动态规划】整数拆分

整数拆分&#xff08;难度&#xff1a;中等&#xff09; 该题对应力扣网址 AC代码 class Solution { public:int integerBreak(int n) {//动态规划//感觉这个题和零钱兑换有点像&#xff0c;只是零钱兑换提供了coin列表vector <int> dp(n1,0);//1、定义子问题//将原问题…

PolarisMesh源码系列--Polaris-Go注册发现流程

导语 北极星是腾讯开源的一款服务治理平台&#xff0c;用来解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问题。在分布式和微服务架构的治理领域&#xff0c;目前国内比较流行的还包括 Spring Cloud&#xff0c;Apache Dubbo 等。在 Kubernete…

错误:PHP:Deprecated: Required parameter $xxx follows optional parameter $yyy

前言 略 错误 Deprecated: Required parameter $xxx follows optional parameter $yyy 解决办法 设置 error_reporting E_ALL & ~E_DEPRECATED & ~E_STRICT 参考 https://blog.csdn.net/lxw1844912514/article/details/100028023

创建自己的 app: html网页直接打包成app;在线网页打包app工具fusionapp、pake

1、html网页直接打包成app 主要通过hbuilderx框架工具来进行打包 https://www.dcloud.io/hbuilderx.html 参考&#xff1a; https://www.bilibili.com/video/BV1XG411r7QZ/ https://www.bilibili.com/video/BV1ZJ411W7Na 1&#xff09;网页制作 这里做的工具是TodoList 页面&a…