【从零到一,C++项目实战】CineShare++(基于C++的视频点播系统)

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正


文章目录

  • 前言
  • 开发环境
  • 第三方库
    • jsoncpp
    • Mysql 数据库 API 认识
    • httplib
  • 项目实现
    • 服务端工具类实现
      • 文件实用工具类
        • 完整代码
        • 功能测试
      • Json实用工具类实现
      • 功能测试
    • 服务端数据管理功能
      • 视频数据表的设计
      • 数据管理类设计
        • 功能测试
    • 服务端业务处理模块
      • 网络通信接口设计
      • 业务处理类模块设计
        • 功能测试
    • 前端界面实现

前言

项目源代码:代码仓库

开发环境

项目名称:CineShare++(基于C++的视频点播系统)

项目功能:搭建一个共享点播系统,服务器能够支持用户通过前端浏览器进行访问,提供清晰的展示、观看和操作界面。该系统不仅可以实现视频的上传功能,还能满足用户观看视频的需求。同时,系统还具备基本的管理功能,包括视频的增、删、改、查等操作,以便管理员对视频内容进行有效管理和维护。通过此系统,用户可以便捷地上传和浏览视频,管理员则能确保内容的有序管理与更新。

开发环境包括:Ubuntu 22.04 操作系统VSCode 编辑器g++ 编译器gdb 调试工具,以及 Makefile 构建工具

技术特点包括:HTTP 服务器搭建、RESTful 风格接口设计、JSON 序列化处理、线程池管理,以及 HTML、CSS 和 JavaScript 基础开发。这些技术共同支持系统的高效性、可扩展性和用户体验。

项目模块:

  • 数据管理模块:基于Mysql进行数据管理
  • 业务处理模块:基于httplib搭建http,使用RESTful 风格接口设计处理客户端需求
  • 前端界面模块:基于基础的HTML+CSS+JS完成基于简单模板前端界⾯的修改与功能完成

第三方库

jsoncpp

json 是⼀种数据交换格式,采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。

这个第三方库在文章【Linux网络编程】应用层:自定义协议 | 序列化和反序列化 | 系统角度理解read、write、recv、 send 和 tcp 为什么支持全双工 | 实现网络版计算器 | jsoncpp库中介绍过以及了解如何安装jsoncpp库。

  • json 数据类型:对象,数组,字符串,数字
  • 对象:使⽤花括号 {} 括起来的表⽰⼀个对象。
  • 数组:使⽤中括号 [] 括起来的表⽰⼀个数组。
  • 字符串:使⽤常规双引号 "" 括起来的表⽰⼀个字符串

在本项目直接使用jsoncpp的接口,不需要我们自己去写字符串序列化与反序列化,这样减少了项目的开发周期。

jsoncpp实现的功能:

  • 实现json格式的序列化与反序列化
  • 完成将多个数据对象组织称为json格式字符串
  • 完成将json格式字符串解析得到多个数据对象

这三个功能主要借助三个类以及对应的成员函数来实现的:

  • Json::Value:主要实现中间数据的存储
class Json::Value{
	Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取
	数据都可以通过
	Value& operator[](const std::string& key);//简单的⽅式完成 val["姓名"] = "⼩
	明";
	Value& operator[](const char* key);
	Value removeMember(const char* key);//移除元素
	const Value& operator[](ArrayIndex index) const; //val["成绩"][0]
	Value& append(const Value& value);//添加数组元素val["成绩"].append(88);
	ArrayIndex size() const;//获取数组元素个数 val["成绩"].size();
	std::string asString() const;//转string string name =
	val["name"].asString();
	const char* asCString() const;//转char* char *name =
	val["name"].asCString();
	Int asInt() const;//转int int age = val["age"].asInt();
	float asFloat() const;//转float
	bool asBool() const;//转 bool
};
  • JSON_API StreamWriter类:用于对Json::Value中数据进行序列化
class JSON_API StreamWriter {
	virtual int write(Value const& root, std::ostream* sout) = 0;
}
  • JSON_API CharReader:实现反序列化,解析得到的对象存储到Json::Value
class JSON_API CharReader {
	virtual bool parse(char const* beginDoc, char const* endDoc,
						Value* root, std::string* errs) = 0;
}

将Value中存储的数据转换成Json字符串,实现序列化:

#include<iostream>
#include<sstream>
#include<string>
#include<memory>
#include<jsoncpp/json/json.h>

int main()
{
    const char *name = "Gavin";
    int age = 21;
    float score[] = {150, 149, 148};
    Json::Value val;
    val["Name"] = name;
    val["Age"] = age;
    val["Score"].append(score[0]);
    val["Score"].append(score[1]);
    val["Score"].append(score[2]);

    Json::StreamWriterBuilder swb;

    // 设置 JSON 输出不转义 Unicode 字符
    //swb.settings_["indentation"] = "";  // 去掉缩进
    swb.settings_["ensureAscii"] = false;  // 使中文字符不被转义

    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream ss;
    int ret = sw->write(val,&ss);
    if(ret != 0)
    {
        std::cout<<"write failed!\n";
        return -1;
    }
    std::cout << ss.str() <<std::endl;

    return 0;
}

在这里插入图片描述

将Json字符串转换到原始格式,实现反序列化:

void unserialize(const std::string &str)
{
    Json::Value val;
    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(),&val,&err);
    if(ret==false)
    {
        std::cout << "parse failed\n";
        return ;
    }
    std::cout << val["Name"] <<std::endl;
    std::cout << val["Age"] <<std::endl;
    int sz = val["Score"].size();
    for(int i=0;i<sz;i++)
        std::cout << val["Score"][i] <<std::endl;
}

