视频点播系统

服务端功能模块划分

数据管理模块:负责针对客户端上传的视频信息进行管理。

网络通信模块:搭建网络通信服务器,实现与客户端通信。

业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。

前端界面模块:完成前端浏览器上视频共享点播的各个 html 页面,在页面中支持增删改查以及观看功能。

image-20230509201246604

环境搭建

Gcc 升级7.3版本

sudo yum install centos-release-scl-rh centos-release-scl

sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c++

source /opt/rh/devtoolset-7/enable

echo “source /opt/rh/devtoolset-7/enable” >> ~/.bashrc

环境搭建安装 Jsoncpp

sudo yum install epel-release

sudo yum install jsoncpp-devel

理解jsoincpp

json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。

jsoncpp 库用于实现 json 格式的序列化反序列化,完成将多个数据对象组织成为 json 格式字符串,以及将 json格式字符串解析得到多个数据对象的功能。

下载httplib

理解httplib

httplib – 帮我们完成网络通信模块的功能

Mysql 数据库及开发包安装

sudo yum install -y mariadb

sudo yum install -y mariadb-server

sudo yum install -y mariadb-devel

sudo vim /etc/my.cnf.d/client.cnf

sudo vim /etc/my.cnf.d/mysql-clients.cnf

sudo vim /etc/my.cnf.d/server.cnf

安装配置博客:https://zhuanlan.zhihu.com/p/49046496

设置开机自启动

sudo systemctl enable mysqld

查看状态

systemctl status mysqld

#[mysqld]

# 新增以下配置

collation-server = utf8_general_ci

init-connect = ‘SET NAMES utf8’

character-set-server = utf8

sql-mode = TRADITIONAL

认识jsoncpp库

jsoncpp 库用于实现 json 格式的序列化和反序列化,完成将多个数据对象组织成为 json 格式字符串,以及将 json

格式字符串解析得到多个数据对象的功能。

这其中主要借助三个类以及其对应的少量成员函数完成:

//Json数据对象类
class Json::Value{ 
 Value &operator=(const Value &other); //Value重载了[]和=,因此所有的赋值和获取数据都可以通过
 Value& operator[](const std::string& key);//简单的方式完成 val["姓名"] = "小明"; 
 Value& operator[](const char* key); 
 Value removeMember(const char* key);//移除元素
 const Value& operator[](ArrayIndex index) const; //val["成绩"][0] 
 Value& append(const Value& value);//添加数组元素val["成绩"].append(88); 
 ArrayIndex size() const;//获取数组元素个数 val["成绩"].size(); 
 std::string asString() const;//转string string name = val["name"].asString(); 
 const char* asCString() const;//转char* char *name = val["name"].asCString(); 
 Int asInt() const;//转int int age = val["age"].asInt(); 
 float asFloat() const;//转float 
 bool asBool() const;//转 bool 
}; 
//json序列化类,低版本用这个更简单
class JSON_API Writer {
 virtual std::string write(const Value& root) = 0; 
} 
class JSON_API FastWriter : public Writer { 
 virtual std::string write(const Value& root); 
} 
class JSON_API StyledWriter : public Writer { 
 virtual std::string write(const Value& root); 
} 
//json序列化类,高版本推荐,如果用低版本的接口可能会有警告 !!!
class JSON_API StreamWriter { 
 virtual int write(Value const& root, std::ostream* sout) = 0; 
} 
class JSON_API StreamWriterBuilder : public StreamWriter::Factory { 
 virtual StreamWriter* newStreamWriter() const; 
} 
//json反序列化类,低版本用起来更简单
class JSON_API Reader { 
 bool parse(const std::string& document, Value& root, bool collectComments = true); 
} 
//json反序列化类,高版本更推荐 !!!
class JSON_API CharReader { 
 virtual bool parse(char const* beginDoc, char const* endDoc, 
 Value* root, std::string* errs) = 0; 
} 
class JSON_API CharReaderBuilder : public CharReader::Factory { 
 virtual CharReader* newCharReader() const; 
}

序列化样例

#include <iostream> 
#include <sstream> 
#include <string> 
#include <memory> 
#include <jsoncpp/json/json.h> 
int main() 
{ 
 const char *name = "西小明"; 
 int age = 19; 
 float score[] = {77.5, 88, 99.5}; 
 Json::Value val;   //vaule!!!
 val["姓名"] = name; 
 val["年龄"] = 19; 
 val["成绩"].append(score[0]); 
 val["成绩"].append(score[1]); 
 val["成绩"].append(score[2]); 
 Json::StreamWriterBuilder swb;   //build
 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; 
}

image-20230524140552109

反序列化样例

int main() 
{ 
 	//R("")是cpp11原生字符串的用法,里面的所有字符没有特殊含义。
	 std::string str = R"({"姓名":"小明", "年龄":18, "成绩":[76.5, 55, 88]})"; 
	 Json::Value root;   //value!!!
	 Json::CharReaderBuilder crb;   //build
 	std::unique_ptr<Json::CharReader> cr(crb.newCharReader()); 
 
	 std::string err; 
	 cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err); 
 
	 std::cout << root["姓名"].asString() << std::endl; 
	 std::cout << root["年龄"].asInt() << std::endl; 
	 int sz = root["成绩"].size(); 
 	for (int i = 0; i < sz; i++) { 
		 std::cout << root["成绩"][i].asFloat() << std::endl; 
	 } 
	 for (auto it = root["成绩"].begin(); it != root["成绩"].end(); it++){ 
 		std::cout << it->asFloat() << std::endl; 
	 } 
	 return 0; 
}

认识mysql库

这里主要介绍 Mysql 的 C 语言 API 接口。

Mysql 是 C/S 模式,其实咱们编写代码访问数据库就是实现了一个 Mysql 客户端,实现咱们的专有功能。

//Mysql操作句柄初始化
//这个句柄就是通过它去链接我们的mysql服务器
MYSQL *mysql_init(MYSQL *mysql);
//参数为空则动态申请句柄空间进行初始化
//失败返回NULL 
//连接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句柄里面就相当于有了一个网络套接字,它可以去完成我们mysql服务器的网络通信
//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) 
//mysql--初始化完成的句柄
//db-----要切换选择的数据库名称
//返回值:成功返回0, 失败返回非0;
 
//执行sql语句
int mysql_query(MYSQL *mysql, const char *stmt_str) 
//mysql--初始化完成的句柄
//stmt_str--要执行的sql语句
//返回值:成功返回0, 失败返回非0;
 
//保存查询结果到本地
MYSQL_RES *mysql_store_result(MYSQL *mysql) 
//mysql--初始化完成的句柄
//返回值:成功返回结果集的指针, 失败返回NULL;
 
//获取结果集中的行数与列数
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列
//并且这个接口会保存当前读取结果位置,每次获取的都是下一条数据
//释放结果集
void mysql_free_result(MYSQL_RES *result) 
//result--保存到本地的结果集地址
//返回值:void 
 
//关闭数据库客户端连接,销毁句柄:
void mysql_close(MYSQL *mysql) 
 
//获取mysql接口执行错误原因
const char *mysql_error(MYSQL *mysql)

