搭建视频共享点播服务器,可以让所有人通过浏览器访问服务器,实现视频的上传查看,以及管理并播放的功能。主要是完成服务器端的程序业务功能的实现以及前端访问界面 html 的编写,能够支持客户端浏览器针对服务器上的所有视频进行操作。
该视频点播系统基本上包含四个模块:数据管理、网络通信、业务处理、前端界面,其功能如下:
由于在该项目中会引入许多第三方库,比如httplib
库,该库就会要求gcc编译器必须是较新的版本。如果使用老版本的编译器要么编译不通过,要么就会运行报错。因此我们需要对gcc进行升级,以下是升级至 gcc 7.3 的方法:
gcc --version
sudo yum install centos-release-scl-rh centos-release-scl
sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c++
这里需要注意一下,如果想安装7.版本的,就改成devtoolset-7-gcc,以此类推。
source /opt/rh/devtoolset-7/enable
此时GCC就成功升级到了 7.3 版本。
需要注意的是scl命令启用只是临时的,退出 shell 或重启就会恢复原系统gcc版本。如果想要一启动shell就立即生效可以进行以下配置:
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc
即把启动scl的命令添加到文件.bashrc
中,每次启动shell就会执行该语句。
JSON 是一种轻量级的数据交换格式。它可以代表数字、字符串、值的有序序列和名称/值的集合对。
JsonCpp 是一个C++库,允许操作 JSON 值,包括字符串的序列化和反序列化。它还可以保存反序列化/序列化步骤中的现有注释,方便
用于存储用户输入文件的格式。
以下是安装JsonCpp的命令:
sudo yum install epel-release
sudo yum install jsoncpp-devel
安装好的JsonCpp存放在/usr/include/jsoncpp/json
目录下:
cpp-httplib
是个开源的库,是一个c++封装的http库,使用这个库可以在linux、windows平台下完成http客户端、http服务端的搭建,这是一个多线程“阻塞”HTTP 库。使用起来非常方便,只需要包含头文件httplib.h
即可。源码地址
获取httplib
库:
git clone https://github.com/yhirose/cpp-httplib.git
此次项目的开发我们使用的数据库是MariaDB,MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。
以下是安装MariaDB数据库的步骤:
sudo yum install -y mariadb-server
sudo yum install -y mariadb
sudo yum install -y mariadb-libs
其实 MariaDB 的服务中包含了客户端和相关的C语言接口,因此只需要安装 mariadb-server
即可。
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 ServerCopyright © 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 [(none)]> select version();
+----------------+
| version() |
+----------------+
| 5.5.68-MariaDB |
+----------------+
1 row in set (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
首先认识Json:
Json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
例如:使用Json来表示张三同学的学生信息。
const char* name1 = "张三";
int age1 = 18;
float scores1[3] = {60.0, 59.5, 61.0};
const char* 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 数据对象:
class Json::Value{
Value &operator=(const Value &other); // Value重载了[]和=,因此所有的赋值和获取数据都可以通过
Value& operator[](const std::string& key);// 简单的方式完成 val["姓名"] = "小明";
Value& operator[](const char* key);
Value removeMember(const char* 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();
const char* asCString() const;// 转char* char *name = val["name"].asCString();
Int asInt() const;// 转int int age = val["age"].asInt();
float asFloat() const;// 转float
bool asBool() const;// 转 bool
}
Json序列化类:
// 建议低版本使用
class JSON_API Writer {
virtual std::string write(const Value& root) = 0;
}
class JSON_API FastWriter : public Writer {
virtual std::string write(const Value& root);
}
class JSON_API StyledWriter : public Writer {
virtual std::string write(const Value& root);
}
// 建议较高版本使用,如果用低版本接口可能报错
class JSON_API StreamWriter {
virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
virtual StreamWriter* newStreamWriter() const;
}
Json反序列化类:
// 低版本用起来更简单
class JSON_API Reader {
bool parse(const std::string& document, Value& root, bool collectComments = true);
}
// 高版本更推荐
class JSON_API CharReader {
virtual bool parse(char const* beginDoc, char const* endDoc,
Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
virtual CharReader* newCharReader() const;
}
#include
#include
#include
#include
#include
int main()
{
const char* 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;
return 0;
}
#include
#include
#include
#include
#include
int main()
{
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;
return 0;
}
MySQL C语言API就是用C语言编写的MySQL编程接口,使用这些接口函数可以实现对MySQL数据库的查询等操作。以下是MySQL的C语言API接口。
MYSQL* mysql_init(MYSQL* mysql);
【说明】
NULL
。句柄是什么?
句柄(handle)是C++程序设计中经常提及的一个术语。它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念。句柄一般是指获取另一个对象的方法——一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系。
在C++中,要访问一个对象,通常可以建立一个指向对象的指针。但是在很多具体的应用中,直接用指针代表对象并不是一个好的解决方案。因此引入了句柄的概念。
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);
【参数说明】
NULL
【返回值】
NULL
int mysql_set_character_set(MYSQL* mysql, const char* csname);
【参数说明】
utf8
【返回值】
int mysql_select_db(MYSQL* mysql, const char* db);
【参数说明】
【返回值】
int mysql_query(MYSQL* mysql, const char* stmt_str);
【参数说明】
【返回值】
MYSQL_RES* mysql_store_result(MYSQL* mysql);
【参数说明】
【返回值】
NULL
uint64_t mysql_num_rows(MYSQL_RES* result);
【参数说明】
【返回值】
unsigned int mysql_num_fields(MYSQL_RES* result);
【参数说明】
【返回值】
MYSQL_ROW mysql_fecth_row(MYSQL_RES* result);
【参数说明】
【返回值】
MYSQL_ROW
实际上是一个char**
类型的二级指针将每一条数据表示成了字符串指针数组。比如row[0]
表示第0行,row[1]
表示第一行。void mysql_free_result(MYSQL_RES* result);
【说明】
注意一定要释放结果集,否则会造成内存泄漏。
void mysql_close(MYSQL* mysql);
const char* mysql(MYSQL* mysql);
【说明】
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)
);
创建成功使用desc test_tb;
语句查看对test_tb
的描述:
对数据库进行增删改查的代码样例:
#include
#include
#include
#include
//添加数据
int add(MYSQL* mysql)
{
const char* 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;
}
return 0;
}
int main()
{
//初始化操作句柄
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);
return 0;
}
编译指令:
gcc -o mysql_test mysql_test.c -L/usr/lib64/mysql -lmysqlclient
注意在进行编译的时候需要使用-L
指定mysqlclient
动态库所在的路径,因为它不是之间存在于/usr/lib64/
路径下的。
//修改
int mod(MYSQL* mysql)
{
const char* 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;
}
return 0;
}
编译执行后的结果:
//删除
int del(MYSQL* mysql)
{
const char* 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;
}
return 0;
}
//查询
int get(MYSQL* mysql)
{
const char* 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);
return 0;
}
编译执行的结果:
以上就是使用MySQL的C语言API对数据库进行增删改查的存在。
httplib
库是一个基于C++11
的跨平台的HTTP/HTTPS
库,它的安装非常简单,只需要将httplib.h
包含在代码中即可。
httplib
库实际上是用于搭建一个简单的HTTP
服务器或者客户端的库,使用这种第三方网络库,可以帮助我们省去自己搭建服务器或者客户端的时间,把更多的精力投入到具体的业务处理当中,提高开发的效率。
以下是对httplib
库实现的简单剖析,该库中主要包含四个类:发送请求Request
类,响应数据Response
类,服务端Server类
,客户端Client
类。
发送请求Request
类的组成:
namespace httplib
{
struct MultipartFormData {
std::string name;
std::string content;
std::string filename;
std::string content_type;
};
using MultipartFormDataItems = std::vector<MultipartFormData>;
struct Request
{
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;
bool has_header(const char *key) const;//判断是否有某个头部字段
std::string get_header_value(const char *key, size_t id = 0) const;//获取头部字段值
void set_header(const char *key, const char *val);//设置头部字段
bool has_file(const char *key) const;//文件上传中判断是否有某个文件的信息
MultipartFormData get_file_value(const char *key) const;//获取指定的文件信息
};
}
响应数据Response
类:
namespace httplib
{
struct Response
{
std::string version;//存放协议版本
int status = -1;//存放响应状态码
std::string reason;
Headers headers;//存放响应头部字段键值对的map
std::string body;//存放响应正文
std::string location; // Redirect location重定向位置
void set_header(const char *key, const char *val);//添加头部字段到headers中
void set_content(const std::string &s, const char *content_type);//添加正文到body中
void set_redirect(const std::string &url, int status = 302);//设置全套的重定向信息
};
}
服务端Server类
:
namespace httplib
{
class Server
{
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);
bool listen(const char *host, int port, int socket_flags = 0);//开始服务器监听
bool set_mount_point(const std::string &mount_point, const std::string &dir,
Headers headers = Headers());//设置http服务器静态资源根目录
};
}
客户端Client
类:
namespace httplib
{
class Client
{
//创建client
Client(host,port);
Get()
Post()
Put()
...
};
}
前端代码样例:
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
head>
<body>
<h1>Hello HTTPh1>
<form action="/multipart" method="post" enctype="multipart/form-data">
<input type="file" name="file1">
<input type="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
using namespace httplib;
void Handler(const Request &req, Response& rsp)
{
rsp.body = "Hello World!";
rsp.status = 200; //可以忽略,httplib默认会加上一个200的状态码
}
void Numbers(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;
}
void Multipart(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; //区域文件内容
}
int main()
{
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);
return 0;
}
其中各个函数的作用:
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。如果文件存在则将文件名和文件内容输出到控制台。Handler
、Numbers
、Multipart
,并且指定服务端静态资源根目录为./www
。然后监听8080的端口号等待客户端连接。在视频点播系统中会涉及到文件的上传,需要对上传的文件进行备份存储,因此首先设计封装一个文件操作类,将这个类封装完成后,则在任意模块中对文件进行操作时都将得到简化。
该类主要涉及到的功能是:获取文件的大小、判断文件是否存在、向文件中写入数据、从文件中读取数据、针对目标文件创建目录。具体实现如以下代码:
#include
#include
#include
#include
#include
namespace aod
{
class FileUtil
{
private:
std::string _name; //文件路径名称
public:
FileUtil(const std::string& name):_name(name){}
//判断文件是否存在
bool Exists()
{
int ret = access(_name.c_str(), F_OK); //access的第一个参数是文件名,第二个参数如果传入F_OK用于判断文件是否存在
if(ret != 0)
{
std::cout << "file is not exist!" << std::endl;
return false;
}
return true;
}
//获取文件大小
size_t Size()
{
if(this->Exists() == false)
{
return 0;
}
//stat接口用于获取文件属性,结构体struct stat中的st_size就是文件大小
struct stat st;
int ret = stat(_name.c_str(), &st);
if(ret != 0)
{
std::cout << "get file size failed!" << std::endl;
return 0;
}
return st.st_size;
}
//获取文件内容
bool GetContent(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;
return false;
}
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;
return false;
}
ifs.close();
return true;
}
//向文件中写入内容
bool SetContent(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;
return false;
}
ofs.write(content.c_str(), content.size());
if(ofs.good() == false)
{
std::cout << "write file content failed!" << std::endl;
return false;
}
ofs.close();
return true;
}
//根据文件名称创建目录
bool CreateDirectory()
{
if(this->Exists())
return true;
int ret = mkdir(_name.c_str(), 0777);
if(ret != 0)
{
std::cout << "create directory failed!" << std::endl;
return false;
}
return true;
}
};
}
#endif
【说明】
access
,调用成功返回 0 ,失败则返回 -1 ,其定义如下:#include
int access(const char *path, int amode);
其中path
是文件路径名称,amode
用于指定access
函数的作用,其取值如下:
F_OK 值为0,判断文件是否存在
X_OK 值为1,判断对文件是可执行权限
W_OK 值为2,判断对文件是否有写权限
R_OK 值为4,判断对文件是否有读权限
注:后三种可以使用或“|”的方式,一起使用,如W_OK|R_OK
stat
,其功能是获取文件的属性,调用成功返回 0 ,失败则返回 - 1,定义如下:#include
int stat(const char *path, struct stat *buf);
其中path
代表文件的路径,struct stat
是一个描述文件的结构体,其定义如下:
struct stat {
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 */
};
mkdir
,调用成功返回 0, 失败则返回 -1,其定义如下:#include
int mkdir(const char *path, mode_t mode);
其中path
表示要创建的文件名,mode
表示赋予给新创建的文件权限。
Json工具类包含的功能有两个,一个是将Json::Value
对象序列化成为一个字符串,另一个是将字符串反序列化成为Json::Value
对象。具体实现代码如下:
//Json工具类
class JsonUtil
{
public:
//序列化
static bool Serialize(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;
return false;
}
*str = ss.str();
return true;
}
//反序列化
static bool Deserialize(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;
return false;
}
return true;
}
};
在视频点播系统中,视频数据和图片数据都存储在文件中,所有需要使用数据库来管理用户上传的每个视频的属性信息。这里只需要创建一个简单的视频信息表即可,其属性如下:
- 视频ID
- 视频名称
- 视频描述信息
- 视频文件的
URL
路径 (加上静态资源根目录就是实际存储路径)- 数据封面图片的
URL
路径 (加上静态资源根目录就是实际存储路径)
数据库的创建代码如下:
drop database if exists aod_system;
create database if not exists aod_system;
use aod_system;
create table if not exists tb_video(
id int primary key auto_increment comment '视频ID',
name varchar(32) comment '视频名称',
info text comment '视频描述',
video varchar(256) comment '视频文件url,加上静态资源根目录就是实际存储路径',
image varchar(256) comment '封面图片文件url,加上静态资源根目录就是实际存储路径'
);
数据管理模块负责统一对数据库中数据的增删改查管理,其他所有的模块要进行对数据的操作都要通过数据管理模块来完成。
然而,数据库中可能存在多张表,每张表的数据又不相同,进行的数据操作也不同。因此,就需要为每张表中的数据操作都设计一个数据管理类,通过类的实例化对象来管理这张表中的数据。由于在视频点播系统中只涉及一张表,因此只设计一个类即可,该类包含的数据库操作有:新增、修改、删除、查询所有数据、查询单个数据、模糊匹配。
由于视频信息在接口之间的传递字段数量可能很多,因此使用 Json::Value
对象进行传递。以下是具体代码的实现:
#ifndef __MY_DATA__ //防止头文件重复包含
#define __MY_DATA__
#include "util.hpp"
#include
#include
#include
namespace aod
{
#define HSOT "127.0.0.1"
#define USER "root"
#define PASSWD ""
#define DBNAME "aod_system"
//MYSQL句柄初始化
static MYSQL* MySQLInit()
{
MYSQL* mysql = mysql_init(NULL);
if(NULL == mysql)
{
std::cout << "init mysql instance failed!" << std::endl;
return NULL;
}
//连接数据库服务器
if(mysql_real_connect(mysql, HSOT, USER, PASSWD, DBNAME, 0, NULL, 0) == NULL)
{
std::cout << "connect mysql server failed!" << std::endl;
mysql_close(mysql);
return NULL;
}
mysql_set_character_set(mysql, "utf8");
return mysql;
}
//释放MYSQL句柄
static void MySQLDestroy(MYSQL* mysql)
{
if(mysql != NULL)
{
mysql_close(mysql);
}
return;
}
//执行sql语句
static bool MySQLQuery(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;
return false;
}
return true;
}
class TableVideo
{
private:
MYSQL* _mysql; //MYSQL句柄
std::mutex _mutex; //解决操作对象在多线程中操作这张表的线程安全问题
public:
//完成对mysql句柄的初始化
TableVideo()
{
_mysql = MySQLInit();
if(NULL == _mysql)
{
exit(-1);
}
}
//释放mysql句柄
~TableVideo()
{
MySQLDestroy(_mysql);
}
//新增---传入视频信息
bool Insert(const Json::Value& video)
{
//id name info video image
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().size() == 0
|| video["info"].asString().size() == 0
|| video["video"].asString().size() == 0
|| video["image"].asString().size() == 0)
{
std::cout << "新增视频信息有误!" << std::endl;
return false;
}
sprintf(&sql[0], INSERT_VIDEO, video["name"].asCString(), video["info"].asCString(), video["video"].asCString(), video["image"].asCString());
return MySQLQuery(_mysql, sql);
}
//修改---传入视频id和信息
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;"
sprintf(&sql[0], UPDATE_VIDEO, video["name"].asCString(), video["info"].asCString(), video_id);
return MySQLQuery(_mysql, sql);
}
//删除---传入视频id
bool Delete(int video_id)
{
std::string sql;
sql.resize(1024);
#define DELETE_VIDEO "delete from tb_video where id=%d;"
sprintf(&sql[0], DELETE_VIDEO, video_id);
return MySQLQuery(_mysql, sql);
}
//查询并输出所有视频信息
bool SelectAll(Json::Value* videos)
{
#define SELECT_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();
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
if(NULL == res)
{
std::cout << "store result failed!" << std::endl;
_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;
}
//传入id,查询单个视频信息
bool SelectOne(int video_id, Json::Value* video)
{
std::string sql;
sql.resize(1024);
#define SELECT_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();
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
if(NULL == res)
{
std::cout << "store result failed!" << std::endl;
_mutex.unlock();
return false;
}
_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);
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)
{
std::string sql;
sql.resize(1024);
#define SELECT_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();
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);
if(NULL == res)
{
std::cout << "store result failed!" << std::endl;
_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;
}
};
}
#endif
首先要明确的是:
网络通信接口设计其实就是定义好:什么样的请求是一个查询请求、什么样的请求是一个删除请求 、、、、、、
服务端提高的功能包括:新增视频、删除视频、修改视频、查询所有视频、查询单个视频、模糊匹配查询。
因此,要让不同的功能对应到不同的接口,在网络通信接口的设计中,就借助了 REST
设计风格来设计网络接口。
REST
是 Representational State Transfer
的缩写,中文名叫表现层状态转换。是Roy Thomas Fielding博士于2000年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件或程序在网络(例如互联网)中互相传递信息。
REST
是基于HTTP
协议之上而确定的一组约束和属性,可以充分利用HTTP
协议的各种功能,是HTTP
协议的最佳实践。RESTful API
是一种软件架构风格,可以让软件更加的清晰、简介、富有层次感、提高可维护性。
匹配于 REST
这种架构风格的网络服务,允许客户端发出以URL
访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质。
在REST
风格中定义了:
Json
、XML
数据格式获取所有视频信息:
请求:
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: "/"
业务处理模块负责与客户端进行网络通信,接收客户端的请求,然后根据请求信息,明确客户端用户的意图进行业务处理,并返回相应的处理结果给客户端。
由于在实现网络通信相关功能使用的是httplib
库,大大减小了开发成本,因此这里将网络通信模块和业务处理模块合并在同一个类中。因此在视频点播系统中,业务处理模块主要包含两大功能:网络通信功能和业务处理功能。
业务处理模块主要完成的功能有:
代码框架如下:
#ifndef __MY_SERVER__
#define __MY_SERVER__
#include "data.hpp"
#include "httplib.h"
namespace aod
{
#define WWW_ROOT "./www" //资源根目录
#define VIDEO_ROOT "/video/" //视频目录
#define IMAGE_ROOT "/image/" //图片目录
// 因为 httplib 是基于多线程,因此数据管理模块需要在多线程被访问,为了便于访问定义全局变量
aod::TableVideo * table_video = NULL;
class Server
{
private:
int _port; //服务器监听的端口号
httplib::Server _server; //用于搭建HTTP服务器
public:
//业务处理接口
//新增
static void Insert(const httplib::Request& req, httplib::Response& rsp);
//修改
static void Update(const httplib::Request& req, httplib::Response& rsp);
//删除
static void Delete(const httplib::Request& req, httplib::Response& rsp);
//查询单个
static void SelectOne(const httplib::Request& req, httplib::Response& rsp);
//查询所有或者模糊匹配
static void SelectAll(const httplib::Request& req, httplib::Response& rsp);
public:
Server(int port) :_port(port){}
//建立请求与处理函数之间的映射关系,设置静态资源根目录,启动服务器
bool RunModule();
};
}
#endif
RunModule
的实现:
bool RunModule()
{
//1. 初始化---初始化数据管理模块、创建指定的目录
table_video = new TableVideo();
if (aod::FileUtil(WWW_ROOT).CreateDirectory() == false)
{
std::cout << "create directory: " << WWW_ROOT << " failed!" << std::endl;
return false;
}
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;
return false;
}
if (aod::FileUtil(image_real_path).CreateDirectory() == false)
{
std::cout << "create directory: " << image_real_path << " failed!" << std::endl;
return false;
}
// 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);
return true;
}
Insert
的实现:
static void Insert(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.jpg
if (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
的实现:
static void Update(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
的实现:
static void Delete(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
的实现:
static void SelectOne(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
的实现:
static void SelectAll(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
函数就可判断出此次查询请求是查询所有还是通过关键字查询。
调试代码:
#include "server.hpp"
int main()
{
aod::Server server(9090);
server.RunModule();
return 0;
}
服务器的功能测试借助一个工具 Postman
完成。Postman下载地址
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="OrcasThemes">
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>Hometitle>
<link href="css/bootstrap.css" rel="stylesheet">
<link rel="stylesheet" href="css/screen.css">
<link rel="stylesheet" href="css/animation.css">
<link rel="stylesheet" href="css/font-awesome.css">
<link href="css/lity.css" rel="stylesheet">
<style>
[v-cloak] {
display: none;
}
style>
head>
<body>
<div id="myapp">
<div id="home1" class="container-fluid standard-bg">
<div class="row header-top">
<div class="col-lg-3 col-md-6 col-sm-5 col-xs-8">
<a class="main-logo" href="#"><img src="img/main-logo.png" class="main-logo img-responsive"
alt="Muvee Reviews" title="Muvee Reviews">a>
div>
<div class="col-lg-6 hidden-md text-center hidden-sm hidden-xs">
div>
<div class="col-lg-3 col-md-6 col-sm-7 hidden-xs">
<div class="right-box">
<button type="button" class="access-btn" data-toggle="modal" data-target="#enquirypopup">新增视频button>
div>
div>
div>
<div class="row home-mega-menu ">
<div class="col-md-12">
<nav class="navbar navbar-default">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse"
data-target=".js-navbar-collapse">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
div>
<div class="collapse navbar-collapse js-navbar-collapse megabg dropshd ">
<ul class="nav navbar-nav">
<li><a href="index.html">视频点播a>li>
ul>
<div class="search-block">
<form>
<input type="search" placeholder="Search">
form>
div>
div>
nav>
div>
div>
<div class="row">
<div class="col-lg-2 col-md-4 hidden-sm hidden-xs">
div>
<div class="col-lg-10 col-md-8">
<section id="home-main">
<h2 class="icon"><i class="fa fa-television" aria-hidden="true">i>视频列表h2>
<div class="row">
<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 in videos">
<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>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}}video>a>
h4>
div>
div>
article>
div>
<div class="clearfix spacer">div>
div>
<div class="col-lg-3 hidden-md col-sm-12 text-center top-sidebar">
div>
div>
section>
div>
div>
div>
<div id="channels-block" class="container-fluid channels-bg">
div>
<div id="bottom-banner" class="container text-center">
div>
<div id="footer" class="container-fluid footer-background">
<div class="container">
<footer>
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
<div class="row auto-clear">
div>
div>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
div>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
div>
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12">
div>
div>
<div class="row copyright-bottom text-center">
<div class="col-md-12 text-center">
<a href="" class="footer-logo" title="Video Magazine Bootstrap HTML5 template">
<img src="img/footer-logo.png" class="img-responsive text-center"
alt="Video Magazine Bootstrap HTML5 template">
a>
<p v-cloak>Copyright © Author by {{author}}p>
div>
div>
footer>
div>
div>
<div id="enquirypopup" class="modal fade in " role="dialog">
<div class="modal-dialog">
<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>
div>
body>
<script src="js/jquery-1.12.1.min.js">script>
<script src="js/bootstrap.min.js">script>
<script src="js/lity.js">script>
<script src="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 = new Vue({
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>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="OrcasThemes">
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>title>
<link href="css/bootstrap.css" rel="stylesheet">
<link rel="stylesheet" href="css/screen.css">
<link rel="stylesheet" href="css/animation.css">
<link rel="stylesheet" href="css/font-awesome.css">
<link href="css/lity.css" rel="stylesheet">
<style>
[v-cloak] {
display: none;
}
style>
head>
<body>
<div id="myapp">
<div id="single-video" class="container-fluid standard-bg">
<div class="row header-top">
<div class="col-lg-3 col-md-6 col-sm-5">
<a class="main-logo" href="#"><img src="img/main-logo.png" class="main-logo" alt="Muvee Reviews"
title="Muvee Reviews">a>
div>
<div class="col-lg-6 hidden-md text-center hidden-sm hidden-xs">
div>
<div class="col-lg-3 col-md-6 col-sm-7 hidden-xs">
<div class="right-box">
<button type="button" class="access-btn" data-toggle="modal" v-on:click="delete_video()">视频删除button>
<button type="button" class="access-btn" data-toggle="modal" data-target="#enquirypopup">视频修改button>
div>
div>
div>
<div class="row home-mega-menu ">
<div class="col-md-12">
<nav class="navbar navbar-default">
<div class="navbar-header">
<button class="navbar-toggle" type="button" data-toggle="collapse"
data-target=".js-navbar-collapse">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
div>
<div class="collapse navbar-collapse js-navbar-collapse megabg dropshd ">
<ul class="nav navbar-nav">
<li><a href="index.html">视频点播a>li>
ul>
<div class="search-block">
<form>
<input type="search" placeholder="Search">
form>
div>
div>
nav>
div>
div>
<div class="row">
<div class="col-lg-2 col-md-4 hidden-sm hidden-xs">
div>
<div id="single-video-wrapper" class="col-lg-10 col-md-8">
<div class="row">
<div class="col-lg-9 col-md-12 col-sm-12">
<article class="post-video">
<div class="video-info">
<div class="embed-responsive embed-responsive-16by9 video-embed-box">
<iframe v-bind:src="video.video" class="embed-responsive-item">iframe>
div>
div>
<div class="clearfix spacer">div>
<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>
<div class="col-lg-3 hidden-md hidden-sm">
div>
div>
<div class="clearfix spacer">div>
<div class="row">
div>
div>
div>
div>
<div id="channels-block" class="container-fluid channels-bg">
<div class="container-fluid ">
<div class="col-md-12">
<div class="clearfix">div>
div>
div>
div>
<div id="footer" class="container-fluid footer-background">
<div class="container">
<footer>
<div class="row copyright-bottom text-center">
<div class="col-md-12 text-center">
<a href="" class="footer-logo" title="Video Magazine Bootstrap HTML5 template">
<img src="img/footer-logo.png" class="img-responsive text-center"
alt="Video Magazine Bootstrap HTML5 template">
a>
<p v-cloak>Copyright © Author by {{author}}p>
div>
div>
footer>
div>
div>
<div id="enquirypopup" class="modal fade in " role="dialog">
<div class="modal-dialog">
<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="post">
<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" type="submit" title="Subscribe"
v-on:click.prevent="update_video()">提交button>
div>
form>
div>
div>
div>
div>
div>
body>
<script src="js/jquery-1.12.1.min.js">script>
<script src="js/bootstrap.min.js">script>
<script src="js/lity.js">script>
<script src="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 = new Vue({
el: '#myapp',
data: {
author: "Lihaifei",
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({
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.6
、vim
、g++
、gdb
、makefile
、vscode
等。
技术特点: 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