int main()
{
    //serialize();
    std::string str = R"({"Name":"Gavin", "Age":"22", "Score":[66, 77, 88]})";
    unserialize(str);
    return 0;
}

在这里插入图片描述

Mysql 数据库 API 认识

MySQL操作句柄初始化

MYSQL *mysql_init(MYSQL *mysql)

连接MySQL服务器

MYSQL *mysql_real_connect(MYSQL *mysql, const char *host, const char *user,const char *passwd,const char *db, unsigned int port,
const char *unix_socket, unsigned long client_flag);

//mysql--初始化完成的句柄
//host---连接的mysql服务器的地址
//user---连接的服务器的⽤⼾名
//passwd-连接的服务器的密码
//db ----默认选择的数据库名称
//port---连接的服务器的端⼝: 默认0是3306端⼝
//unix_socket---通信管道⽂件或者socket⽂件,通常置NULL
//client_flag---客⼾端标志位,通常置0
//返回值:成功返回句柄,失败返回NULL
//设置当前客⼾端的字符集

设置当前客户端的字符集

int mysql_set_character_set(MYSQL *mysql, const char *csname)

//mysql--初始化完成的句柄
//csname--字符集名称,通常:"utf8"
//返回值:成功返回0, 失败返回⾮0;
//选择操作的数据库

选择操作的数据库

int mysql_select_db(MYSQL *mysql, const char *db)

执行sql语句:

int mysql_query(MYSQL *mysql, const char *stmt_str)
//mysql--初始化完成的句柄
//stmt_str--要执⾏的sql语句

保存查询结果到本地

MYSQL_RES *mysql_store_result(MYSQL *mysql)

获取结果集中的⾏数与列数

uint64_t mysql_num_rows(MYSQL_RES *result)//result--保存到本地的结果集地址
//返回值:结果集中数据的条数

unsigned int mysql_num_fields(MYSQL_RES *result)
//result--保存到本地的结果集地址
//返回值:结果集中每⼀条数据的列数

遍历结果

MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)
//result--保存到本地的结果集地址
//返回值:实际上是⼀个char **的指针,将每⼀条数据做成了字符串指针数组 row[0]-第0列row[1]-第1列
//并且这个接⼝会保存当前读取结果位置,每次获取的都是下⼀条数据

释放结果集

oid mysql_free_result(MYSQL_RES *result)
//result--保存到本地的结果集地址
//返回值:void

关闭数据库客⼾端连接,销毁句柄

void mysql_close(MYSQL *mysql)

获取mysql接口执⾏错误原因

const char *mysql_error(MYSQL *mysql)

测试样例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <mysql/mysql.h>

void add(MYSQL *mysql)
{
    char *sql = "insert into test_tb values(NULL, 22, '小高', 88);";
    int ret = mysql_query(mysql, sql);
    if (ret != 0)
    {
        printf("mysql query failed, err: %d, error: %s\n", mysql_errno(mysql), mysql_error(mysql));
        return;
    }
}

void mod(MYSQL *mysql)
{
    char *sql = "update test_tb set age=25 where name='小高';";
    int ret = mysql_query(mysql,sql);
    if (ret != 0)
    {
        printf("mysql query failed, err: %d, error: %s\n", mysql_errno(mysql), mysql_error(mysql));
        return;
    }
}

void del(MYSQL *mysql)
{
    char *sql = "delete from test_tb where name='小高';";
    int ret = mysql_query(mysql,sql);
    if (ret != 0)
    {
        printf("mysql query failed, err: %d, error: %s\n", mysql_errno(mysql), mysql_error(mysql));
        return;
    }
}

void get(MYSQL *mysql)
{
    char *sql = "select * from test_tb;";
    int ret = mysql_query(mysql, sql);
    if (ret != 0)
    {
        printf("mysql query failed, err: %d, error: %s\n", mysql_errno(mysql), mysql_error(mysql));
        return;
    }
    MYSQL_RES *res = mysql_store_result(mysql);
    if (res == NULL)
    {
        printf("mysql store result error:%s\n", mysql_error(mysql));
        return;
    }
    int row = mysql_num_rows(res);
    int col = mysql_num_fields(res);
    printf("%10s%10s%10s%10s\n", "ID", "年龄", "姓名", "成绩");
    for (int i = 0; i < row; i++)
    {
        MYSQL_ROW row_data = mysql_fetch_row(res);
        for (int i = 0; i < col; i++)
        {
            printf("%10s", row_data[i]);
        }
        printf("\n");
    }
    mysql_free_result(res);
    return;
}

int main()
{
    MYSQL *mysql = mysql_init(NULL);

    if ((mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "test_db", 0, NULL, 0)) == NULL)
    {
        printf("mysql connect failed: %d, error: %s\n", mysql_errno(mysql), mysql_error(mysql));
        return -1;
    }
    mysql_set_character_set(mysql, "utf8");

    //add(mysql);
    //add(mysql);
    //mod(mysql);
    //del(mysql);
    get(mysql);
    mysql_close(mysql);  // 关闭连接
    return 0;
}

httplib

httplib 库,⼀个 C++11 单⽂件头的跨平台HTTP/HTTPS库。