流程

image-20230524203036333

样例

create database if not exists test_db; 
use test_db; 
create table if not exists test_tb( 
 id int primary key auto_increment, 
 age int, 
 name varchar(32), 
 score decimal(4, 2) 
);
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <mysql/mysql.h> 
#define HOST "127.0.0.1" 
#define USER "root" 
#define PASSWD "" 
#define DBNAME "test_db" 
void add(MYSQL *mysql) { 
 char *sql = "insert into test_tb values(null, 18, '张三', 88.88), (null, 17, '李四', 
77);"; 
 int ret = mysql_query(mysql, sql); 
 if (ret != 0) { 
 printf("mysql query error:%s\n", mysql_error(mysql)); 
 return ; 
 } 
 return ; 
} 
void mod(MYSQL *mysql) { 
 char *sql = "update test_tb set age=34 where name='张三';"; 
 int ret = mysql_query(mysql, sql); 
 if (ret != 0) { 
 printf("mysql query error:%s\n", mysql_error(mysql)); 
 return ; 
 }
  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 error:%s\n", mysql_error(mysql)); 
 return ; 
 } 
 return ; 
} 
void get(MYSQL *mysql) { 
 char *sql = "select * from test_tb;"; 
 int ret = mysql_query(mysql, sql); 
 if (ret != 0) { 
 printf("mysql query error:%s\n", 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); //查找结果所放数据是new出来的,所以要去释放
 return ; 
} 
int main() 
{ 
 MYSQL *mysql = mysql_init(NULL); 
 if (mysql == NULL) { 
 printf("init mysql handle failed!\n"); 
 return -1; 
 } 
 if (mysql_real_connect(mysql, HOST, USER, PASSWD, DBNAME, 0, NULL, 0) == NULL) { 
 printf("mysql connect error:%s\n", mysql_error(mysql)); 
 return -1; 
 } 
 mysql_set_character_set(mysql, "utf8"); 
 //mysql_select_db(mysql, DBNAME); 
 add(mysql); 
 get(mysql); 
 mod(mysql);
 get(mysql); 
 del(mysql); 
 get(mysql); 
 mysql_close(mysql); 
 return 0; 
}
[lml@localhost example]$ make 
gcc mysql.c -o mysql_test -L/usr/lib64/mysql -lmysqlclient 
[lml@localhost example]$ ./mysql_test 
 ID 年龄 姓名 成绩
 5 18 张三 88.88 
 6 17 李四 77.00 
 ID 年龄 姓名 成绩
 5 34 张三 88.88 
 6 17 李四 77.00 
 ID 年龄 姓名 成绩
 6 17 李四 77.00

认识httplib库

httplib 库,一个 C++11 单文件头的跨平台 HTTP/HTTPS 库。安装起来非常容易。只需包含 httplib.h 在你的代码中即可。

httplib 库实际上是用于搭建一个简单的 http 服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。

image-20230524205939916

里面的两个重要的结构体Request和Response就是用来存储请求和响应信息的,以便于组织请求和响应数据。
image-20230614145252430

PS:上面7点没有,才会去再找静态资源

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;//获取指定的文件信息
 };
  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);//设置全套的重定向信息
 }; 
 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服务器静态资源根目录
 }; 
}

样例

image-20230525150436923image-20230525150404403

服务端功能实现

文件工具类

在视频点播系统中因为涉及到文件上传,需要对上传的文件进行备份存储,因此首先设计封装文件操作类,这个类封

装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。

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

也可以用cpp17的filesystem(https://en.cppreference.com/w/cpp/experimental/fs)

util.hpp(代码)

#ifndef __MY_UTIL__
#define __MY_UTIL__
#include <iostream>
#include <fstream>
#include <sstream>
#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()//判断文件是否存在 access
            {
                //access的F_OK专门用于检测文件是否存在 -- 存在返回0
                int ret = access(_name.c_str(),F_OK);
                if(ret!=0){
                    std::cerr<<"file is not exits"<<std::endl;
                    return false;
                }
                return true;
            }
            size_t FileSize()//获取文件大小 stat
            {
                if(this->Exists()==false){
                    std::cout<<"file is not exits"<<std::endl;
                    return 0;
                }
                struct stat st;
                //stat接口用于获取文件属性,其中 st_size 就是文件大小成员
                int ret = stat(_name.c_str(),&st);
                if(ret!=0){
                    std::cerr<<"get file stat failed!"<<std::endl;
                }
                return st.st_size;
            }
            bool GetContent(std::string *content)//把_name文件的数据读取到content ifstream
            {
                std::ifstream ifs;
                ifs.open(_name,std::ios::binary);
                if(ifs.is_open() == false){
                    std::cerr<<"open file failed!"<<std::endl;
                    return false;
                }
                size_t flen = this->FileSize();
                content->resize(flen);
                //这里不能content->c_str()因为返回是const的
                //我们这里利用对象然后取地址返回的就是非const的
                ifs.read(&(*content)[0],flen);    
                if(ifs.good()==false){
                    std::cerr<<"read file content failed!"<<std::endl;
                    ifs.close();
                    return false;
                }
                ifs.close();
                return true;
            }
            bool SetContent(const std::string content)//把contrnt数据写入到_name文件 ofstream
            {
                std::ofstream ofs;
                ofs.open(_name,std::ios::binary);
                if(ofs.is_open() == false){
                    std::cerr<<"open file failed!"<<std::endl;
                    return false;
                }
                ofs.write(content.c_str(),content.size());    
                if(ofs.good()==false){
                    std::cerr<<"write file content failed!"<<std::endl;
                    ofs.close();
                    return false;
                }
                ofs.close();
                return true;
            }
            bool CreateDirectory()//针对目录时创建目录 mkdir
            {
                if(this->Exists()){
                    return true;
                }
                mkdir(_name.c_str(),0777);
                return true;
            }
    };
}

#endif

json工具类

  • 实现序列化 – 将一个json Value对象序列成一个字符串返回
  • 实现反序列化 – 将一个json格式的字符串,反序列化得到一个json Value对

json.h 位置

ls /usr/include/jsoncpp/json/

assertions.h config.h forwards.h reader.h version.h

autolink.h features.h json.h value.h writer.h

#注意,centos版本不同有可能安装的jsoncpp版本不同,安装的头文件位置也就可能不同了。

关于数据存储模块

视频数据表的设计(数据库)

在视频共享点播系统中,视频数据和图片数据都存储在文件中,而我们需要在数据库管理用户上传的每个视频信息

只是完成一个简单的视频信息表。

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

数据管理类设计(类)

数据管理模块负责统一对于数据库中数据的增删改查管理,其他所有模块要进行数据的操作都通过数据管理模块完成。

然而,数据库中有可能存在很多张表,每张表中数据又有不同,要进行的数据操也各不相同,因此咱们将数据的操作

分摊到每一张表上,为每一张表中的数据操作都设计一个类,通过类实例化的对象来访问这张数据库表中的数据,这

样的话当我们要访问哪张表的时候,使用哪个类实例化的对象即可。

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

