服务端功能模块划分
数据管理模块:负责针对客户端上传的视频信息进行管理。
网络通信模块:搭建网络通信服务器,实现与客户端通信。
业务处理模块:针对客户端的各个请求进行对应业务处理并响应结果。
前端界面模块:完成前端浏览器上视频共享点播的各个 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; //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;
}
反序列化样例
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)
流程
样例
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专门用于检测文件是否存在 -- 存在返回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 对应删除
整体设计
关于上传视频信息以及文件
因为上传视频信息的时候,会携带有视频文件和封面图片的文件上传,而这些文件数据都是二进制的,用 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>
代码解释:
-
创建了一个 div, id 为 app
-
在 div 中使用 “插值表达式” {{message}} 来指定 div 内部的内容.
-
js 中创建了一个名为 app 的 Vue 实例. 构造函数中的参数是一个对象.
-
参数中的 el 字段是一个选择器, 对应到 html 中的具体元素id.
-
参数中的 data 字段是一个特定的对象, 用来放置数据.
-
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 完成基于简单模板前端界面的修改与功能完成。
项目扩展:
-
添加用户管理以及视频分类管理
-
添加视频的评论,打分功能。
-
服务器上的视频或图片采用压缩处理节省空间,进行热点与非热点的管理
常见问题:
-
说说你的项目
-
为什么做这个项目
-
项目中的某个技术点你是怎么实现的,为什么要用它
-
服务器怎么搭建的,为什么不自己实现
-
多个客户端同时视频上传怎么处理
-
你的服务器支持多少个客户端,如何进行测试的