httplib 库实际上是⽤于搭建⼀个简单的 http 服务器或者客户端的库,这种第三⽅⽹络库,可以让我们免去搭建服务器或客⼾端的时间,把更多的精⼒投⼊到具体的业务处理中,提⾼开发效率。

在这里插入图片描述

测试用例

#include"./httplib.h"
using namespace httplib;

void HelloGavin(const Request &req, Response &rsp)
{
    rsp.body = "Hello Gavin!";
    rsp.status = 200;
}
void Numbers(const Request &req, Response &rsp)
{
    std::string num = req.matches[1]; // matches存放正则表达式匹配的规则数据,也就是()里面单独捕捉的数据
    rsp.set_content(num, "Text/plain");
    rsp.status = 200; // 向rsp.body里面添加数据
}
void Multipart(const Request &req, Response &rsp)
{
    if(req.has_file("file1") == false)
    {
        rsp.status = 100;
        return;
    }

    const auto& file = req.get_file_value("file1");    
    std::cout << file.filename << std::endl;
    std::cout << file.content << std::endl;

    rsp.status = 200;
}
int main()
{
    Server server;

    // 设置一个静态资源目录
    server.set_mount_point("/","./www");

    // 添加请求:处理函数映射信息
    server.Get("/hi",HelloGavin);
    // 正则表达式:\d表示数字,+表示匹配匹配前面的字符一次或者多次,()表示单独捕捉数据
    server.Get("/numbers/(\\d+)",Numbers);
    server.Post("/multipart",Multipart);
    server.listen("0.0.0.0", 8888);

    return 0;
}

在这里插入图片描述

项目实现

服务端工具类实现

文件实用工具类

在视频点播系统中因为涉及到⽂件上传,需要对上传的⽂件进⾏备份存储,因此⾸先设计封装⽂件操作类,这个类封装完毕之后,则在任意模块中对⽂件进⾏操作时都将变的简单化。

功能:

  1. 获取文件大小(属性)
  2. 判断文件是否存在
  3. 向文件写入数据
  4. 从文件读取数据
  5. 针对目录文件多创建一个目录

工具类的框架

namespace aod
{
    class FileUtil 
    {
    public:
        FileUtil(const std::string name):_name(name){}
        bool Exists(); // 判断文件是否存在
        size_t Size(); // 获取文件大小
        bool ReadContent(std::string *body); // 读取文件数据到body中
        bool WriteContent(const std::string &body); //向文件写入数据
        bool CreateDirectory(); // 针对目录创建目录
        
    private:
        std::string _name;
    };
}

判断文件是否存在

调用Linux中access()函数:

#include <unistd.h>
int access(const char *pathname, int mode);

成功返回0,否则出错返回-1

FileUtil(const std::string name):_name(name){}
// 判断文件是否存在
bool Exists()
{
    int ret = access(_name.c_str(), F_OK); // F_OK专门用于检测文件是否存在:存在返回0,不存在返回-1
    if(ret != 0) return false;
    return true;
}

获取文件大小

Linux中获取文件属性可以调用stat()函数

 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>

 int stat(const char *pathname, struct stat *statbuf);

功能实现:

// 获取文件大小
size_t Size()
{
    if(this->Exists() == false) return 0;
    struct stat st;
    int ret = stat(_name.c_str(), &st);
    if(ret != 0) return 0;
    return st.st_size;
}