视频信息在接口之间的 传递因为字段数量可能很多,因此使用 Json::Value 对象进行传递

data.hpp(代码)

#ifndef __MY_DATA__
#define __MY_DATA__
#include "util.hpp"
#include <mutex>
#include <cstdlib>
#include <mysql/mysql.h>

namespace aod{
#define HOST "127.0.0.1"
#define USER "root"
#define PASS "15115655971Lml"
#define NAME "aod_system"
    static MYSQL* MysqlInit()
    {
        MYSQL* mysql = mysql_init(NULL);//句柄初始化
        mysql_set_character_set(mysql,"utf8");//设置字符编码
        if(mysql==NULL){
            std::cerr<<"init mysql instance failed!"<<std::endl;
            return NULL;
        }
        if(mysql_real_connect(mysql,HOST,USER,PASS,NAME,0/*0就是默认3306*/,NULL,0)==nullptr){//连接mysql
            std::cerr<<"connect mysql server failed!"<<std::endl;
            mysql_close(mysql);
            return NULL;
        }
        return mysql;
    } 
    static void MysqlDestroy(MYSQL* mysql)
    {
        if(mysql!=NULL)
        {
            mysql_close(mysql);
        }
    }
    static bool MysqlQuery(MYSQL* mysql,const std::string &sql)
    {
        int ret = mysql_query(mysql,sql.c_str());//查询即执行sql
        if(ret!=0){
            std::cerr<< sql <<std::endl;
            std::cerr<< mysql_error(mysql) <<std::endl;
            return false;
        }
        return true;
    }

    class TableVideo{
        private:
            MYSQL* _mysql;//一个对象就是一个客户端,管理一张表
            std::mutex _mutex;//防备操作对象在多线程中使用存在的线程安全 问题
        public:
            TableVideo()//完成mysql句柄初始化
            {
                _mysql = MysqlInit();
                if(_mysql == NULL){
                    exit(-1);
                }
            }
            ~TableVideo()//释放msyql操作句柄
            {
                MysqlDestroy(_mysql);
            }
            bool Insert(const Json::Value &video)//新增-传入视频信息
            {
                //id name info video image
                std::string sql;
                sql.resize(4094 + video["info"].asString().size());//防止简介过长
                #define INSERT_VIDEO "insert tb_video values(null,'%s','%s','%s','%s');"
                //要完成还需要鸽子1校验,不然直接使用可能会出问题
                sprintf(&sql[0],INSERT_VIDEO,video["name"].asCString(),
                                             video["info"].asCString(),
                                             video["video"].asCString(),
                                             video["image"].asCString());
                return MysqlQuery(_mysql,sql);
            }
            bool Update(int video_id,const Json::Value &video) //修改-传入视频id,和信息
            {
                //id name info video image
                std::string sql;
                sql.resize(4094 + 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);
            }
            bool Delete(const int video_id)//删除-传入视频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);
            }
            bool SelectAll(Json::Value *videos)//查询所有--输出所有视频信息
            {
                #define SELECTEALL_VIDEO "select * from tb_video;"
                _mutex.lock();//-----lock  start 查询到的结果与保存数据到本地应该要是原子的
                bool ret = MysqlQuery(_mysql,SELECTEALL_VIDEO);
                if(ret == false){
                    _mutex.unlock();
                    return false;
                }
                MYSQL_RES *res = mysql_store_result(_mysql);//保存结果集
                if(res == NULL){
                    std::cerr<<"mysql store result failed!"<<std::endl;
                    _mutex.unlock();
                    return false;
                }
                _mutex.unlock();//-----lock  end
                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["vedio"] = row[3];
                    video["image"] = row[4];

                    videos->append(video);
                }
                mysql_free_result(res);//释放结果集
                return true;
            }
            bool SelectOne(int video_id,Json::Value *video)//查询单个-输入视频id,输出信息
            {
                #define SELECTEONE_VIDEO "select * from tb_video where id='%d'"
                char sql[1024]={0};
                sprintf(sql,SELECTEONE_VIDEO,video_id);
                _mutex.lock();//-----lock  start 查询到的结果与保存数据到本地应该要是原子的
                bool ret = MysqlQuery(_mysql,sql);
                if(ret == false){
                    _mutex.unlock();
                    return false;
                }
                MYSQL_RES *res = mysql_store_result(_mysql);//保存结果集 
                if(res == NULL){
                    std::cerr<<"mysql store result failed!"<<std::endl;
                    mysql_free_result(res);//注意退出前要释放结果集
                    _mutex.unlock();
                    return false;
                }
                _mutex.unlock();//-----lock  end
                int num_rows = mysql_num_rows(res);//获取结果集的行数
                if(num_rows!=1){
                    std::cerr<<"have no data!"<<std::endl;
                    mysql_free_result(res);
                    return false;
                }
                MYSQL_ROW row = mysql_fetch_row(res);//获取每一行的结果,内部会自动++
                (*video)["id"] = atoi(row[0]);
                (*video)["name"] = row[1];
                (*video)["info"] = row[2];
                (*video)["vedio"] = row[3];
                (*video)["image"] = row[4];

                mysql_free_result(res);//释放结果集
                return true;
            }
            bool SelectLike(const std::string &key,Json::Value *videos)//模糊匹配-输入名称关键字,输出视频信息
            {
                #define SELECTELIKE_VIDEO "select * from tb_video where name like '%%%s%%';"
                char sql[1024]={0};
                sprintf(sql,SELECTELIKE_VIDEO,key.c_str());
                _mutex.lock();//-----lock  start 查询到的结果与保存数据到本地应该要是原子的
                bool ret = MysqlQuery(_mysql,sql);
                if(ret == false){
                    _mutex.unlock();
                    return false;
                }
                MYSQL_RES *res = mysql_store_result(_mysql);//保存结果集
                if(res == NULL){
                    std::cerr<<"mysql store result failed!"<<std::endl;
                    _mutex.unlock();
                    return false;
                }
                _mutex.unlock();//-----lock  end
                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["vedio"] = row[3];
                    video["image"] = row[4];

                    videos->append(video);
                }
                mysql_free_result(res);//释放结果集
                return true;
            }
    };
};

#endif

关于网络通信模块(httplib)

网络通信框架设计

restful 认识

  • REST 是 Representational State Transfer 的缩写,一个架构符合 REST 原则,就称它为 RESTful 架构
  • RESTful 架构可以充分的利用 HTTP 协议的各种功能,是 HTTP 协议的最佳实践,正文通常采用 JSON 格式
  • RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好

restful 使用五种 HTTP 方法,对应 CRUD(增删改查) 操作

  • GET 表示查询获取
  • POST 对应新增
  • PUT 对应修改
  • DELETE 对应删除

整体设计

image-20230527133012238

关于上传视频信息以及文件

因为上传视频信息的时候,会携带有视频文件和封面图片的文件上传,而这些文件数据都是二进制的,用 json 不好

传输,因此在这里使用传统的 http 上传文件请求格式,而并没有使用 restful 风格

