【项目】视频点播系统2

本文涉及的产品
数据管理 DMS,安全协同 3个实例 3个月
推荐场景:
学生管理系统数据库
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 【项目】视频点播系统

【项目】视频点播系统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 

【说明】

  1. 在判断文件是否存在时使用的接口是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
  1. 在获取文件大小的函数中使用了一个接口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 */
};


  1. 在创建文件目录时使用的函数是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方法:表示删除
  • 资源正文数据采用JsonXML数据格式


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

获取所有视频信息:

请求:
GET /video HTTP/1.1
Connection: keep-alive
......
响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......
[
  {
    "id": 1,
    "name": "电影1",
    "info": "xxxxxx",
    "video": "/video/movie1.mp4",
    "image": "/img/thumbs/movie1.png",
  },
  {
    "id": 2,
    "name": "电影2",
    "info": "xxxxxx",
    "video": "/video/movie2.mp4",
    "image": "/img/thumbs/movie2.png",
  }
]

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


请求:
GET /video?search="电影1" HTTP/1.1
Connection: keep-alive
......
响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......
[
  {
    "id": 1,
    "name": "电影1",
    "info": "xxxxxx",
    "video": "/video/movie1.mp4",
    "image": "/img/thumbs/movie1.png",
  }
]


获取指定视频信息:

请求:
GET /video/1 HTTP/1.1
Connection: keep-alive
......
响应:
HTTP/1.1 200 OK
Content-Length: xxx
Content-Type: application/json
......
[
  {
    "id": 1,
    "name": "电影1",
    "info": "xxxxxx",
    "video": "/video/movie1.mp4",
    "image": "/img/thumbs/movie1.png",
  }
]


删除指定视频信息:

请求:
DELETE /video/1 HTTP/1.1
Connection: keep-alive
......
响应:
HTTP/1.1 200 OK
......

修改指定视频信息:

请求:
PUT /video/1 HTTP/1.1
Connection: keep-alive
......
响应:
HTTP/1.1 200 OK
......


上传视频信息:

因为在上传视频信息的时候,会携带有视频文件、封面图片文件的上传,而这些文件数据都是二进制的,所以使用Json格式就不再合适了。因此在上传视频的时候就使用HTTP协议默认的上传文件请求格式,而不使用REST风格。

请求:
PSOT /video HTTP/1.1
Content-Type: video/form-data; boundary="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
Content-Length: xxx
......
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="name"
name(视频的名称)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="info"
info(视频的描述)
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="video"; filename="video.mp4"
Content-Type: text/plain
video视频数据
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="image"; filename="image.jpg"
Content-Type: text/plain
image封面图片数据
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name="submit"
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
响应:
HTTP/1.1 303 See Other
Location: "/"

七、业务处理模块的实现

7.1 业务处理模块类的设计

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


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


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

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

代码框架如下:

#ifndef __MY_SERVER__
#define __MY_SERVER__ 
#include "data.hpp"
#include "httplib.h"
namespace aod
{
#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 &copy; 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 &copy; 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 &copy; 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.服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理

目录
相关文章
|
4月前
|
JSON 关系型数据库 MySQL
【项目】视频点播系统1
【项目】视频点播系统
61 0
|
9月前
|
SQL 前端开发 JavaScript
基于java+springboot的视频点播网站-在线视频点播系统
该系统是基于java+springboot开发的视频点播系统。是给师妹开发的课程设计。
409 0
|
9月前
|
存储 编解码 应用服务中间件
如何实现一个c/s模式的flv视频点播系统
如何实现一个c/s模式的flv视频点播系统
71 0
|
编解码 应用服务中间件 nginx
|
6月前
|
存储 安全 机器人
如何下载阿里云视频点播数据
如何下载阿里云视频点播(VOD)数据
254 0
|
9月前
|
存储 编解码 安全
阿里云视频点播简介和购买流程
阿里云视频点播是阿里巴巴集团旗下的一项强大的视频云服务,为用户提供在线视频上传、存储、转码、播放等全方位的视频解决方案。作为中国最大的云计算服务提供商,阿里云视频点播在视频领域拥有丰富的技术实力和经验,为用户提供高效、可靠、安全的视频服务。
|
3月前
|
NoSQL Java 微服务
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)-3
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)
126 1
|
3月前
|
前端开发 NoSQL easyexcel
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)-2
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)
103 0
|
3月前
|
NoSQL 应用服务中间件 对象存储
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)-1
前后端分离项目知识汇总(阿里云Oss,EasyExcel,视频点播,SpringCloud,Redis,Nuxt)
76 0

热门文章

最新文章