读取文件数据到body中

  • 实例化一个文件输入流
  • 以二进制形式打开文件
  • 获取文件大小
  • 重置缓冲区大小
  • 将文件依次输入到缓冲区中
  • 成功后关闭文件
        // 读取文件数据到body中
        bool ReadContent(std::string *body)
        {
            std::ifstream ifs;
            ifs.open(_name, std::ios::binary); // 以二进制方式打开
            if(ifs.is_open() == false)
            {
                std::cout << "open failed!\n";
                return false;
            }
            size_t flen = this->Size();
            body->resize(flen);
            ifs.read(&(*body)[0], flen);
            //body[0] 是 std::string 对象的第一个字符,而不是字符串的底层缓冲区地址
            // (*body) 获取指针 body 指向的 std::string 对象,
            //然后 (*body)[0] 获取该字符串的第一个字符,&(*body)[0] 获取该字符的地址,也就是底层数据的起始位置
            if(ifs.good() == false)
            {
                std::cout << "read file content failed!\n";
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }

向文件写入数据

  • 实例化一个文件输出流
  • 以二进制形式打开
  • 写入数据
  • 写入成功后关闭文件
 //向文件写入数据
 bool WriteContent(const std::string &body)
 {
     std::ofstream ofs;
     ofs.open(_name, std::ios::binary); // 以二进制方式打开
     if(ofs.is_open() == false)
     {
         std::cout << "open failed!\n";
         return false;
     }
     ofs.write(body.c_str(), body.size());
     if(ofs.good() == false)
     {
         std::cout << "write file content failed!\n";
         ofs.close();
         return false;
     }
     ofs.close();
     return true;
 }

针对目录创建目录

在Linux中调用mkdir()函数:

#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);

功能实现:

 // 针对目录创建目录
 bool CreateDirectory()
 {
     if(this->Exists()) return true;
     mkdir(_name.c_str(),0777);
     return true;
 }
完整代码
#ifndef __MY_UTIL__
#define __MY_UTIL__
#include<iostream>
#include<fstream>
#include<string>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>

namespace aod
{
    class FileUtil 
    {
    public:
        FileUtil(const std::string name):_name(name){}
        // 判断文件是否存在
        bool Exists()
        {
            int ret = access(_name.c_str(), F_OK); // F_OK专门用于检测文件是否存在:存在返回0,不存在返回-1
            if(ret != 0)
            {
                std::cout << "file is not exists!\n";
                return false;
            }
            return true;
        }

        // 获取文件大小
        size_t Size()
        {
            if(this->Exists() == false) return 0;
            struct stat st;
            int ret = stat(_name.c_str(), &st);
            if(ret != 0) 
            {
                std::cout << "get file stat failed!\n";   
                return 0;
            }
            return st.st_size;
        }

        // 读取文件数据到body中
        bool ReadContent(std::string *body)
        {
            std::ifstream ifs;
            ifs.open(_name, std::ios::binary); // 以二进制方式打开
            if(ifs.is_open() == false)
            {
                std::cout << "open failed!\n";
                return false;
            }
            size_t flen = this->Size();
            body->resize(flen);
            ifs.read(&(*body)[0], flen);
            //body[0] 是 std::string 对象的第一个字符,而不是字符串的底层缓冲区地址
            // (*body) 获取指针 body 指向的 std::string 对象,
            //然后 (*body)[0] 获取该字符串的第一个字符,&(*body)[0] 获取该字符的地址,也就是底层数据的起始位置
            if(ifs.good() == false)
            {
                std::cout << "read file content failed!\n";
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }

        //向文件写入数据
        bool WriteContent(const std::string &body)
        {
            std::ofstream ofs;
            ofs.open(_name, std::ios::binary); // 以二进制方式打开
            if(ofs.is_open() == false)
            {
                std::cout << "open failed!\n";
                return false;
            }
            ofs.write(body.c_str(), body.size());
            if(ofs.good() == false)
            {
                std::cout << "write file content failed!\n";
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        // 针对目录创建目录
        bool CreateDirectory()
        {
            if(this->Exists()) return true;
            mkdir(_name.c_str(),0777);
            return true;
        }
    private:
        std::string _name;
    };
}

#endif
功能测试
void FileTest1()
{
    aod::FileUtil("./www").CreateDirectory();
    aod::FileUtil("./www/index.html").WriteContent("<html></html>");
    std::string body;
    aod::FileUtil("./www/index.html").ReadContent(&body);
    std::cout << body << std::endl;
    std::cout << aod::FileUtil("./www/index.html").Size() <<std::endl;
}

在这里插入图片描述

Json实用工具类实现

主要实现功能:

  1. 实现序列化
  2. 实现反序列化

工具框架

// Json工具类
class JsonUtil
{
public:
    static bool Serialize(const Json::Value &value ,std::string *body);
    static bool Unserialize(const std::string &body, Json::Value *value);
};

序列化

  • Json::StreamWriterBuilder:这是 JsonCpp 提供的一个构建器,用于配置并生成一个流写入器 StreamWriter。该写入器负责将 Json::Value 转换为 JSON 格式的字符串。
  • std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter()):创建一个流写入器对象 sw,它会根据 StreamWriterBuilder 中的配置来序列化 JSON 数据。
  • std::stringstream ss:一个字符串流,用于暂时存储序列化后的结果。
  • sw->write(value, &ss):执行序列化操作,将 value 序列化到字符串流 ss 中。write 函数返回值为 0 时表示成功,非 0 则表示失败。
  • *body = ss.str():将字符串流 ss 中的内容转换为字符串,并赋值给 body
static bool Serialize(const Json::Value &value ,std::string *body)
{
    Json::StreamWriterBuilder swb;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());

    std::stringstream ss;
    int ret = sw->write(value, &ss);
    if(ret != 0)
    {
        std::cout << "Serialize failed!\n";
        return;
    }
    *body = ss.str();
    return true;
}

static bool Unserialize(const std::string &body, Json::Value *value);
};

反序列化

  • 定义一个智能指针对象
  • 将智能指针指向的对象直接反序列化
  • 反序列化成功直接将结果传给了vlaue
static bool Unserialize(const std::string &body, Json::Value *value)
{
   Json::CharReaderBuilder crb;
   std::unique_ptr<Json::CharReader> cr(crb.newCharReader());

   std::string err;
   bool ret = cr->parse(body.c_str(), body.c_str() + body.size(), value, &err);
   if(ret == false)
   {
       std::cout << "Unserialize failed!\n";
       return false;
   }
   return true;
}

功能测试

void JsonTest1()
{
    Json::Value val;
    val["Name"] = "Gavin";
    val["Sex"] = "man";
    val["Age"] = 21;
    val["Score"].append(110.5);   
    val["Score"].append(110.5);   
    val["Score"].append(120.5);

    std::string body;
    aod::JsonUtil::Serialize(val, &body);
    std::cout << body <<std::endl;

    Json::Value stu;
    aod::JsonUtil::Unserialize(body, &stu);
    std::cout << stu["Name"].asString() <<std::endl;   
    std::cout << stu["Sex"].asString() <<std::endl;   
    std::cout << stu["Age"].asString() <<std::endl;
    for(auto &a : stu["Score"])
    {
        std::cout << a.asFloat() <<std::endl;
    }   
}

在这里插入图片描述

服务端数据管理功能

视频数据表的设计

使用 MySQL数据库管理视频信息,只管理视频及图片的信息,视频以及图片是存储的文件中。

本项目中只是完成一个简单的视频信息表:

  • 视频ID
  • 视频名称
  • 视频描述信息
  • 视频文件的url路径(只是链接)
  • 视频封面图片的url路径(只是链接)
create database if not exists aod_system;
use aod_system;
create table if not exists tb_video(
id int primary key auto_increment comment '视频ID',
name varchar(32) comment '视频名称',
info text comment '视频描述',
video varchar(256) comment '视频⽂件url,加上静态资源根⽬录就是实际存储路径',
image varchar(256) comment '封⾯图⽚⽂件url,加上静态资源根⽬录就是实际存储路径'
);

在这里插入图片描述

数据管理类设计

本模块主要为了实现对数据库的增删查改管理,但是实际的项目中,会存在多个表,每个表要操作的数据不一样,因此对数据进行增删查改时需要为每一个表进行创建一个类。

视频操作:

  • 新增
  • 修改
  • 删除
  • 查询所有
  • 查询单个
  • 模糊匹配

视频信息在接口直接的传递因为字段数量可能很多,因此需要用Json::Value对象进行存储

基本框架

static MYSQL *MysqlInit();
static void MysqlDestory();
static bool MysqlQuery(MYSQL *mysql, const std::string &sql);
class TableVideo
{
public:
    TableVideo();
    ~TableVideo();
    bool Insert(const Json::Value &video);
    bool Update(int video_id, const Json::Value &video);
    bool Delete(int video_id);
    bool SelectAll(Json::Value *videos);
    bool SelectOne(int video_id, const Json::Value *video);
    bool SelectLike(const std::string &key, Json::Value *videos);
private:
    MYSQL *mysql;
    std::mutex _mutex;
};

初始化

  • 初始化一个句柄
  • 建立连接
  • 设置字符集
  • 将初始化的句柄返回给外部
static MYSQL *MysqlInit()
{
    MYSQL *mysql = mysql_init(NULL);
    if(mysql == NULL)
    {
        std::cout << "Init failed!\n";
        return NULL;
    }
    if(mysql_real_connect(mysql, HOST, USER, PASS, NAME, 0, NULL, 0) ==NULL)
    {
        std::cout << "connect failed!\n";
        mysql_close(mysql);
        return NULL;
    }
    mysql_set_character_set(mysql,"utf8");
    return mysql;
}

销毁

  • 句柄不为空时直接销毁即可
static void MysqlDestory(MYSQL *mysql)
{
    if(mysql != NULL)
    {
        mysql_close(mysql);
    }
    return;
}

语句执行

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 << mysql_error(mysql) << std::endl;
        return false; 
    }
    return true;
}