请求:///
POST /video HTTP/1.1 
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarydsrFiETIzKETHWkn 
------WebKitFormBoundarydsrFiETIzKETHWkn 
Content-Disposition: form-data; name="name" 
Xhsell连接事项,也就是视频名称
------WebKitFormBoundarydsrFiETIzKETHWkn 
Content-Disposition: form-data; name="info" 
一部非常好看的视频的描述信息
------WebKitFormBoundarydsrFiETIzKETHWkn 
Content-Disposition: form-data; name="image"; filename="image.jpg" 
Content-Type: text/plain 
image封面图片数据
------WebKitFormBoundarydsrFiETIzKETHWkn 
Content-Disposition: form-data; name="video"; filename="video.mp4" 
Content-Type: text/plain 
video视频数据
------WebKitFormBoundarydsrFiETIzKETHWkn 
Content-Disposition: form-data; name="submit" 
------WebKitFormBoundarydsrFiETIzKETHWkn-- 
响应:///
HTTP/1.1 303 See Other 
Location: "/"

网络通信类的设计

业务处理模块负责与客户端进行网络通信,接收客户端的请求,然后根据请求信息,明确客户端端用户的意图,进行

业务处理,并进行对应的结果响应。

在视频共享点播系统中,业务处理主要包含两大功能:1、网络通信功能的实现;2、业务功能处理的实现

其中网络通信功能的实现咱们借助 httplib 库即可方便的搭建 http 服务器完成。这也是咱们将网络通信模块与业务

处理模块合并在一起完成的原因。

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

  • 客户端的视频数据和信息上传
  • 客户端的视频列表展示(视频信息查询)
  • 客户端的视频观看请求(视频数据的获取)
  • 客户端的视频其他管理(修改,删除)功能

类框架

#ifndef __MY_SERVERE__ 
#define __MY_SERVERE__ 
#include "httplib.h" 
#include "data.hpp" 
namespace aod { 
 #define WWW_ROOT "./www" 
 #define VIDEO_ROOT "/video/" 
 #define IMAGE_ROOT "/image/" 
 //因为httplib基于多线程,因此数据管理对象需要在多线程中访问,为了便于访问定义全局变量
 TableVideo *table_video = NULL; 
 //这里为了更加功能模块划分清晰一些,不使用lamda表达式完成,否则所有的功能实现集中到一个函数中太过庞大
 class Server { 
 private: 
 int _port;//服务器的 监听端口
 httplib::Server _srv;//用于搭建http服务器
 private: 
 //对应的业务处理接口
 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 GetOne(const httplib::Request &req, httplib::Response &rsp); 
 static void GetAll(const httplib::Request &req, httplib::Response &rsp); 
 public: 
 Server(int port):_port(port); 
 bool RunModule();//建立请求与处理函数的映射关系,设置静态资源根目录,启动服务器,
 }; 
}

server.hpp(代码)

#include "data.hpp"
#include </home/lml/cpp-httplib/httplib.h>

namespace aod{
#define WWWROOT "./www" //静态资源根目录
#define VIDEO_ROOT "/video/"
#define IMAGE_ROOT "/image/"
    //因为httplib基于多线程,因此数据管理对象需要在多线程中访问,为了便于访问定义全局变量
    TableVideo* tb_video = NULL;
     //这里为了更加功能模块划分清晰一些,不使用lamda表达式完成,否则所有的功能实现集中到一个函数中太过庞大
    class Server{
        private:
            int _port;//服务器的 监听端口
            httplib::Server _srv;//用于搭建http服务器
        private:
            //对应的业务处理接口
            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 ; 
                }
                //MultipartFormData 4个string成员{name 字段名,content 文件的数据,filename 文件名称,content_type 正文类型}
                httplib::MultipartFormData name = req.get_file_value("name");//视频名称
                httplib::MultipartFormData info = req.get_file_value("info");//视频简介
                httplib::MultipartFormData video = req.get_file_value("video");//视频文件
                httplib::MultipartFormData image = req.get_file_value("image");//图片文件

                std::string video_name = name.content;
                std::string video_info = info.content;
                // ./www/image/发发t.jpg
                std::string wroot=WWWROOT;
                std::string video_path = wroot + VIDEO_ROOT + video_name + video.filename;
                std::string image_path = wroot + IMAGE_ROOT + video_info + image.filename;

