服务端功能模块划分
数据管理模块:负责针对客户端上传的视频信息进行管理。
网络通信模块:搭建网络通信服务器,实现与客户端通信。
业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。
前端界面模块:完成前端浏览器上视频共享点播的各个 html 页面,在页面中支持增删改查以及观看功能。
环境搭建
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; //buildstd::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; }
反序列化样例
int main() { //R("")是cpp11原生字符串的用法,里面的所有字符没有特殊含义。std::string str = R"({"姓名":"小明", "年龄":18, "成绩":[76.5, 55, 88]})"; Json::Value root; //value!!!Json::CharReaderBuilder crb; //buildstd::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)
流程
样例
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 服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。
里面的两个重要的结构体Request和Response就是用来存储请求和响应信息的,以便于组织请求和响应数据。
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服务器静态资源根目录}; }
样例
服务端功能实现
文件工具类
在视频点播系统中因为涉及到文件上传,需要对上传的文件进行备份存储,因此首先设计封装文件操作类,这个类封
装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。
- 获取文件大小(属性)
- 判断文件是否存在
- 向文件写入数据
- 从文件读取数据
- 针对目录文件多一个创建目录
也可以用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专门用于检测文件是否存在 -- 存在返回0int 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){//连接mysqlstd::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());//查询即执行sqlif(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 imagestd::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 imagestd::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 endint 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 endint 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 endint 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 对应删除
整体设计
关于上传视频信息以及文件
因为上传视频信息的时候,会携带有视频文件和封面图片的文件上传,而这些文件数据都是二进制的,用 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.jpgstd::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.jpgif(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. 获取视频的idint 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>
无语义标签: p & span
p 标签, pision 的缩写, 含义是 分割
span 标签, 含义是跨度
说白了就是两个盒子. 常用于网页布局
- p 是独占一行的, 是一个大盒子
- span 不独占一行, 是一个小盒子
<p>p是个大盒子独占一行</p> <span>span是个小盒子并不独占一行</span> <span>span是个小盒子并不独占一行</span>
html5 语义化标签
p 没有语义. 对于搜索引擎来说没有意义. 为了让搜索引擎能够更好的识别和分析页面(SEO 优化), HTML 引入了更多
的 “语义化” 标签. 但是这些标签其实本质上都和 p 一样(对于前端开发来说). 然而对于搜索引擎来说, 见到 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. 基础选择器: 单个选择器构成的
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 程序
<p id="app">{{message}}</p> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { message: 'hello', }, }); </script>
代码解释:
创建了一个 p, id 为 app
在 p 中使用 “插值表达式” {{message}} 来指定 p 内部的内容.
js 中创建了一个名为 app 的 Vue 实例. 构造函数中的参数是一个对象.
参数中的 el 字段是一个选择器, 对应到 html 中的具体元素id.
参数中的 data 字段是一个特定的对象, 用来放置数据.
data 中的 message 属性值, 就对应到 {{message}} 中的内容. Vue 会自动解析 {{message}} , 并把 data 中对应
的名字为 message 的属性值替换进去.
理解响应式: 修改 message 的值, 界面显示的内容就会发生改变
插值操作: {{}}
<p id="app"> {{str1}} {{str2}} </p> <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> <p id="app" v-cloak> {{message}} </p>
注意**😗* v-cloak 相当于 p 标签的一个属性. 这个属性在 Vue 接管 p 之前会存在, 但是 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 后面使用 : 连接后续的事件名称
<p id="app"> <button v-on:click="dialog('点击了按钮')">按钮</button> <!--当按钮被点击就会触发事件函数的调用--> </p> <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 对象的函数或方法都放在其中
<p id="app"> <form action="http://www.sogou.com"> <input type="submit" value="提交" v-on:click.prevent="click()"> </form> </p> <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 时不显示
<p id="app"> <h3 v-show="flag">hello</h3> </p> <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
通过一个表达式决定某个元素是否被渲染.
<p id="app"> <p v-if="score > 90">学神</p> <p v-else-if="score > 80">学霸</p> <p v-else-if="score > 60">普通</p> <p v-else>学渣</p> </p> <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 可以绑定数据到数组来渲染一个标签
<p id="app"> <p v-for="hero in heros">{{hero}}</p> </p> <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 数据与标签数据关联起来,实现一荣俱荣一损俱损的效果。
<p id="app"> <input type="text" v-model="message"> {{message}} </p> <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> <p id="app"> <button v-on:click="myclick()">提交</button> </p> </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 完成基于简单模板前端界面的修改与功能完成。
项目扩展:
添加用户管理以及视频分类管理
添加视频的评论,打分功能。
服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理
常见问题:
说说你的项目
为什么做这个项目
项目中的某个技术点你是怎么实现的,为什么要用它
服务器怎么搭建的,为什么不自己实现
多个客户端同时视频上传怎么处理
你的服务器支持多少个客户端,如何进行测试的