构造与析构

  • 直接调用初始化接口以及销毁接口
TableVideo()
{
    _mysql = MysqlInit();
    if(_mysql == NULL) 
        exit(-1);
}

~TableVideo()
{
    MysqlDestory(_mysql);
}

新增操作

  • 定义一个sql表示查询语句
  • 调整sql的大小
  • 设置一个sql语句模版
  • 格式化字符串填充语句
bool Insert(const Json::Value &video)
{
    std::string sql;
    sql.resize(4096 + video["info"].asString().size()); // 防止简介过长
    #define INSERT_VIDEO "INSERT tb_video values(null, '%s', '%s', '%s', '%s');"
    sprintf(&sql[0], INSERT_VIDEO, 
                video["name"].asString(),
                video["info"].asString(),
                video["video"].asString(),
                video["image"].asString()
            );
    return MysqlQuery(_mysql,sql);
}

修改操作

bool Update(int video_id, const Json::Value &video)
{
    std::string sql;
    sql.resize(4096 + video["info"].asString().size()); // 防止简介过长
    #define UPDATE_VIDEO "update tb_video set name='%s', info='%s' where id=%d;"
    sprintf(&sql[0], UPDATE_VIDEO, 
            video["name"].asString(),
            video["info"].asString(),
            video_id
            );
    return MysqlQuery(_mysql,sql);
}

删除操作

bool Delete(int video_id)
{
    #define DELETE_VIDEO "delete from tb_video where id=%d;"
    char sql[1024] = {0};
    sprintf(sql, DELETE_VIDEO, video_id);
    return MysqlQuery(_mysql, sql);
}

查询所有数据

  • 类实例化的对象可能在多线程环境下,因此需要保证在查询结果后将数据保存到本地操作时原子的,避免一个线程还没有保存到本地,另一个线程开始查询,此时线程就会
  • 处理查新结果:将查询到的结果存储在MYSQL_RES结构体中
  • 遍历查询结果:
    • mysql_num_rows(res):获取查询结果的行数,即视频记录的数量
    • mysql_fetch_row(res):遍历查询结果集,返回当前行的数据(每行是一个MYSQL_ROW 数组)
    • 在遍历过程中,使用atoi 函数将 row[i] 转换为整数并赋值给 video 对象的相应字段
bool SelectAll(Json::Value *videos)
{
    #define SELECTALL_VIDEO "select * from tb_video;"
    _mutex.lock();
    bool ret = MysqlQuery(_mysql, SELECTALL_VIDEO);
    if(ret == false)
    {
        _mutex.unlock();
        return false;
    }
    MYSQL_RES *res = mysql_store_result(_mysql);
    if(res == NULL)
    {
        std::cout << "mysql store result failed!\n";
        _mutex.unlock();
        return false;
    }
    _mutex.unlock();
    int num_rows = mysql_num_rows(res);
    for(int i = 0; i < num_rows; i++)
    {
        MYSQL_ROW row = mysql_fetch_row(res);
        Json::Value video;
        video["id"] = atoi(row[0]);
        video["name"] = atoi(row[1]);
        video["info"] = atoi(row[2]);
        video["video"] = atoi(row[3]);
        video["image"] = atoi(row[4]);
        videos->append(video);
    }
    mysql_free_result(res);
    return true;
}