                //文件的存储
                if(FileUtil(video_path).SetContent(video.content)==false){
                    rsp.status = 501;
                    rsp.body = R"({"result":false,"reason":"视频文件存储失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                if(FileUtil(image_path).SetContent(image.content)==false){
                    rsp.status = 502;
                    rsp.body = R"({"result":false,"reason":"视频图片存储失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                Json::Value video_json;
                video_json["name"] = video_name;
                video_json["info"] = video_info;
                video_json["video"] = VIDEO_ROOT + video_name + video.filename;
                video_json["image"] = IMAGE_ROOT + video_info + image.filename; // /image/发发t.jpg
                if(tb_video->Insert(video_json)==false){
                    rsp.status = 503;
                    rsp.body = R"({"result":false,"reason":"数据库新增数据失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                return ;
            }
            static void Delete(const httplib::Request &req,httplib::Response &rsp)
            {
                //1. 获取要删除的视频id
                //matches:存放正则表达式匹配的规则数据 /numbers/123 matches[0]="/numbers/123,matches[1]="123"
                int video_id = std::stoi(req.matches[1]);
                //2. 删除视频和图片文件
                Json::Value video;
                if(tb_video->SelectOne(video_id,&video)==false){
                    rsp.status = 504;
                    rsp.body = R"({"result":false,"reason":"不存在的视频信息"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                std::string root = WWWROOT;
                std::string video_path = root + video["video"].asString();
                std::string image_path = root + video["image"].asString();
                remove(video_path.c_str());//Bug ? 
                remove(image_path.c_str());
                //3. 删除数据库信息 
                if(tb_video->Delete(video_id)==false){
                    rsp.status = 505;
                    rsp.body = R"({"result":false,"reason":"删除数据库信息失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                return ;
            }
            static void Update(const httplib::Request &req,httplib::Response &rsp)
            {
                //1. 获取要修改的视频信息 1. 视频id 2. 修改后的信息
                int video_id = std::stoi(req.matches[1]);
                Json::Value video;
                if(JsonUtil::UnSerialize(req.body,&video)==false){
                    rsp.status = 400;
                    rsp.body = R"({"result":false,"reason":"新的视频信息格式解析失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                //2. 修改数据库数据
                if(tb_video->Update(video_id,video)==false){
                    rsp.status = 506;
                    rsp.body = R"({"result":false,"reason":"修改数据库信息失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                return ;
            }
            static void SelectOne(const httplib::Request &req,httplib::Response &rsp)
            {
                //1. 获取视频的id
                int video_id = std::stoi(req.matches[1]);
                //2. 在数据库中查询指定视频信息
                Json::Value video;
                if(tb_video->SelectOne(video_id,&video)==false){
                    rsp.status = 507;
                    rsp.body = R"({"result":false,"reason":"查询数据库指定信息失败"})";
                    rsp.set_header("Content-Type","application/json");
                    return ;
                }
                //3. 组织响应正文 -- json格式的字符串
                JsonUtil::Serialize(video,&rsp.body);
                rsp.set_header("Content-Type","application/json");
                return ;
            }
            static void SelectAll(const httplib::Request &req,httplib::Response &rsp)
            {
                // /video &  /video?search="关键字"     看是哪种查询
                bool select_flag = true;//默认所有查询
                std::string search_key;
                if(req.has_param("search")==true){
                    select_flag = false;//模糊匹配
                    search_key = req.get_param_value("search");
                }
                Json::Value videos;
                if(select_flag == true)
                {
                    if(tb_video->SelectAll(&videos)==false){
                        rsp.status = 508;
                        rsp.body = R"({"result":false,"reason":"查询所有数据库信息失败"})";
                        rsp.set_header("Content-Type","application/json");
                        return ;
                    }
                }
                else
                {
                    if(tb_video->SelectLike(search_key,&videos)==false){
                        rsp.status = 509;
                        rsp.body = R"({"result":false,"reason":"查询匹配数据库信息失败"})";
                        rsp.set_header("Content-Type","application/json");
                        return ;
                    }
                }
                //3. 组织响应正文 -- json格式的字符串
                JsonUtil::Serialize(videos,&rsp.body);
                rsp.set_header("Content-Type","application/json");
                return ;     
            }
        public:
            Server(int port):_port(port){}
            bool RunMoudule()//建立请求与处理函数的映射关系,设置静态资源根目录,启动服务器
            {
                //1. 初始化操作--初始化数据管理模块,创建指定的目录
                tb_video = new TableVideo(); //data.hpp里面是没有初始化TableVideo对象的!
                FileUtil(WWWROOT).CreateDirectory();
                std::string wroot = WWWROOT;
                std::string video_real_path = wroot + VIDEO_ROOT;
                FileUtil(video_real_path).CreateDirectory();
                std::string image_real_path = wroot + IMAGE_ROOT;
                FileUtil(image_real_path).CreateDirectory();
                //2. 搭建http服务器,开始运行
                //2.1设置静态资源根目录
                _srv.set_mount_point("/",WWWROOT);//即设置"/"就是WWWWROOT
                //2.2添加请求-处理函数映射关系
                _srv.Post("/video",Insert);
                _srv.Delete("/video/(\\d+)",Delete);
                _srv.Put("/video/(\\d+)",Update);
                _srv.Get("/video/(\\d+)",SelectOne);
                _srv.Get("/video",SelectAll);
                //正则表达式中 ,\d表示数字,+表示匹配前面的字符一次或者多次,()表示单独捕捉数据
                //3. 启动服务器
                _srv.listen("0.0.0.0",_port);
                return true;
                /*程序进来之后,先初始化数据管理模块,然后创建该创建的目录,然后设置静态资源根目录,
                然后添加请求与处理函数的对应关系,最后启动服务器。
                httplib在收到请求之后首先查看请求的方法以及资源路径,看映射关系里面有没有对应的函数去
                处理,如果有就直接调用,如果没有再查看请求的是否是一个静态资源(查看WWWROOT里面有没有
                对应的文件,如果有就进行响应,如果没有就返回404)
                而我们需要做的就是对应的业务处理过程(就是这些对应的函数)*/
            }
    };
}

前端页面实现

HTML(标签语言–但是丑)+ CSS(样式语言–可以让HTML的标签更好看)+ JS(独立的脚本语言,可以完成一些动态的页面(随着数据页面变化))

HTML 认识

基础认识

HTML 代码是由标签构成的。我们可以理解不同的标签代表不同的控件元素,前端浏览器拿到 html 代码之后,根据

标签之间的关系进行解析,得到一棵 DOM(Document Object Mode - 文档对象模型的缩写) 树。

然后根据 DOM 树渲染出不同的控件元素,得到我们所看到的页面。

标签之间具有不同的关系:

  • 父子关系
  • 兄弟关系
<html> 
 <head> 
 <title>第一个页面</title> 
 </head> 
 <body id="myId"> 
 hello world 
 </body> 
</html>
  • 标签名 (body) 放到 < > 中
  • 大部分标签成对出现. 为开始标签, 为结束标签
  • 少数标签只有开始标签, 称为 “单标签”
  • 开始标签和结束标签之间, 写的是标签的内容. (hello)
  • 开始标签中可能会带有 “属性”. id 属性相当于给这个标签设置了一个唯一的标识符(身份证号码)(因为可能有相同的标签)

标题标签: h1-h6

<h1>hello</h1> 

<h2>hello</h2> 

<h3>hello</h3> 

<h4>hello</h4> 
<head><!--head标签是头部标签,内部通常编写页面的属性--> 
 <meta charset="UTF-8">                !!!!!!!!
 <title>html扫盲</title> 
</head>

段落标签: p

<p>段落,
在html中一般的回车并不起作用,会被解释成为一个 空格<br/>但是br不一样,br标签的作用就是换行(行内换行)。
</p>
把一段比较长的文本粘贴到 html 中, 会发现并没有分成段落. 在 html 中使用 <p> 标签括起一个段落进行换行。当然

也可以在段落内使用 <br/> 标签进行换行操作。

图片标签: img

<img src="./rose.jpg" alt="显示失败时的信息" title="图片提示信息" width="150px" height="150px" 
border="5px">

超链接标签: a

<a href="http://www.baidu.com" target="_blank(是否打开一个新的页面)">点击这里打开新标签访问百度</a>

表格标签: table

  • table 标签: 表示整个表格
  • tr: 表示表格的一行
  • td: 表示一个单元格
  • th: 表示表头单元格. 会居中加粗
  • thead: 表格的头部区域(注意和 th 区分, 范围是比 th 要大的)
  • tbody: 表格得到主体区域
<table align="center" border="1" cellpadding="1(表格内容与表格线的距离)" cellspacing="0(表格线之间的距离)" width="200" height="20"> 
 	<tr> 
 		<th>菜名</th> 
 		<th>单价</th> 
 		<th>折扣</th> 
 	</tr> 
 	<tr> 
 		<td align="center">红烧茄子</td> 
 		<td align="center">¥18</td> 
 		<td align="center">8.8折</td> 
 	</tr> 
 	<tr> 
 		<!--colspan合并列, rowspan合并行--> 
 		<td colspan="3" align="right">总价:¥18</td> 
 	</tr> 
</table>

列表标签: ol & ul & dl

主要使用来布局的, 整齐好看

  • 无序列表[重要] ul li
  • 有序列表[用的不多] ol li
  • 自定义列表[重要] dl (总标签) dt (小标题) dd (围绕标题来说明) 上面有个小标题, 下面有几个围绕着标题来展开的
<ul> 
 <li>ul/li是无序列表</li> 
 <li>ul/li是无序列表</li> 
</ul> 
<ol> 
 <li>ol/li是有序列表</li> 
 <li>ol/li是有序列表</li> 
</ol> 
<dl> 
 <dt>dl/dt是小标题</dt> 
 <dd>dl/dd是围绕标题的描述</dd> 
 <dd>dl/dd是围绕标题的描述</dd> 
</dl>

表单标签: form

表单是让用户输入信息的重要途径.

分成两个部分:

  • 表单域: 包含表单元素的区域. 重点是 form 标签.
  • 表单控件: 输入框 , 提交按钮等. 重点是 input 标签

form 标签认识(重点)

被 form 标签括起来的部分称之为表单域,当点击表单提交按钮时,将会将表单域中所有表单控件数据提交给指定服务器。

  • action :表单动作,或者说当点击表单提交时的请求链接
  • method :请求方法
  • enctype :编码类型,其中 multipart/form-data 常用于文件上传
<form action="/upload(当我们表单提交给服务器的时候,是提交给服务器的哪个资源路径)" method="post" enctype="multipart/form-data(编码类型)"> 
 <input type="text" placeholder="input标签默认是文本框"> <br/> (input标签就是我们的表单控件)
 <input type="file" value="file是文件选择按钮框"><br/> 
 <input type="submit" value="submit是提交按钮">点击这里就会向服务器提交表单域中的表单数据<br/> 
</form>

input 标签的认识

<input type="text" placeholder="input标签默认是文本框"> <br/> 
<input type="password" placeholder="type属性为password是密码框"> <br/> 
<input type="radio" name="sex">type属性为radio是单选框,name属性相同则默认为同一组-男 <br/> 
<input type="radio" name="sex" checked="checked">type属性为radio是单选框-女<br/> 
<input type="checkbox"> checkbox是复选框-吃饭 <br/> 
<input type="checkbox"> checkbox是复选框-睡觉 <br/> 
<input type="checkbox"> checkbox是复选框-打游戏<br/> 
<input type="checkbox" id="testid"> 
<label for="testid">label标签for属性与对应的输入框id对应起来,这时候点击文字也能选中</label><br/> 
<input type="button" value="button是普通按钮" onclick="alert('alert是提示框调用函数')"><br/> 
<input type="submit" value="submit是提交按钮">点击这里就会向服务器提交表单域中的表单数据<br/> 
<input type="file" value="file是文件选择按钮框"><br/> 
<input type="reset" value="reset是清空按钮,会清空表单域的所有数据"><br>

下拉菜单标签: select

option 中定义 selected=“selected” 表示默认选中

<select> 
 <option selected="selected">--请选择年份--</option> 
 <option>1990</option> 
 <option>1991</option> 
 <option>1992</option> 
</select>

文本域标签: textarea

<textarea name="文本域标签" id="" cols="30" rows="10" placeholder="textarea是文本域标签"> 
</textarea>

无语义标签: div & span

div 标签, division 的缩写, 含义是 分割

span 标签, 含义是跨度

说白了就是两个盒子. 常用于网页布局

  • div 是独占一行的, 是一个大盒子
  • span 不独占一行, 是一个小盒子
<div>div是个大盒子独占一行</div> 
<span>span是个小盒子并不独占一行</span> 
<span>span是个小盒子并不独占一行</span>

html5 语义化标签

div 没有语义. 对于搜索引擎来说没有意义. 为了让搜索引擎能够更好的识别和分析页面(SEO 优化), HTML 引入了更多

的 “语义化” 标签. 但是这些标签其实本质上都和 div 一样(对于前端开发来说). 然而对于搜索引擎来说, 见到 header

和 article 这种标签就会重点进行解析, 而 footer 这种标签就可以适当忽略

  • header: 头部
  • nav: 导航
  • article: 内容
  • section: 某个区域
  • aside: 侧边栏
  • footer: 尾部
<header>头部容器标签</header> 
<nav>导航容器标签</nav> 
<article>内容容器标签</article> 
<section>某个区域的容器标签</section> 
<aside>侧边栏容器标签</aside> 
<footer>尾部容器标签</footer>

多媒体标签: video & audio

video标签:视频 audio标签:音频

<video src="https://www.runoob.com/try/demo_source/movie.mp4" controls="controls" 
type="video/mp4">video是视频控件标签</video> 
 <audio src="https://www.runoob.com/try/demo_source/horse.mp3" controls="controls" 
type="audio/mp3">audio是音频控件标签</audio>

CSS 认识

层叠样式表 ( Cascading Style Sheets )。

CSS 能够对网页中元素位置的排版进行像素级精确控制, 实现美化页面的效果. 能够做到页面的样式和结构分离。

基本语法规范

选择器 + {一条/N条声明}

  • 选择器决定针对谁修改 (找谁)
  • 声明决定修改啥. (干啥)
  • 声明的属性是键值对. 使用 ; 区分键值对, 使用 : 区分键和值
<style> 
 	p { 
 		/* 设置字体颜色 */ 
 		color: red; 
 		/* 设置字体大小 */ 
 		font-size: 30px; 
 	} 
</style> 
<p>hello</p>
  • CSS 要写到 style 标签中(后面还会介绍其他写法)
  • style 标签可以放到页面任意位置. 一般放到 head 标签内.
  • CSS 使用 /* */ 作为注释.
  • CSS 不区分大小写, 我们开发时统一使用小写字母

选择器的种类

1. 基础选择器: 单个选择器构成的

  • 标签选择器
  • 类选择器
  • id 选择器
  • 通配符选择器

2. 复合选择器: 把多种基础选择器综合运用起来

  • 后代选择器
  • 子选择器
  • 并集选择器
  • 伪类选择器
/*通配选择器-对所有的标签产生效果*/ 
* { 
 	margin: 0px; 
 	padding: 3px; 
}

/*p这种与html标签同名的称之为标签选择器,同类标签都具有这个属性*/ 
p { 
 color: red; 
 font-size: 30px; 
} 
<p>这是一个标题</p>

/*类选择器,类名以.开头, 多个类属性可以在一个标签内使用*/ 
.green { 
 color: green; 
} 
.big { 
 font-size: 40px; 
} 
<p class="green big">这是一个类选择器修饰的属性</p>

/*id选择器,名称以#开头,修饰指定id的标签容器,只能被一个标签使用,因为id唯一*/ 
#font { 
 font-size: 20px; 
 font-family:sans-serif; 
 color: aqua; 
} 
<p id="font">这个是id选择器弄的样式</p>

vue.js 认识

js 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言,是作为Web开发页面的脚本语言。

vue.js 就是一个js库, 类似于一个cpp的第三方库,可以让我们的某些操作更简单。

安装

<!-- 开发环境版本,包含了有帮助的命令行警告 --> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<!-- 生产环境版本,优化了尺寸和速度 --> 
<script src="https://cdn.jsdelivr.net/npm/vue"></script>

入门案例

使用 vue 实现一个简单的 hello world 程序

<div id="app">{{message}}</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	var app = new Vue({ 
 	el: '#app', 
 	data: { 
	 message: 'hello', 
	 }, 
 }); 
</script>

代码解释:

  1. 创建了一个 div, id 为 app

  2. 在 div 中使用 “插值表达式” {{message}} 来指定 div 内部的内容.

  3. js 中创建了一个名为 app 的 Vue 实例. 构造函数中的参数是一个对象.

  4. 参数中的 el 字段是一个选择器, 对应到 html 中的具体元素id.

  5. 参数中的 data 字段是一个特定的对象, 用来放置数据.

  6. data 中的 message 属性值, 就对应到 {{message}} 中的内容. Vue 会自动解析 {{message}} , 并把 data 中对应

的名字为 message 的属性值替换进去.

理解响应式: 修改 message 的值, 界面显示的内容就会发生改变

插值操作: {{}}

<div id="app"> {{str1}} {{str2}} </div> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
		data: { 
 			str1: 'hello', 
 			str2: 'world' 
 	} 
 }); 
</script>

遮罩: v**-**cloak

cloak 意思是 “斗篷”, 用来遮罩被渲染之前的插值表达式.

HTML 解析代码的时候是从上往下解析. 如果加载 Vue 的速度比较慢, 那么就会在界面上看到 {{ }} 这样的内容.

<style> 
 	[v-cloak] { 
 		display: none; 
 	} 
</style> 

<div id="app" v-cloak> {{message}} </div>

注意**😗* v-cloak 相当于 div 标签的一个属性. 这个属性在 Vue 接管 div 之前会存在, 但是 Vue 执行之后这个 v-cloak 就会被 Vue 去掉. 此时 display 样式就不再生效了

[v-cloak] 是属性选择器(是 CSS 的基本选择器之一). 选择了所有包含 v-cloak 属性的元素

绑定属性: v-bind

很多标签的属性都是需要动态进行设置的. 比如 标签的 href , 的 src 等.此时使用插值表达式在属性中是不能使用的.

<img v-bind:src="url" alt=""> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 			url: "一个图片路径链接" 
 	} 
 }); 
