【项目】视频点播系统1:https://developer.aliyun.com/article/1383996
注意在进行编译的时候需要使用-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对数据库进行增删改查的存在。
3.6 认识httplib库
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() ... }; }
3.7 使用httplib库搭建简单的服务器
前端代码样例:
<html> <head> <meta content="text/html; charset=utf-8" http-equiv="content-type" /> </head> <body> <h1>Hello HTTP</h1> <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 <iostream> 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的端口号等待客户端连接。
四、服务端工具类的实现
4.1 文件工具类的设计
在视频点播系统中会涉及到文件的上传,需要对上传的文件进行备份存储,因此首先设计封装一个文件操作类,将这个类封装完成后,则在任意模块中对文件进行操作时都将得到简化。
该类主要涉及到的功能是:获取文件的大小、判断文件是否存在、向文件中写入数据、从文件中读取数据、针对目标文件创建目录。具体实现如以下代码:
#include <iostream> #include <fstream> #include <string> #include <unistd.h> #include <sys/stat.h> 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 <unistd.h> 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 <sys/stat.h> 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 <sys/stat.h> int mkdir(const char *path, mode_t mode);
其中path
表示要创建的文件名,mode
表示赋予给新创建的文件权限。
4.2 Json 工具类的设计
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; } };
五、数据管理模块的实现
5.1 视频数据表的设计
在视频点播系统中,视频数据和图片数据都存储在文件中,所有需要使用数据库来管理用户上传的每个视频的属性信息。这里只需要创建一个简单的视频信息表即可,其属性如下:
- 视频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,加上静态资源根目录就是实际存储路径' );
创建成功后:
5.2 数据管理类的设计
数据管理模块负责统一对数据库中数据的增删改查管理,其他所有的模块要进行对数据的操作都要通过数据管理模块来完成。
然而,数据库中可能存在多张表,每张表的数据又不相同,进行的数据操作也不同。因此,就需要为每张表中的数据操作都设计一个数据管理类,通过类的实例化对象来管理这张表中的数据。由于在视频点播系统中只涉及一张表,因此只设计一个类即可,该类包含的数据库操作有:新增、修改、删除、查询所有数据、查询单个数据、模糊匹配。
由于视频信息在接口之间的传递字段数量可能很多,因此使用 Json::Value 对象进行传递。以下是具体代码的实现:
#ifndef __MY_DATA__ //防止头文件重复包含 #define __MY_DATA__ #include "util.hpp" #include <cstdlib> #include <mutex> #include <mysql/mysql.h> 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设计风格来设计网络接口。
6.1 REST 设计风格
REST 是 Representational State Transfer的缩写,中文名叫表现层状态转换。是Roy Thomas Fielding博士于2000年在他的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件或程序在网络(例如互联网)中互相传递信息。
REST是基于HTTP协议之上而确定的一组约束和属性,可以充分利用HTTP协议的各种功能,是HTTP协议的最佳实践。RESTful API是一种软件架构风格,可以让软件更加的清晰、简介、富有层次感、提高可维护性。
匹配于 REST这种架构风格的网络服务,允许客户端发出以URL访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化。因此表现层状态转换提供了在互联网络的计算系统之间,彼此资源可交互使用的协作性质。
在REST风格中定义了:
- GET方法:表示查询
- POST方法:表示新增
- PUT方法:表示修改
- DELETE方法:表示删除
- 资源正文数据采用
Json
、XML
数据格式
6.2 REST 风格下 CRUD 操作的 HTTP 格式
获取所有视频信息:
请求: GET /video HTTP/1.1 Connection: keep-alive ...... 响应: HTTP/1.1 200 OK Content-Length: xxx Content-Type: application/json ...... [ { "id": 1, "name": "电影1", "info": "xxxxxx", "video": "/video/movie1.mp4", "image": "/img/thumbs/movie1.png", }, { "id": 2, "name": "电影2", "info": "xxxxxx", "video": "/video/movie2.mp4", "image": "/img/thumbs/movie2.png", } ]
搜索关键字获取视频信息:
请求: GET /video?search="电影1" HTTP/1.1 Connection: keep-alive ...... 响应: HTTP/1.1 200 OK Content-Length: xxx Content-Type: application/json ...... [ { "id": 1, "name": "电影1", "info": "xxxxxx", "video": "/video/movie1.mp4", "image": "/img/thumbs/movie1.png", } ]
获取指定视频信息:
请求: GET /video/1 HTTP/1.1 Connection: keep-alive ...... 响应: HTTP/1.1 200 OK Content-Length: xxx Content-Type: application/json ...... [ { "id": 1, "name": "电影1", "info": "xxxxxx", "video": "/video/movie1.mp4", "image": "/img/thumbs/movie1.png", } ]
删除指定视频信息:
请求: DELETE /video/1 HTTP/1.1 Connection: keep-alive ...... 响应: HTTP/1.1 200 OK ......
修改指定视频信息:
请求: PUT /video/1 HTTP/1.1 Connection: keep-alive ...... 响应: HTTP/1.1 200 OK ......
上传视频信息:
因为在上传视频信息的时候,会携带有视频文件、封面图片文件的上传,而这些文件数据都是二进制的,所以使用Json
格式就不再合适了。因此在上传视频的时候就使用HTTP
协议默认的上传文件请求格式,而不使用REST
风格。
请求: PSOT /video HTTP/1.1 Content-Type: video/form-data; boundary="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; Content-Length: xxx ...... xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Disposition: form-data; name="name" name(视频的名称) xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Disposition: form-data; name="info" info(视频的描述) xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Disposition: form-data; name="video"; filename="video.mp4" Content-Type: text/plain video视频数据 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Disposition: form-data; name="image"; filename="image.jpg" Content-Type: text/plain image封面图片数据 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Content-Disposition: form-data; name="submit" xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 响应: HTTP/1.1 303 See Other Location: "/"
七、业务处理模块的实现
7.1 业务处理模块类的设计
业务处理模块负责与客户端进行网络通信,接收客户端的请求,然后根据请求信息,明确客户端用户的意图进行业务处理,并返回相应的处理结果给客户端。
由于在实现网络通信相关功能使用的是httplib库,大大减小了开发成本,因此这里将网络通信模块和业务处理模块合并在同一个类中。因此在视频点播系统中,业务处理模块主要包含两大功能:网络通信功能和业务处理功能。
业务处理模块主要完成的功能有:
- 客户端的视频数据和信息的上传
- 客户端的视频列表的展示
- 客户端的观看视频请求
- 客户端的视频管理(修改、删除)
代码框架如下:
#ifndef __MY_SERVER__ #define __MY_SERVER__ #include "data.hpp" #include "httplib.h" namespace aod { #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
函数就可判断出此次查询请求是查询所有还是通过关键字查询。
7.2 综合调试
调试代码:
#include "server.hpp" int main() { aod::Server server(9090); server.RunModule(); return 0; }
服务器的功能测试借助一个工具 Postman
完成。
八、前端界面的实现
8.1 前端视频展示界面的实现
<!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>Home</title> <!-- Bootstrap core CSS --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- Custom styles for this template --> <link rel="stylesheet" href="css/screen.css"> <link rel="stylesheet" href="css/animation.css"> <!--[if IE 7]> <![endif]--> <link rel="stylesheet" href="css/font-awesome.css"> <!--[if lt IE 8]> <link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection"> <![endif]--> <link href="css/lity.css" rel="stylesheet"> <style> [v-cloak] { display: none; } </style> </head> <body> <div id="myapp"> <!-- HOME 1 --> <div id="home1" class="container-fluid standard-bg"> <!-- HEADER --> <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> <!-- MENU --> <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 navigation</span> <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-collapse --> </nav> </div> </div> <!-- CORE --> <div class="row"> <!-- SIDEBAR --> <div class="col-lg-2 col-md-4 hidden-sm hidden-xs"> </div> <!-- HOME MAIN POSTS --> <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"> <!-- 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 in videos"> <!-- 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></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> <!-- <span class="posts-txt" title="Posts from Channel"><i class="fa fa-thumbs-up" aria-hidden="true"></i>20.895</span> <div class="ratings"> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star-half-o" aria-hidden="true"></i> <i class="fa fa-star-o"></i> <i class="fa fa-star-half"></i> </div> --> </div> </div> </article> </div> <div class="clearfix spacer"></div> </div> <!-- RIGHT ASIDE --> <div class="col-lg-3 hidden-md col-sm-12 text-center top-sidebar"> </div> </div> </section> </div> </div> </div> <!-- CHANNELS --> <div id="channels-block" class="container-fluid channels-bg"> </div> <!-- BOTTOM BANNER --> <div id="bottom-banner" class="container text-center"> </div> <!-- FOOTER --> <div id="footer" class="container-fluid footer-background"> <div class="container"> <footer> <!-- SECTION FOOTER --> <div class="row"> <!-- SOCIAL --> <div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"> <div class="row auto-clear"> </div> </div> <!-- TAGS --> <div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"> </div> <!-- POST --> <div class="col-lg-3 col-md-3 col-sm-6 col-xs-12"> </div> <!-- LINKS --> <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> <!-- MODAL --> <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="/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> <!-- JAVA SCRIPT --> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <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>
8.2 前端视频观看页面的实现
<!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> <!-- Bootstrap core CSS --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- Custom styles for this template --> <link rel="stylesheet" href="css/screen.css"> <link rel="stylesheet" href="css/animation.css"> <!--[if IE 7]> <![endif]--> <link rel="stylesheet" href="css/font-awesome.css"> <!--[if lt IE 8]> <link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection"> <![endif]--> <link href="css/lity.css" rel="stylesheet"> <style> [v-cloak] { display: none; } </style> </head> <body> <div id="myapp"> <!-- SINGLE VIDEO --> <div id="single-video" class="container-fluid standard-bg"> <!-- HEADER --> <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> <!-- MENU --> <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 navigation</span> <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-collapse --> </nav> </div> </div> <!-- 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-9 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"> <iframe v-bind:src="video.video" class="embed-responsive-item"></iframe> </div> <!-- <div class="metabox"> <span class="meta-i"> <i class="fa fa-thumbs-up" aria-hidden="true"></i>20.895 </span> <span class="meta-i"> <i class="fa fa-thumbs-down" aria-hidden="true"></i>3.981 </span> <span class="meta-i"> <i class="fa fa-user"></i><a href="#" class="author" title="John Doe">John Doe</a> </span> <span class="meta-i"> <i class="fa fa-clock-o"></i>March 16. 2017 </span> <span class="meta-i"> <i class="fa fa-eye"></i>1,347,912 views </span> <div class="ratings"> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star-half-o" aria-hidden="true"></i> <i class="fa fa-star-o"></i> <i class="fa fa-star-half"></i> </div> </div> --> </div> <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-3 hidden-md hidden-sm"> </div> </div> <div class="clearfix spacer"></div> <div class="row"> </div> </div> </div> </div> <!-- CHANNELS --> <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> <!-- FOOTER --> <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> <!-- MODAL --> <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="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> <!-- JAVA SCRIPT --> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <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>
8.2 前端视频观看页面的实现
<!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> <!-- Bootstrap core CSS --> <link href="css/bootstrap.css" rel="stylesheet"> <!-- Custom styles for this template --> <link rel="stylesheet" href="css/screen.css"> <link rel="stylesheet" href="css/animation.css"> <!--[if IE 7]> <![endif]--> <link rel="stylesheet" href="css/font-awesome.css"> <!--[if lt IE 8]> <link rel="stylesheet" href="css/ie.css" type="text/css" media="screen, projection"> <![endif]--> <link href="css/lity.css" rel="stylesheet"> <style> [v-cloak] { display: none; } </style> </head> <body> <div id="myapp"> <!-- SINGLE VIDEO --> <div id="single-video" class="container-fluid standard-bg"> <!-- HEADER --> <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> <!-- MENU --> <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 navigation</span> <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-collapse --> </nav> </div> </div> <!-- 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-9 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"> <iframe v-bind:src="video.video" class="embed-responsive-item"></iframe> </div> <!-- <div class="metabox"> <span class="meta-i"> <i class="fa fa-thumbs-up" aria-hidden="true"></i>20.895 </span> <span class="meta-i"> <i class="fa fa-thumbs-down" aria-hidden="true"></i>3.981 </span> <span class="meta-i"> <i class="fa fa-user"></i><a href="#" class="author" title="John Doe">John Doe</a> </span> <span class="meta-i"> <i class="fa fa-clock-o"></i>March 16. 2017 </span> <span class="meta-i"> <i class="fa fa-eye"></i>1,347,912 views </span> <div class="ratings"> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star" aria-hidden="true"></i> <i class="fa fa-star-half-o" aria-hidden="true"></i> <i class="fa fa-star-o"></i> <i class="fa fa-star-half"></i> </div> </div> --> </div> <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-3 hidden-md hidden-sm"> </div> </div> <div class="clearfix spacer"></div> <div class="row"> </div> </div> </div> </div> <!-- CHANNELS --> <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> <!-- FOOTER --> <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> <!-- MODAL --> <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="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> <!-- JAVA SCRIPT --> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <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.服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理