查询单个数据

bool SelectOne(int video_id, Json::Value *video)
{
    #define SELECTONE_VIDEO "select * from tb_video where id=%d;"
    char sql[1024] = {0};
    sprintf(sql, SELECTONE_VIDEO, video_id);
    _mutex.lock();
    bool ret = MysqlQuery(_mysql, sql);
    if(ret == false)
    {
        _mutex.unlock();
        return false;
    }
    MYSQL_RES *res = mysql_store_result(_mysql);
    if(res == NULL)
    {
        std::cout << "mysql store result failed!\n";
        _mutex.unlock();
        return false;
    }
    _mutex.unlock();
    int num_rows = mysql_num_rows(res);
    if(num_rows != 1)
    {
        std::cout << "hace no data!\n";
        mysql_free_result(res);
        return false;
    }

    MYSQL_ROW row = mysql_fetch_row(res);
    (*video)["id"] = video_id;
    (*video)["name"] = row[1];
    (*video)["info"] = row[2];
    (*video)["video"] = row[3];
    (*video)["image"] = row[4];

    mysql_free_result(res);
    return true;            
}

模糊匹配

bool SelectLike(const std::string &key, Json::Value *videos)
{
    #define SELECTLIKE_VIDEO "select * from tb_video where name like '%%%s%%';"
    char sql[2014] = {0};
    sprintf(sql, SELECTLIKE_VIDEO, key.c_str());
    _mutex.lock();
    bool ret = MysqlQuery(_mysql, sql);
    if(ret == false)
    {
        _mutex.unlock();
        return false;
    }
    MYSQL_RES *res = mysql_store_result(_mysql);
    if(res == NULL)
    {
        std::cout << "mysql store result failed!\n";
        _mutex.unlock();
        return false;
    }
    _mutex.unlock();
    int num_rows = mysql_num_rows(res);
    for(int i = 0; i < num_rows; i++)
    {
        MYSQL_ROW row = mysql_fetch_row(res);
        Json::Value video;
        video["id"] = atoi(row[0]);
        video["name"] = atoi(row[1]);
        video["info"] = atoi(row[2]);
        video["video"] = atoi(row[3]);
        video["image"] = atoi(row[4]);
        videos->append(video);
    }
    mysql_free_result(res);
    return true;        
}
功能测试
void DataTest()
{
    aod::TableVideo tb_video;
    Json::Value video;
    // video["name"] = "Jhon";
    // video["info"] = "a man but not a student";
    // video["video"] = "/video/man.ma4";
    // video["image"] = "/image/man.jpg";
    //tb_video.Insert(video);
    // tb_video.Update(2, video);
    // tb_video.SelectAll(&video);
    // tb_video.SelectOne(1, &video);
    // tb_video.SelectLike("Gavin", &video);
    // std::string body;
    // aod::JsonUtil::Serialize(video, &body);
    // std::cout << body <<std::endl;
    tb_video.Delete(2);
}

服务端业务处理模块

网络通信接口设计

http协议其实是一种数据格式,是一个tcp传输,在应用层采用的一种数据特定格式

网络通信接口设计就是定义好什么样的请求是一个查询请求,什么的请求是一个删除请求。

服务端提供的功能:新增视频、删除视频、修改视频、查询所有视频信息、查询单个视频、模糊匹配视频

接口设计:使用restful接口设计
RESTful 架构可以充分的利⽤HTTP协议的各种功能,是 HTTP 协议的最佳实践,正⽂通常采⽤ JSON 格式,使用GET表示查询,POST表示新增,PUT表示修改,DELETE表示删除,并且正文资源数据采用jsonhtml格式。

业务处理类模块设计

本项目中,业务处理主要包含两个功能:

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

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

网络通信借助httplib库即可快速搭建,因此完成此项目重点就可以在业务处理中。

业务处理功能包括:

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

业务处理模块框架

#include"data.hpp"
#include"httplib.h"

namespace aod
{
#define WWWROOT "./www"
#define VIDEO_ROOT "./video/"   // 实际上是"./www/video/"
#define VIDEO_IMAGE "./image"
    TableVideo *tb_video = NULL;
    class Server
    {
    public:
        Server(int port): _port(port)
        {}

        bool RunModule();

    private:
        int _port;
        httplib::Server _ser;
    
    private:
        static void Insert(const httplib::Request &req, httplib::Response &res); // 这里需要定义成一个静态成员函数
        // 静态成员函数的参数不包括this指针,确保这个函数只有两个参数
        static void Update(const httplib::Request &req, httplib::Response &res);
        static void Delete(const httplib::Request &req, httplib::Response &res);
        static void GetOne(const httplib::Request &req, httplib::Response &res);
        static void GetAll(const httplib::Request &req, httplib::Response &res);
    };
}

启动服务器

  • 首先初始化数据管理模块,创建指定的地址
  • 搭建http服务器,使用httplib