</script>

事件监听: v-on

v-on 后面使用 : 连接后续的事件名称

<div id="app"> 
 	<button v-on:click="dialog('点击了按钮')">按钮</button> <!--当按钮被点击就会触发事件函数的调用--> 
</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 		}, 
	 	methods: { 
 			dialog: function (str) { 
 				alert(str); 
 			} 
 		}
  }); 
</script>

methods : vue 对象的函数或方法都放在其中

<div id="app"> 
 	<form action="http://www.sogou.com"> 
 		<input type="submit" value="提交" v-on:click.prevent="click()"> 
 	</form> 
</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 		}, 
 		methods: { 
 			click: function () { 
 				console.log('hello'); 
 			}, 
 		} 
 	}); 
</script>

v-on:click.prevent :阻止元素默认行为. 比如这里则进制了默认的form表单提交操作

条件显示: v-show

v-show , 条件为 true 的时候显示元素, 条件为 false 时不显示

<div id="app"> 
 	<h3 v-show="flag">hello</h3> 
</div> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 			flag: true, 
 	} 
 }); 
</script>

条件指令: v-if

通过一个表达式决定某个元素是否被渲染.

<div id="app"> 
 	<div v-if="score > 90">学神</div> 
 	<div v-else-if="score > 80">学霸</div> 
 	<div v-else-if="score > 60">普通</div> 
 	<div v-else>学渣</div> 