bool RunModule()
{
    //1.初始化数据管理模块,创建指定的目录
    tb_video = new TableVideo();
    FileUtil(WWW_ROOT).CreateDirectory();
    std::string video_real_path = WWW_ROOT + VIDEO_ROOT;
    FileUtil(video_real_path).CreateDirectory();
    std::string image_real_path = WWW_ROOT + IMAGE_ROOT;
    //2.搭建http服务器
    //a.设置静态资源根目录
    _srv.set_mount_point("/", WWW_ROOT);
    //b.添加请求与处理函数的映射管理
    _srv.Post("/video", Insert);
    _srv.Delete("/video/(\\d+)", Delete);
    _srv.Put("/video/(\\d+)", Update);
    _srv.Get("/video/(\\d+)", GetOne);
    _srv.Get("/video", GetAll);
    //c.启动
    _srv.listen("0.0.0.0", _port);

    return true;
}

新增数据

  • 从请求中获取文件字段
  • 将文件内容写到指定的路径
  • 创建一个Json对象存储数据到数据库中
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");                    
    }
    auto name = req.get_file_value("name");
    auto info = req.get_file_value("info");
    auto video = req.get_file_value("video");
    auto image = req.get_file_value("image");

    std::string video_name = name.content;
    std::string video_info = info.content;
    std::string video_path = WWW_ROOT + VIDEO_ROOT + name.filename + video.filename;
    std::string image_path = WWW_ROOT + IMAGE_ROOT + name.filename + image.filename;

    if((FileUtil(video_path).WriteContent(video.content)) == NULL)
    {
        rsp.status = 500;
        rsp.body = R"({"result":false, "reason":"视频文件存储失败"})";
        rsp.set_header("Content-type", "application/json");
    }

    if((FileUtil(image_path).WriteContent(image.content)) == NULL)
    {
        rsp.status = 500;
        rsp.body = R"({"result":false, "reason":"图片文件存储失败"})";
        rsp.set_header("Content-type", "application/json");                
    }

    Json::Value video_json;
    video_json["name"] = video_name;
    video_json["info"] = video_info;
    video_json["video"] = VIDEO_ROOT + name.filename + video.filename;
    video_json["image"] = IMAGE_ROOT + name.filename + image.filename;
    if((tb_video->Insert(video_json)) == NULL)
    {
        rsp.status = 500;
        rsp.body = R"({"result":false, "reason":"新增数据失败"})";
        rsp.set_header("Content-type", "application/json");                
    }

    return ;
}

修改操作

功能测试
void ServerTest()
{
    aod::Server server(8888);
    server.RunModule();
}

新增

在这里插入图片描述

在这里插入图片描述

查询

在这里插入图片描述

模糊匹配

在这里插入图片描述

修改

在这里插入图片描述

在这里插入图片描述

删除视频信息

在这里插入图片描述

在这里插入图片描述

前端界面实现

index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="description" content="">
	<meta name="author" content="OrcasThemes">
	<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
	<title>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="gwj_logo" href="#"><img src="img/gwj_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>02:02</span></div>
							   <img class="img-responsive" v-bind:src="video.image" alt="#" v-cloak>
							</a>
						 </div>
						 <div class="infor">
							<h4>
							   <a class="title" href="#" v-cloak>{{video.name}}</a>
							</h4>
							<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>
<!-- 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/gwj_logo.png" class="img-responsive text-center" alt="Video Magazine Bootstrap HTML5 template">
			 </a>	
			 <p v-cloak>HAUE &copy; Author by Gaowenjun</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="/add" 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: "Gaowenjun",
			videos:[]
        },
		methods : {
			get_allvideos : function() {
				$.ajax({
					url: "/video",
					type: "get",
					context: this,
					success : function(result, status, xhr){
						this.videos = result;
					}
				})
			}
		}
    });
	app.get_allvideos();
</script>
</html>

video.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="description" content="">
	<meta name="author" content="OrcasThemes">
	<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
	<title>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="gwj_logo" href="#"><img src="img/gwj_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>02:02</span></div>
							   <img class="img-responsive" v-bind:src="video.image" alt="#" v-cloak>
							</a>
						 </div>
						 <div class="infor">
							<h4>
							   <a class="title" href="#" v-cloak>{{video.name}}</a>
							</h4>
							<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>
<!-- 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/gwj_logo.png" class="img-responsive text-center" alt="Video Magazine Bootstrap HTML5 template">
			 </a>	
			 <p v-cloak>HAUE &copy; Author by Gaowenjun</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="/add" 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: "Gaowenjun",
			videos:[]
        },
		methods : {
			get_allvideos : function() {
				$.ajax({
					url: "/video",
					type: "get",
					context: this,
					success : function(result, status, xhr){
						this.videos = result;
					}
				})
			}
		}
    });
	app.get_allvideos();
</script>
</html>


在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【leetcode100】从前序与中序遍历序列构造二叉树

1、题目描述 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,nul…

免费GPU算力,不花钱部署DeepSeek-R1

在人工智能和大模型技术飞速发展的今天&#xff0c;越来越多的开发者和研究者希望能够亲自体验和微调大模型&#xff0c;以便更好地理解和应用这些先进的技术。然而&#xff0c;高昂的GPU算力成本往往成为了阻碍大家探索的瓶颈。幸运的是&#xff0c;腾讯云Cloud Studio提供了免…

window保存好看的桌面壁纸

1、按下【WINR】快捷键调出“运行”窗口&#xff0c;输入以下命令后回车。 %localappdata%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets 2、依次点击【查看】【显示】&#xff0c;勾选【隐藏的项目】&#xff0c;然后按【CtrlA】全部…

android 的aab包

什么是 AAB (Android App Bundle)&#xff1f; AAB (Android App Bundle) 是 Google 推出的新一代 Android 应用发布格式&#xff0c;用于取代传统的 APK 格式。AAB 的全称是 Android App Bundle&#xff0c;扩展名为 .aab&#xff0c;它并不是直接可以安装的文件&#xff0c;…