</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 			score: 95, 
 		} 
 	}); 
</script>

v-show 和 v-if 的区别:当把 flag 置为 false 时, v-if 对应的元素不会被构建到 dom 树中, 而 v-show 的元素构建到 dom 树中了, 只是通过 display:none 隐藏了

循环指令: v-for

v-for 可以绑定数据到数组来渲染一个标签

<div id="app"> 
 	<div v-for="hero in heros">{{hero}}</div> 
</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 			heros: ['小乔', '曹操', '李白'], 
 		} 
 	}); 
</script>

双向绑定: v**-**model

表单是实际开发中和用户交互的重要手段. 通过 v-model 可以将一个 vue 数据与标签数据关联起来,实现一荣俱荣一损俱损的效果。

<div id="app"> 
 	<input type="text" v-model="message"> 
 	{{message}} 
</div> 

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({
  	el: '#app', 
 	data: { 
 		message: 'hello', 
 	} 
 }); 
</script>

通过 v-model 命令就把 input 标签的 value 和 message 关联起来了

此时, 通过修改输入框的内容, app.message 就会发生改变.

修改 app.message 的值, 界面也会随之发生改变.

这个操作就称为 双向绑定

jquery.ajax 认识

A JAX 是与服务器交换数据的技术,它在不重载全部页面的情况下,实现了对部分网页的更新。

其实简单来说, ajax 就是一个 http 客户端,可以异步请求服务器。

<html> 
 	<body> 
 		<div id="app"> 
 			<button v-on:click="myclick()">提交</button> 
 		</div> 
 	</body> 
</html> 

<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
<script> 
 	let app = new Vue({ 
 		el: '#app', 
 		data: { 
 			numbers: 0, 
 			videos: [] 
 		}, 
		methods: { 
 			myclick: function() { 
 				$.ajax({ 
 					url: "http://192.168.122.137:9090/video", 
 					type: "get", 
 					context: this,//这里是将vue对象传入ajax作为this对象
 					success: function(result,status,xhr) {//请求成功后的处理函数
 						this.videos = result; 
 						alert(result); 
 					} 
 				}) 
 			} 
 		}
  	}); 
</script>

项目总结

**项目名称:**视频共享点播系统

**项目功能:**搭建一个共享点播系统,服务器支持用户通过前端浏览器访问服务器,获取展示与观看和操作的界面,最

终实现视频的上传以及观看和删改查等基础管理功能。

开发环境: centos7.6/vim、g++、gdb、makefile

技术特点: http 服务器搭建, restful 风格接口设计, json 序列化,线程池(httplib), html+css+js 基础

项目模块:

  • 数据管理模块:基于 MYSQL 进行数据管理,封装数据管理类进行数据统一访问
  • 业务处理模块:基于 HTTPLIB 搭建 HTTP 服务器,使用 restful风格 进行接口设计处理客户端业务请求
  • 前端界面模块:基于基础的 HTML+CSS+JS 完成基于简单模板前端界面的修改与功能完成。

项目扩展:

  1. 添加用户管理以及视频分类管理

  2. 添加视频的评论,打分功能。

  3. 服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理

常见问题:

  1. 说说你的项目

  2. 为什么做这个项目

  3. 项目中的某个技术点你是怎么实现的,为什么要用它

  4. 服务器怎么搭建的,为什么不自己实现

  5. 多个客户端同时视频上传怎么处理

  6. 你的服务器支持多少个客户端,如何进行测试的

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

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

相关文章

RabbitMQ 安装

目录 一、安装RabbitMQ1、Linux 安装⑴、官网rpm包安装①、下载rpm安装包官网下载erlangrabbitmq packagecloud下载erlangrabbitmq ②、安装 erlang③、安装rabbitmq ⑵、packagecloud快速安装⑶、添加用户账号及权限并登陆⑷、卸载erlang与rabbitmq卸载rabbitmq卸载erlang 2、…

手把手教你实战TDD | 京东云技术团队

1. 前言 领域驱动设计&#xff0c;测试驱动开发。 我们在《手把手教你落地DDD》一文中介绍了领域驱动设计&#xff08;DDD&#xff09;的落地实战&#xff0c;本文将对测试驱动开发&#xff08;TDD&#xff09;进行探讨&#xff0c;主要内容有&#xff1a;TDD基本理解、TDD常…

了解D-Galactopyranose pentaacetate,CAS号25878-60-8的性质和应用

​ 中文名称&#xff1a;1,2,3,4,6-D-葡萄糖五乙酸酯 英文名称&#xff1a;D-Galactopyranose pentaacetate 规格标准&#xff1a;1g、5g、10g CAS&#xff1a;25878-60-8 分子式&#xff1a;C16H22O11 分子量&#xff1a;390.34 熔点&#xff1a;113C 沸点&#xff1a;451C 密…

MATLAB文化算法

目录 文化算法 主要代码 Sphere AdjustCulture 结果 文化算法 基本概念&#xff1a;优化算法 | 详解文化算法&#xff08;附MATLAB代码&#xff09; - 知乎 不同于遗传算法只有种群进化空间&#xff0c;文化算法包含信念空间、种群空间两个进化空间&#xff0c;因此&#…

ceph分布式存储实战

ceph分布式存储实战 分布式存储系统简介 性能与优势对比 虚拟机安装ceph集群 ceph存储系统简介 分布式存储概述 ceph基础 高可用ceph分布式存储系统部署 部署ceph集群 节点管理 ceph使用基础及数据存储案例 PG状态、数据读写流程及存储池操作 mon服务器的高可用: # apt in…

KaiwuDB 受邀亮相 IOTE 2023 第十九届国际物联网展

5月17日&#xff0c;IOTE 2023 第十九届国际物联网展在上海拉开序幕&#xff0c;全球超过 350 家参展企业到场展示先进的物联网技术和产品&#xff0c;行业专家、领军企业代表等人物齐聚一堂&#xff0c;共话 IoT 未来趋势。KaiwuDB 受邀亮相参展并就《工业物联网产业数字化转型…

CRF条件随机场的原理、例子、公式推导和应用

转子&#xff1a;https://zhuanlan.zhihu.com/p/148813079 条件随机场&#xff08;Conditional Random Field&#xff0c;CRF&#xff09;是自然语言处理的基础模型&#xff0c;广泛应用于中文分词、命名实体识别、词性标注等标注场景。 条件随机场CRF与深度学习结合&#xf…

如何使用Github搭建个人博客

介绍 在本文中&#xff0c;我将介绍如何使用GitHub搭建个人博客&#xff08;免费&#xff09;。GitHub是一个功能强大的版本控制和协作平台&#xff0c;它也可以用来托管和发布静态网页。通过将你的个人博客托管在GitHub上&#xff0c;你可以享受到版本控制的好处&#xff0c;…

抖音自动生成视频、字幕、自动上传发布

dy-auto ✨ 抖音自动生成视频、字幕、自动上传发布✨ 项目地址 点击进入https://github.com/Richard0403/dy-auto 录屏效果 https://github.com/Richard0403/dy-auto/assets/14147304/21400a42-9296-4956-9517-ced8d8bf4737 技术架构 名称功能ffmpeg处理视频的生成&…

大模型LLM领域,有哪些可以作为学术研究方向?

清湛人工智能研究院 2023-05-31 09:23 发表于江苏 编者&#xff1a;本文转载了清华大学计算机系刘知远教授对大模型的一些思索&#xff0c;以飨读者。 刘知远 CCF 高级会员&#xff0c;CCCF 前编委。清华大学计算机系副教授、博士生导师。已在ACL、IJCAI、AAAI等人工智能领域…

【sentinel】Sentinel规则的持久化

Sentinel规则的推送有下面三种模式: 推送模式说明优点缺点原始模式API将规则推送至客户端并直接更新到内存中简单&#xff0c;无任何依赖不保证一致性&#xff1b;规则保存在内存中&#xff0c;重启即消失。严重不建议用于生产环境Pull模式扩展写数据源&#xff08;WritableDa…

初学Nginx要掌握哪些概念

文章目录 为什么要使用Nginx&#xff1f;什么是Nginx&#xff1f;Nginx的作用&#xff1f;反向代理负载均衡动静分离 为什么要使用Nginx&#xff1f; 小公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发的情况下&#xff0c;一个…

【链表复习】C++ 链表复习及题目解析 (2)

目录 牛客 CM11 链表分割 牛客 OR36 之链表的回文结构 Leetcode 160. 相交链表 LeetCode 141. 环形链表 LeetCode 138. 复制带随机指针的链表 本文继续延续前文&#xff0c;为大家带来几道经典的链表中等难度的题目。 牛客 CM11 链表分割 现有一链表的头指针 ListNode* p…

GUT|IF30+的联合分析文章:宏基因加代谢组

● 代谢组学是基于LC-MS/MS液质联用技术对生物样本中的小分子代谢物进行定性和相对定量分析&#xff1b; ● 宏基因组-代谢组的联合分析可以用来解释差异菌群与差异代谢物的关联性&#xff1b; ● 从而帮助建立微生物-代谢物-表型之间的逻辑关系。 凌恩生物的宏基因组学引入了…

JDK21要来了,并发编程更加丝滑了

大家好&#xff0c;我是风筝&#xff0c;公众号「古时的风筝」&#xff0c;专注于 Java技术 及周边生态。 我的个人网站&#xff1a;古时的风筝 目前 Java 的最新稳定版是 JDK 20&#xff0c;但这是个过渡版&#xff0c;JDK21就是 LTS 版的了&#xff0c;也快要发布了&#xff…

经典文献阅读之--A Review of Motion Planning(轨迹规划回顾)

0. 简介 对于自动驾驶以及机器人而言&#xff0c;除了SLAM以外&#xff0c;另一个比较重要的部分就是轨迹规划了。而最近作者看到了几篇比较好的文章&#xff0c;分别为《A Review of Motion Planning Techniques for Automated Vehicle》、《A review of motion planning alg…

Python中处理无效数据的详细教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

IDEA全局设置JDK、Maven、编码格式

本机已安装JDK版本&#xff1a; 本机已安装Maven版本&#xff1a; 一、IDEA设置全局JDK设置 File---->New Projects Settings---->Structure for New Projects... 先将本地安装的JDK添加到SDK 将项目SDK设置为刚刚添加的本地JDK版本 File---->New Projects Settings-…

8分钟让你完全掌握代理IP基础知识和实际应用

概念 代理IP可以理解为一个中转服务器&#xff0c;将用户和目标服务器之间的请求和响应进行转发和代理。使用代理IP的主要目的是隐藏用户的真实IP地址、访问被限制的内容、提高网络连接速度和保护用户隐私。 目录 概念 一、代理IP的工作原理 二、代理IP的类型 三、为什么…

Docker安装ClickHouse22.6.9.11并与SpringBoot、MyBatisPlus集成

背景 上一篇文章CentOS6.10上离线安装ClickHouse19.9.5.36并修改默认数据存储目录记录了在旧版的操作系统上直接安装低版本 ClickHouse &#xff08;脱胎于俄罗斯头号搜索引擎的技术&#xff09;的过程&#xff0c;开启远程访问并配置密码&#xff1b; 其实通过 Docker 运行 …