【25考研】中科院软件考研复试难度分析!

中科院软件复试不需要上机&#xff01;且对专业综合能力要求较高&#xff01;提醒同学一定要认真复习&#xff01; 一、复试内容 二、参考书目 官方并未明确给出&#xff0c;建议同学参考初试书目&#xff1a; 1&#xff09;《数据结构&#xff08;C语言版&#xff09;》严蔚…

2014年蓝桥杯第五届CC++大学B组真题及代码

目录 1A&#xff1a;啤酒和饮料&#xff08;填空&#xff09;&#xff08;枚举&#xff09; 2B&#xff1a;切面条&#xff08;填空&#xff09; 3C&#xff1a;李白打酒&#xff08;填空&#xff09;&#xff08;dfs&#xff09; 4D&#xff1a;史丰收速算&#xff08;代码…

目标跟踪之sort算法(3)

这里写目录标题 1 流程1 预处理2 跟踪 2 代码 参考&#xff1a;sort代码 https://github.com/abewley/sort 1 流程 1 预处理 1.1 获取离线检测数据。1.2 实例化跟踪器。2 跟踪 2.1 轨迹处理。根据上一帧的轨迹预测当前帧的轨迹&#xff0c;剔除到当前轨迹中为空的轨迹得到当前…

单片机基础模块学习——DS18B20温度传感器芯片

不知道该往哪走的时候&#xff0c;就往前走。 一、DS18B20芯片原理图 该芯片共有三个引脚&#xff0c;分别为 GND——接地引脚DQ——数据通信引脚VDD——正电源 数据通信用到的是1-Wier协议 优点&#xff1a;占用端口少&#xff0c;电路设计方便 同时该协议要求通过上拉电阻…

【精选】基于数据挖掘的招聘信息分析与市场需求预测系统 职位分析、求职者趋势分析 职位匹配、人才趋势、市场需求分析数据挖掘技术 职位需求分析、人才市场趋势预测

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

文献阅读 250125-Accurate predictions on small data with a tabular foundation model

Accurate predictions on small data with a tabular foundation model Accurate predictions on small data with a tabular foundation model | Nature 使用一种基于表格的模型来对小型数据实现准确预测 ## Abstract: 基于其他列来填充标签列中缺失值的基本预测任务对于各种应…

shiro学习五:使用springboot整合shiro。在前面学习四的基础上,增加shiro的缓存机制,源码讲解:认证缓存、授权缓存。

文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication…

【QT】 控件 -- 显示类

&#x1f525; 目录 [TOC]( &#x1f525; 目录) 1. 前言 2. 显示类控件2.1 Label 1、显示不同文本2、显示图片3、文本对齐、自动换行、缩进、边距4、设置伙伴 3.2 LCD Number 3.3 ProgressBar 3.4 Calendar Widget 3. 共勉 &#x1f525; 1. 前言 之前我在上一篇文章【QT】…

location的使用规则

1、基于URL的location 负责均衡配置 后端集群中的web服务器&#xff0c;必须要有对应的目录和文件才能被访问到 http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;upstream default_pool {server 10.0.0.7:…

如何制作浪漫风格的壁纸

制作浪漫风格的壁纸需要营造出温馨、柔和、梦幻的氛围&#xff0c;通过色彩、元素和构图来传达浪漫的情感。以下是一个详细的步骤指南&#xff0c;帮助你制作浪漫风格的壁纸&#xff1a; 一、明确设计目标 确定用途&#xff1a; 个人使用&#xff1a;如果是为了个人设备&#…

SpringBoot支持动态更新配置文件参数

前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&#xff1a;Java后端、大数据、算法、分布式微服务、中间件、前端、运维等。 博主所有博客文件…

题海拾贝:P2085 最小函数值

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

企业微信SCRM开创客户管理新纪元推动私域流量高效转化

内容概要 在当今瞬息万变的数字化时代&#xff0c;企业面临着前所未有的客户管理挑战。消费者的需求日益多样化&#xff0c;他们希望能够随时随地与品牌沟通。因此&#xff0c;越来越多的企业意识到&#xff0c;传统的客户管理方式已无法满足市场的需求。在这样的背景下&#…

电子应用设计方案104:智能家庭AI弹簧床系统设计

智能家庭 AI 弹簧床系统设计 一、引言 智能家庭 AI 弹簧床系统旨在为用户提供更加舒适、个性化的睡眠体验&#xff0c;通过结合人工智能技术和先进的床垫设计&#xff0c;实时监测和调整睡眠环境&#xff0c;以满足不同用户的需求。 二、系统概述 1. 系统目标 - 自动适应用户…

【25考研】人大计算机考研复试该怎么准备?有哪些注意事项?

人大毕竟是老牌985&#xff0c;复试难度不会太低&#xff01;建议同学认真复习&#xff01;没有机试还是轻松一些的&#xff01; 一、复试内容 由公告可见&#xff0c;复试包含笔试及面试&#xff0c;没有机试&#xff01; 二、参考书目 官方无给出参考书目&#xff0c;可参照…

随着监测技术的不断升级,将为智能决策提供强大的数据支持和智能帮助的智慧能源开源了

简介 AI视频监控平台, 是一款功能强大且简单易用的实时算法视频监控系统。愿景在最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;减少企业级应用约 95%的开发成本&#xff0c;用户仅需在界面上…