个人博客系统

博客系统

这是一个关于个人博客系统的实现。

主要设计思路

1.项目名称:个人博客系统;
2.开发环境:Centos7.6 -vim/makefile;
3.使用技术:MVC框架,MySQL数据库,httplab,jsoncpp,vue.js,ajax;
4.项目功能:实现一个web服务器,能够提供用户通过浏览器访问服务器,实现博客的展示,以及博客的增删改查管理;
5.框架设计思路:通过MVC框架:实现前端的页面,后台的服务以及数据的管理。

MVC框架

首先介绍一下MVC框架。
前端界面模块需要获取数据进行展示,向服务控制模块发送请求,服务控制模块从数据管理模块获取到数据,组织后发送给前端,是一种服务,数据,页面分离的开发方式。

M–model–数据管理模块,基于MySQL数据库进行数据管理;
C–controller–服务控制模块,博客页面的获取以及博客数据的增删改查操作;
V–view–前端界面模块,基于html+css+js实现前端界面的展示。

主要内容设计

一、数据管理模块

  对于数据管理模块,主要使用mysql数据库进行数据管理,mysql客户端服务器模式,在服务器上统一进行数据管理,外部通过接口进行数据获取。

客户端将对应的语句发送给服务器,服务器进行语句解析之后完成语句中各个元素所表示的操作。当然客户端不需要我们自己搭建,通过mysql提供的接口就可以完成,因此只需要设计我们对应的语句就可以了。

  外界客户端通过数据库的SQL语句完成对数据库中数据的操作(增删改查)。SQL结构化查询语言—通过语句中的元素以及关键字告诉服务器客户端想要如何操作数据,操作哪些数据。

基本操作:
  建库–类似于学校中建立图书馆,
  建表–类似于在图书馆中添加书架,
  增删改查–操作的是就是某个图书馆中的某个书架上的书籍

Note:mysql中的数据是以行和列的关系形式组织管理数据

1、MySql数据库

①库的一些基本操作

//创建库,如果db_name这个库不存在,则创建db_name
create database if not exists db_name;

//删除库:删除db_name数据库
drop database db_name;

//显示所有数据库
show database;

//选择要操作的数据库
use database;

②表的一些基本操作

//创建表,如果tb_name这个表不存在,则创建tb_name
create table if not exists tb_name;

//删表:删除tb_name表
drop table tb_name;

//显示数据库中的所有表
show tables;

//描述tb_name表的字段属性信息
desc tb_name;

③表中数据的操作

//增加、插入信息:
insert tb_name(sn, ch,name,ctime) values ((1, 88.99, "张三", now());

//删除信息:删除信息的时候一定要注意指定判断条件,否则是删除所有数据
delete from tb_name where name = '张三';

//查找信息:
select *from tb_name;
select fields_name... from tb_name;

//修改信息,修改信息的时候一定要注意指定判断条件,否则是针对所有数据进行改变
update tb_name set ch=88,sn=1 where name = '张三';

2、博客数据库中表的设计

①标签表的设计

Tag标签表:标签id,标签名称

create table if not exists tb_tag 
(
	id int primary key auto_increment comment '标签ID',
	name varchar(32) comment '标签名称'	
);
//primary key 主键,用于设置主键字段,为该字段创建索引,并且表示该字段是唯一的,不可以为NULL的
//auto increment: 自增属性,用于整形数据字段,表示每次添加数据的时候该字段的值默认从1开始会自动+1

②博客表的设计

Blog博客表:博客id,标签id,博客标题,博客正文,创建时间

create table if not exists tb_blog
(
    id int primary key auto_increment comment'博客ID',
    tag_id int comment'标签ID',
    title varchar(255) comment'名称',
    content text comment'正文内容',
    ctime datetime comment'创建时间'
);
//primary key 主键,用于设置主键字段,为该字段创建索引,并且表示该字段是唯一的,不可以为NULL的
//auto increment: 自增属性,用于整形数据字段,表示每次添加数据的时候该字段的值默认从1开始会自动+1

③创建一个博客系统库

  首先,创建一个db.sql文件,将SQL语句写入其中。之后再打开数据库时,使用 mysql -uroot -p < db.sql 可以将写好的博客系统库文件导入MySQL中自动执行

//创建db_blog博客库
create database if not exists db_blog;

use db_blog;//选择使用这个库

//创建标签表
create table if not exists tb_tag 
(
  id int primary key auto_increment comment '标签ID',
  name varchar(32) comment '标签名称'
);

//创建博客表
create table if not exists tb_blog
(
  id int primary key auto_increment comment '博客ID',
  tag_id int comment '所属标签的ID',
  title varchar(255) comment '博客标题',
  content text comment '博客内容',
  ctime datetime comment '博客创建时间'
);

//向标签表中增加信息
insert tb_tag values(null, 'C++'), (null, 'Java'), (null, 'Linux'), (null, 'MySQL');

//向博客表中增加信息
insert tb_blog values (null, 1, 'C++', 'C++最好', now()),
                      (null, 2, 'Java', 'Java好', now()),
                      (null, 3, 'Linux', 'Linux好', now());

  上述数据库管理中,我们使用了MySQL提供的客户端访问MySQL服务器实现的数据操作。在本次项目中,我们需要通过自己的数据管理模块完成对数据库的访问,因此我们需要实现一个MySQL客户端完成我们需要的功能。
  主要通过MySQL提供的接口进行客户端的搭建,接下来主要介绍MySQL提供的一些开发接口。

3、MySQL接口

①初始化mysql句柄

参数通常为null,表明动态分配一块空间并进行初始化

MYSQL* mysql_init(MYSQL* mysql)

②连接mysql服务器

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

mysql: 初始化完成的句柄
host: 要连接的mysql服务器IP
user: mysql数据库用户名
passwd: 数据库访问密码
db: 默认要选择使用的数据库名称
port: mysql服务端口, 0默认表示3306端口
unix_socket: 指定socket或管道, 通常为NULL
client_flag: 一些选项操作标志位, 通常置0
返回值: 返回句柄的空间首地址,出错返回NULL

③设置客户端字符编码集

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

mysql:mysql句柄
csname:utf-8
返回值:成功返回0,失败返回非0

④选择使用的数据库

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

mysql:mysql句柄
db:数据库名称
返回值:成功返回0,失败返回非0

⑤表中数据的操作

int mysql_query(MYSQL* mysql,const char* stmt_str)

mysql: mysql句柄
stmt_str: 要执行的sql语句
返回值: 成功返回0, 失败返回非0

⑥获取结果集

增删改操作只需要执行语句成功即可,但查询不一样,在语句执行成功之后需要返回获取的结果,因此需要进行结果集的获取。

//将查询结果获取到本地
MYSQL_RES* mysql_store_result(MYSQL *mysql)

//获取查询结果中的条数
uint64_t mysql_num_rows(MYSQL_RES *result)

//获取查询结果中的列数
unsigned int mysql_num_fields(MYSQL_RES *result)

//遍历结果集,每次获取一条数据
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)

//释放结果集
void mysql_free_result(MYSQL_RES *result)

⑦关闭数据库

void mysql_close(MYSQL *mysql)

⑧获取mysql接口调用失败的原因

const char* mysql_error(MYSQL *mysql)

以上就是MySQL提供的常用接口,总结如下:
初始化: mysql_init;
连接服务器: mysql_real_connect;
设置字符集: mysql_set_character_set;
选择数据库: mysql_select_db;
执行语句: mysql_query;
保存结果集: mysql_store_result;
获取结行数: mysql_num_rows;
获取结列数: mysql_num_fields;
遍历结果: mysql_fetch_row;
释放结果集: mysql_free_result;
关闭数据库: mysql_close;
获取接口错误原因: mysql_error

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

int main()
{
	MYSQL *mysql = mysql_init(NULL);
	if (mysgl == NULL)
	{
		printf("init mysql error\n");
		return -1;
	}  
		
	//连接服务器
	if (mysgl_real_connect(mysql,"172.31.43.144","root","111111","db_blog", 0, NULL, 0) == NULL)
	{
		printf("connect mysql server failed:%s\n", mysql_error(mysql));
		return -1;
	}

	//设置客户端字符集
	if (mysgl_set_character_set(mysgl,"utf8") != 0) 
	{
		printf("set character failed:%s\n",mysql_error(mysql));
		return -1;
	}

	//选择切换使用的数据
	if (mysql_select_db(mysql,"db_blog") != 0)
	{
		printf("select db failed:%s\n", mysql_error(mysql));
		return -1;
	}

	//创建表
   char *sql ="select * from tb_stu;";
   int ret = mysql_query(mysql, sql);
   if (ret != 0) 
   {
   		printf("query sql failed:%s\n",mysql_error(mysql));
   		return -1;
   	}

	//保存结果集
	MYSOL RES *res = mysql_store_result(mysql);
	if (res == NULL) 
	{
		printf("store result failed:%s\n", mysql_error(mysql));
		return -1;
	}
	
	int num_row = mysql_num_rows(res);//行数
	int num_col = mysql_num_fields(res);//列数
	int i = 0;
	for (i = 0; i < num_row; i++) 
	{
		MYSQL_ROW row = myaql_fetch_row(res); 
		//res中有读取位置控制,每次获取的都是下一条数据
		for (int j = 0; j<num_col; j++) 
		{
			printf("%s\t",row[j]);
		}
		printf("\n");
	}
	return 0;
}

4、JSon数据格式

  在数据管理模块,考虑数据对象作为参数进行传递,我们可能要组织很多的数据对象,但参数过多会降低效率,且结果过于复杂。因此使用Json数据系列化方式,则需要安装Jsoncpp库,Jsoncpp库可以将多个数据对象序列化为json格式的字符串,或者将json格式的字符串反序列化得到各个数据对象。

Json::Value 中间的数据交换类
Json::Reader 实现反序列化,将json格式字符串解析得到Json::Value对象
Json::Writer 实现序列化,将Json::Value对象中的数据组织成json字符串

示例:展示接口和对应数据格式的相关转换

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

using namespace std;
int main()
{
	Json::Value value;
	value["name"]="张三";
	value["age"] = 18;
	value["score"]=88.88;

	Json::StyledWriter writer;
	string str = writer.write(value);
	std::cout<<str<<std::endl;

	Json::Value value1;
	Json::Reader reader;
	reader.parse(str, value1);

	std::cout<<value1["name"].asString()<<std::endl;
	std::cout<<value1["age"].asInt()<<std::endl;
	std::cout<<value1["score"].asFloat()<<std::endl;
	return 0;
}

对应输出:

 {
  "name":"张三"
  “age":18
  "score":88.88
 }
 张三
 18
 88.88

5、数据管理模块具体实现

#include <iostream>
#include <mysql/mysql.h>
#include <jsoncpp/json/json.h>
#include <mutex>

using namespace std;

#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PSWD "     "
#define MYSQL_DB "db_blog"

namespace blog_system  //命名空间,防止命名冲突
{
  static std::mutex g_mutex;  //互斥锁

  // #############################################  初始化   #############################################
  MYSQL *MysqlInit() 
  {
    //向外提供接口,返回初始化的mysql句柄
    MYSQL *mysql;
    
    (①初始化句柄,②连接服务器, ③设置字符集  ④选择数据库)
    //①初始化
    mysql = mysql_init(NULL);
    if(mysql == NULL) 
    {
      printf("init mysql error\n"); //初始化失败,返回空
      return NULL;
    }

    //②连接服务器
    if(mysql_real_connect(mysql, MYSQL_HOST, MYSQL_USER, MYSQL_PSWD, NULL, 0, NULL, 0) == NULL) //句柄   服务器地址  用户名  密码  数据库名称空  端口默认为0,使用3306接口   套接字为空
    {
      //连接服务器失败,mysql_error(mysql)获取错误原因
      printf("connect mysql server error:%s\n", mysql_error(mysql)); 
      mysql_close(mysql); //关闭,释放资源
      return NULL;
    }

    //③设置字符集
    if(mysql_set_character_set(mysql, "utf8") != 0) 
    {
      printf("set client character error:%s\n", mysql_error(mysql));
      mysql_close(mysql);
      return NULL;
    }

    //④选择数据库,选择db_blog数据库
    if(mysql_select_db(mysql, MYSQL_DB) != 0) 
    {
      printf("select db error:%s\n", mysql_error(mysql));
      mysql_close(mysql);
      return NULL;
    }
    return mysql;  //全部成功后,返回句柄
  }

  //销毁句柄
  void MysqlRelease(MYSQL* mysql) 
  {
    if (mysql)  {mysql_close(mysql);}
  }

  //执行语句的公有接口
  bool MysqlQuery(MYSQL* mysql, const char* sql) 
  {
    int ret = mysql_query(mysql, sql);
    if(ret != 0) 
    {
      printf("query sql:[%s] failed:%s", sql, mysql_error(mysql));
      return false;
    }
    return true;
  }

  //  #############################################  博客表  #############################################
  class TableBlog 
  {
    public:
      TableBlog(MYSQL* mysql) :_mysql(mysql) {}
      
      // 增:插入信息
      bool Insert(Json::Value &blog) 
      {
#define INSERT_BLOG "insert tb_blog values(null, '%d', '%s', '%s', now());"
        //因为博客正文长度不懂, 有可能会很长, 因此如果直接定义固定长度tmp, 有可能访问越界
        int len = blog["content"].asString().size() + 4096;
        char* tmp = (char*)malloc(len);
        sprintf
        (	tmp, INSERT_BLOG, 
        	blog["tag_id"].asInt(), 
        	blog["title"].asCString(), 
        	blog["content"].asCString()
        );
        	bool ret = MysqlQuery(_mysql, tmp);
        free(tmp);
        return ret;
      }

      //  删:根据博客id删除博客
      bool Delete(int blog_id) 
      { 
#define DELETE_BLOG "delete from tb_blog where id=%d;"
        char tmp[1024] = {0};
        sprintf(tmp, DELETE_BLOG, blog_id);
        bool ret = MysqlQuery(_mysql,tmp);
        return ret;
      }

      //  改:从blog中取出博客信息, 组织sql语句,更新数据库中的数据
      bool Update(Json::Value& blog) 
      {
        //id tag_id title content ctime
#define UPDATE_BLOG "update tb_blog set tag_id=%d, title='%s', content='%s' where id=%d;"
        int len = blog["content"].asString().size() + 4096;
        char *tmp = (char*)malloc(len);
        sprintf
        (
        	tmp, UPDATE_BLOG, 
        	blog["tag_id"].asInt(), 
       	 	blog["title"].asCString(), // C语言风格的字符串
        	blog["content"].asCString(),
        	blog["id"].asInt()
        );
        bool ret = MysqlQuery(_mysql, tmp);
        free(tmp);
        return ret;
      }

      // 查: 通过blog返回所有的博客信息(通常是列表展示, 不返回正文)
      // ①执行查询语句    ②保存结果集    ③遍历结果集
      bool GetAll(Json::Value *blogs)  
      { 
        ①执行查询语句
#define GETALL_BLOG "select id, tag_id, title, ctime from tb_blog;"
        g_mutex.lock(); //加锁
        bool ret = MysqlQuery(_mysql, GETALL_BLOG);
        if (ret == false) //查询失败 
        {
          g_mutex.unlock(); //解锁
          return false;
        }

        // ②查询成功,保存结果集
        MYSQL_RES* res = mysql_store_result(_mysql);
        g_mutex.unlock(); //解锁
        if (res == NULL) 
        {
          printf("store all blog result failed:%s\n", mysql_error(_mysql));
          return false;
        }

        // ③遍历结果集
        int row_num = mysql_num_rows(res); //获取行数
        for(int i = 0; i < row_num; i++)   //遍历
        {
          MYSQL_ROW row = mysql_fetch_row(res);
          Json::Value blog;
          //  id, tag_id, title, ctime   0  1  2  3
          blog["id"] = std::stoi(row[0]); // std::stoi作用将字符串数字转为整形数据   例如: “123” -> 123
          blog["tag_id"] = std::stoi(row[1]);
          blog["title"] = row[2];
          blog["ctime"] = row[3];
          blogs -> append(blog); // 将每个信息都添加到blogs里,添加数组元素。 blogs是参数,返回所有博客信息
        }
        mysql_free_result(res); 
        return true;  //获取所有博客信息成功
      }

      //获取单个博客信息, 包含正文
      bool GetOne(Json::Value *blog) 
      {
#define GETONE_BLOG "select tag_id, title, content, ctime from tb_blog where id=%d;" //组织Sql语句
        char tmp[1024] = {0};
        sprintf(tmp, GETONE_BLOG, (*blog)["id"].asInt());
        g_mutex.lock();
        bool ret = MysqlQuery(_mysql, tmp);
        if(ret == false) 
        {
          g_mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql); //保存结果集
        g_mutex.unlock();
        if (res == NULL) 
        {
          printf("store all blog result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int row_num = mysql_num_rows(res);
        g_mutex.unlock();
        if (row_num != 1) //因为获取的是单个博客信息,所以列数不等于1就出错了
        {
          printf("get one blog result error\n");
          mysql_free_result(res);
          return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        (*blog)["tag_id"] = std::stoi(row[0]);
        (*blog)["title"] = row[1];
        (*blog)["content"] = row[2];
        (*blog)["ctime"] = row[3];
        mysql_free_result(res);
        return true;
      }
    private:
      MYSQL* _mysql;
  };

  // ##############################################  标签表  #############################################
  class TableTag 
  {
    public:
      TableTag(MYSQL* mysql) :_mysql(mysql) {}
      
      bool Insert(Json::Value &tag) //增
      {
#define INSERT_TAG "insert tb_tag values(null, '%s');"  // 组织sql语句,id自动增长,给空
        char tmp[1024] = {0};
        sprintf(tmp, INSERT_TAG, tag["name"].asCString());
        return MysqlQuery(_mysql, tmp);
      }

      //删
      bool Delete(int tag_id) 
      {
#define DELETE_TAG "delete from tb_tag where id=%d;"
        char tmp[1024] = {0};
        sprintf(tmp, DELETE_TAG, tag_id);
        return MysqlQuery(_mysql, tmp);
      }

      //改
      bool Update(Json::Value &tag) 
      {
#define UPDATE_TAG "update tb_tag set name='%s' where id=%d;"
        char tmp[1024] = {0};
        sprintf(tmp, UPDATE_TAG, tag["name"].asCString(), tag["id"].asInt());
        return MysqlQuery(_mysql, tmp);
      }

      //查
      bool GetAll(Json::Value *tags) 
      {
#define GETALL_TAG "select id, name from tb_tag;"
        g_mutex.lock();
        bool ret = MysqlQuery(_mysql, GETALL_TAG);
        if(ret == false) 
        {
          g_mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql); //保存结果集
        g_mutex.unlock();
        if(res == NULL) 
        {
          printf("store all tag result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int row_num = mysql_num_rows(res);  //获取行数
        for(int i = 0; i < row_num; i++) //遍历
        {
          MYSQL_ROW row = mysql_fetch_row(res);
          Json::Value tag;
          tag["id"] = std::stoi(row[0]); 
          tag["name"] = row[1];
          tags->append(tag);
        }
        mysql_free_result(res);
        return true;
      }

      bool GetOne(Json::Value *tag) 
      {
#define GETONE_TAG "select name from tb_tag where id=%d;"
        char tmp[1024] = {0};
        sprintf(tmp, GETONE_TAG, (*tag)["id"].asInt());
        g_mutex.lock();
        bool ret = MysqlQuery(_mysql, tmp);
        if (ret == false) 
        {
          g_mutex.unlock();
          return false;
        }
        MYSQL_RES* res = mysql_store_result(_mysql);
        g_mutex.unlock();
        if(res == NULL) 
        {
          printf("store one tag result failed:%s\n", mysql_error(_mysql));
          return false;
        }
        int row_num = mysql_num_rows(res);
        if(row_num != 1)
        {
          printf("get one tag result error\n");
          mysql_free_result(res);
          return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        (*tag)["name"] = row[0];
        mysql_free_result(res);
        return true;
      }
    private:
      MYSQL* _mysql;
  };
}

锁产生的情况

  执行查询语句 和 保存结果集 是两步分开的操作,并不是原子操作,在多线程中使用同一个mysql句柄时要注意线程安全问题。
  如果每一个查询操作都实例化一个对象,并创建一个mysql句柄,效率低下,所以我们使用的是多个查询对象使用同一个对象,也使用同一个mysql句柄。
  通过引入互斥锁解决问题,在执行语句前加锁,失败或者保存成功后解锁,保证两步是原子操作因此通过引入互斥锁 g_mutex 解决问题,在执行语句前加锁,失败或者保存成功后解锁,保证两步是原子操作。

6、测试示例

在这里插入图片描述

①插入

//向 tabletag 中插入一个新的内容
#include "db.hpp"
int main()
{
	MYSQL *mysql = blog system::MysqlInit();
	blog_system::TableBlog table blog(mysql);
	blog system::TableTag table tag(mysql) ;
	
	Json::Value tag;
	tag["name"] ="C";
	table_tag.Insert(tag);
	return 0;
}

结果:在这里插入图片描述

②删除

#include "db.hpp"
int main()
{
	MYSQL *mysql = blog system::MysqlInit();
	blog_system::TableBlog table blog(mysql);
	blog system::TableTag table tag(mysql) ;

	table_tag.Delete(5); //删除ID=5的
	return 0;
}

在这里插入图片描述

③修改

#include "db.hpp"
int main()
{
	MYSQL *mysql = blog system::MysqlInit();
	blog_system::TableBlog table blog(mysql);
	blog system::TableTag table tag(mysql) ;

	Json::Value tag;
	tag["name"] ="C#";
	tag["id"] =1;
	table_tag.Updata(tag);
	return 0;
}

在这里插入图片描述

④查询/获取

#include "db.hpp"
int main()
{
	MYSQL *mysql = blog system::MysqlInit();
	blog_system::TableBlog table blog(mysql);
	blog system::TableTag table tag(mysql) ;

	Json::Value tag;
	table_tag.GetAll(&tag);
	Json::StyledWriter writer;
	std::cout << writer.write(tag) << std::endl;
	return 0;
}

在这里插入图片描述

#include "db.hpp"
int main()
{
	MYSQL *mysql = blog system::MysqlInit();
	blog_system::TableBlog table blog(mysql);
	blog system::TableTag table tag(mysql) ;

	Json::Value tag;
	tag["id"] =1;
	table_tag.GetOne(&tag);
	Json::StyledWriter writer;
	std::cout << writer.write(tag) << std::endl;
	return 0;
}

在这里插入图片描述

二、业务逻辑模块

1、网络通信功能

  提供客户端与服务器的网络通讯功能,要后台搭建http服务器,客户端就是浏览器。

  http服务器是什么:采用http协议实现的服务,http协议是一个应用层协议,是一种数据格式,采用了http协议进行通信的进程可以称为http服务进程。

  http是应用层协议,在传输层使用的是tcp协议;

  http服务器:就是一个tcp服务器,只是在应用层(应用程序的处理层面),完成http协议格式的请求与响应,以及针对请求提供服务。

如何搭建http服务器:
1.搭建tcp服务端;
2.等待客户端(浏览器)请求,解析请求,得到http协议格式中的各个要素(请求方法,url(资源路径以及查询字符串),头部字段,正文);
3.根据客户端的需要,完成业务处理,组织http响应数据进行响应。

对于搭建http服务器,借助httplib库进行搭建

2、httplib库介绍

通过github下载 httplib 的相关代码,在之后的文件中引用 httplib.h 的头文件就可以使用此库。

httplib基本使用:主要类:Server Client Request Response

  1. 实例化一个server对象;
  2. 注册请求-处理路由(给对应的请求设置对应的处理函数);
  3. 开始服务器监听
    Note:httplib内部当接收到请求时,解析请求,根据不同的请求找到不同的处理函数,执行这个函数进行业务处理,最终进行响应。
#include <httplib.h>
using namespace httplib; //使用 httplib 命名空间
void helloword(const Request &rep, Response &res)
{
res.set_content("Hello Word!", "text/plain");
}
int main(void)
{
  
  Server svr; //实例化一个server对象

  //svr.Get(),成员函数,注册请求,业务处理路由关系,告诉服务器哪个请求对应使用哪个函数进行业务处理
  //第一个参数: "/hi"  url中的path资源路径
  svr.Get("/hi", helloword);  //针对 hi 这个请求,选择用 helloword 函数进行业务处理
  svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) 
  {
    auto numbers = req.matches[1];
    res.set_content(numbers, "text/plain");
  });
  svr.Get("/users/:id", [&](const Request& req, Response& res) 
  {
    auto user_id = req.path_params.at("id");
    res.set_content(user_id, "text/plain");
  });
  svr.Get("/body-header-param", [](const Request& req, Response& res) 
  {
    if (req.has_header("Content-Length")) {
      auto val = req.get_header_value("Content-Length");
    }
    if (req.has_param("key")) 
    {
      auto val = req.get_param_value("key");
    }
    res.set_content(req.body, "text/plain");
  });
  svr.Get("/stop", [&](const Request& req, Response& res) 
  {
    svr.stop();
  });
  //0.0.0.0  可以识别本机任意的网卡地址
  svr.listen("0.0.0.0", 9000); //开始服务器进行监听
  return 0}

httplib处理流程:
1.先提前编写不同请求对应的处理函数,void(*hander)(Request &,Response &);

2.实例化Server对象(对象中包含请求与处理函数映射表,线程池);
在这里插入图片描述

3.根据不同请求以及建立的业务处理,在Server中建立对应的映射关系
Server.Get()/.Post()/.Put()/.Delete()…;

4.搭建tcp服务器开始监听:Server.listen(“ip”,port);

5.当新连接到来,则将新连接抛入线程池进行处理;
在这里插入图片描述

6.线程池中的线程的处理流程:
①接受http请求头部,进行解析,并且根据头部中的 content-length 接受正文,将解析得到的各个元素填充Request对象;
在这里插入图片描述

在这里插入图片描述
②根据 请求方法+资源路径,在映射表中查找对应请求的处理函数,找到之后执行这个函数,并且传入请求信息;

③我们自己编写的处理函数中根据Request中的请求信息,进行业务处理,处理完毕后填充Response对象;
在这里插入图片描述

④处理函数执行完毕后,线程得到填充完毕的Response对象,根据其中的数据组织http响应,发送给客户端;
在这里插入图片描述

⑤如果是短连接则关闭套接字,处理完毕,如果是长连接则等待下一条请求。

3、网络通信接口设计

设计什么样的请求对应什么样的服务。

  采用 RESTful 风格的接口设计:网络通信接口设计风格-基于HTTP通信,http正文使用json对数据进行序列化反序列化
  动作:GET-获取、POST-新增、PUT-更新、DELETE-删除

在这里插入图片描述

bolg/blog_id 是资源路径,表示要请求什么资源

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4、业务逻辑模块具体实现

#include "db.hpp"
#include "httplib.h"

using namespace httplib;
blog_system::TableBlog *table_blog;
blog_system::TableTag *table_tag;

//######################################## 具体对应的博客表业务处理  #########################################

// ######################################  ①插入博客表的业务逻辑
void InsertBlog(const Request &req, Response &rsp) 
{
  //从请求中取出正文 -- 正文就是提交的博客信息, 以json格式的字符串形式组织的
  req.body;  // 博客正文 
  //将json格式的字符串进行反序列化, 得到各个博客信息
  Json::Reader reader; //反序列化
  Json::Value blog;
  Json::FastWriter writer;
  Json::Value errmsg; //错误原因
  bool ret = reader.parse(req.body, blog);//将请求正文进行json反序列化,因为正文就是json格式的博客信息
  if (ret == false) {
    printf("InsertBlog pares blog json failed!\n");
    rsp.status = 400; //响应状态码自己设置的
    errmsg["ok"] = false; 
    errmsg["reason"] = "pares blog json failed!";
    rsp.set_content(writer.write(errmsg), "application/json"); //添加正文到rsp.body中
    return;
  }
  //将得到的博客信息插入到数据库
  ret = table_blog->Insert(blog);
  if (ret == false) {
    printf("InsertBlog insert into database failed!\n");
    rsp.status = 500; //服务器内部错误
    return;
  }
  rsp.status = 200; //成功处理
  return;
}

//  ####################################  ② 删除博客表的业务逻辑
void DeleteBlog(const Request &req, Response &rsp) 
{
  // /blog/123  /blog/(\d+)  req.matches[0] = blog/123  req.matches[1] = 123
  int blog_id = std::stoi(req.matches[1]); //通过URL获取博客id,字符串转整形
  bool ret = table_blog->Delete(blog_id);
  if (ret == false) 
  {
    printf("DaleteBlog delete from database failed!\n");
    rsp.status = 500;
    return ;
  }
  rsp.status = 200;
  return;
}

// ##################################### ③ 修改博客表的业务逻辑
void UpdateBlog(const Request &req, Response &rsp) 
{
  int blog_id = std::stoi(req.matches[1]);
  Json::Value blog; //解析正文
  Json::Reader reader; //反序列化
  bool ret = reader.parse(req.body, blog);
  if (ret == false) //正文json格式出错
  {
    printf("UpdateBlog pares json failed!\n");
    rsp.status = 400;
    return ;
  }
  blog["id"] = blog_id;
  ret = table_blog->Update(blog);
  if (ret == false) 
  {
    printf("UpdateBlog update database failed!\n");
    rsp.status = 500;
    return;
  }
  rsp.status = 200;
  return;
}

//################################### ④ 获取全部博客表的业务逻辑
void GetAllBlog(const Request &req, Response &rsp) 
{
  //从数据库中取出博客列表数据
  Json::Value blogs;
  bool ret = table_blog -> GetAll(&blogs);
  if (ret == false) 
  {
    printf("GetAllBlog select from database failed!\n");
    rsp.status = 500;
    return ;
  }
  //将数据进行json序列化, 添加到rsp正文中
  Json::FastWriter writer;
  rsp.set_content(writer.write(blogs), "application/json");
  rsp.status = 200;
  return;
}

//################################### ⑥ 获取一条博客表的业务逻辑
void GetOneBlog(const Request &req, Response &rsp) 
{
  int blog_id = std::stoi(req.matches[1]);
  Json::Value blog;
  blog["id"] = blog_id;
  bool ret = table_blog -> GetOne(&blog);
  if (ret == false)
  {
    printf("GetAllBlog select from database failed!\n");
    rsp.status = 500;
    return ;
  }
  //将数据进行json序列化, 添加到rsp正文中
  Json::FastWriter writer;
  rsp.set_content(writer.write(blog), "application/json");
  rsp.status = 200;
  return;
}


//######################################## 具体对应的标签表业务处理  #########################################

// ######################################  ①插入标签表的业务逻辑
void InsertTag(const Request &req, Response &rsp) 
{
  Json::Reader reader;
  Json::Value tag;
  Json::FastWriter writer;
  Json::Value errmsg;

  bool ret = reader.parse(req.body, tag);//将请求正文进行json反序列化-因为正文就是json格式的博客信息
  if (ret == false) 
  {
    printf("InsertBlog pares tag json failed!\n");
    rsp.status = 400;
    errmsg["ok"] = false;
    errmsg["reason"] = "pares tag json failed!";
    rsp.set_content(writer.write(errmsg), "application/json"); //添加正文到rsp.body中
    return;
  }
  //将得到的博客信息插入到数据库
  ret = table_tag->Insert(tag);
  if (ret == false) 
  {
    printf("InsertBlog insert into database failed!\n");
    rsp.status = 500;
    return;
  }
  rsp.status = 200;
  return;
}

// ######################################  ② 删除标签表的业务逻辑
void DeleteTag(const Request &req, Response &rsp) 
{
  int tag_id = std::stoi(req.matches[1]); //获取ID
  bool ret = table_tag->Delete(tag_id); //操作数据库
  if (ret == false) 
  {
    printf("DaleteBlog delete from database failed!\n");
    rsp.status = 500;
    return ;
  }
  rsp.status = 200;
  return;
}

// ######################################  ③ 更新标签表的业务逻辑
void UpdateTag(const Request &req, Response &rsp) 
{
  int tag_id = std::stoi(req.matches[1]);
  Json::Value tag;
  Json::Reader reader;
  
  bool ret = reader.parse(req.body, tag);
  if (ret == false) 
  {
    printf("UpdateBlog pares json failed!\n");
    rsp.status = 400;
    return ;
  }
  tag["id"] = tag_id;
  ret = table_tag->Update(tag);
  if (ret == false) 
  {
    printf("UpdateBlog update database failed!\n");
    rsp.status = 500;
    return ;
  }
  rsp.status = 200;
  return;
}

// ################################### ⑥ 获取所有标签表的业务逻辑
void GetAllTag(const Request &req, Response &rsp) 
{
  Json::Value tags;
  bool ret = table_tag->GetAll(&tags);
  if (ret == false) 
  {
    printf("GetAllTag select from database failed!\n");
    rsp.status = 500;
    return ;
  }
  //将数据进行json序列化, 添加到rsp正文中
  Json::FastWriter writer;
  rsp.set_content(writer.write(tags), "application/json");
  rsp.status = 200;
  return;
}

// ################################### ⑥ 获取一条标签表的业务逻辑
void GetOneTag(const Request &req, Response &rsp) 
{
  int tag_id = std::stoi(req.matches[1]);
  Json::Value tag;
  tag["id"] = tag_id;
  bool ret = table_tag -> GetOne(&tag);
  if (ret == false) 
  {
    printf("GetAllTag select from database failed!\n");
    rsp.status = 500;
    return ;
  }
  //将数据进行json序列化, 添加到rsp正文中
  Json::FastWriter writer;
  rsp.set_content(writer.write(tag), "application/json");
  rsp.status = 200;
  return;
}

//###########################################  具体对应的业务处理  #########################################
int main() 
{
  MYSQL *mysql = blog_system::MysqlInit();
  //对数据库进行操作
  table_blog = new blog_system::TableBlog(mysql);
  table_tag= new blog_system::TableTag(mysql);

  //业务处理模块
  Server server;
  //设置相对根目录的目的: 当客户端请求静态文件资源时, httplib会直接根据路径读取文件数据进行响应
  //  /index.html --> ./www/index.html
  server.set_base_dir("./www"); //设置url中的资源相对根目录, 并且在请求 / 时候自动添加index.html
 
  //博客信息的增删查改
  server.Post("/blog", InsertBlog); //新增,使用 InsertBlog 函数进行业务处理
  //正则表达式: \d--匹配数字字符  +表示匹配前面的字符一次或多次  ()为了临时保存匹配的数据
  // /blog/(\d+) 表示匹配以/blog/开头, 后面跟了一个数字的字符串格式, 并且临时保存后边的数字
  server.Delete(R"(/blog/(\d+))", DeleteBlog); //删除,R"()" 取出括号中所有的特殊字符
  server.Put(R"(/blog/(\d+))", UpdateBlog); //改
  server.Get("/blog", GetAllBlog); //查
  server.Get(R"(/blog/(\d+))", GetOneBlog); //查

  //标签信息的增删查改
  server.Post("/tag", InsertTag); 
  server.Delete(R"(/tag/(\d+))", DeleteTag); 
  server.Put(R"(/tag/(\d+))", UpdateTag);
  server.Get("/tag", GetAllTag);
  server.Get(R"(/tag/(\d+))", GetOneTag);

  //网络通信 --- 搭建http服务器
  server.listen("0.0.0.0", 9000);
  return 0;
}

三、前端界面模块

前端页面模块使用一个博客页面的模板进行改写,基于html+css+js进行编写。
html:超文本标签语言(通过浏览器渲染成为一个界面);
css:样式语言(针对html控件使用一个特定的样式进行修饰,让html页面更加好看);
js:javascript,脚本语言(使html更加灵动,使界面动态起来)。

1、Vue.js库的使用

Vue.js类似于一个js库,使用起来非常方便,向静态页面中渲染对应的数据。
Vue使用过程:

  1. 加入Vue链接
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  1. 定义Vue对象
<script>
	var app = new Vue(
	{
		el:'#app', //
		data: //数据对象
			{
			message:'Hello',
			},
		methods:{}
	})
</script>

在一个html容器标签中加入id,id内容与vue对象el元素的值相同,才可以在对应的HTML容器中直接访问vue对象的数据。页面渲染的时候就会将vue对象中对应的字段的值渲染到指定位置。
在这里插入图片描述

2、jQuery ajax的使用

如何在前端,动态的从后台服务器获取到博客以及标签数据,放到vue对象的数据中如果想要获取数据,必须向后台发送获取请求(就要搭建http客户端) ------ ajax:可以实现一个客户端,向后台发送请求获取响应。

使用ajax,首先要加入ajax链接

    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>

使用方法如下:

methods:{
get_blog_list:function(){
	$.ajax({ //  a   jia   ke   si
		url: "/blog",  // 向后台服务器发送请求中的 path 路径
		type: "get",   // 请求中的请求方法
		context: this, // 设置请求成功后回调函数中的this指针--当前赋值的this是vue对象
		success: function (result) {  // success当请求成功时运行的函数
			this.blog_list = result; //成功后将响应的正文的json串赋值	
			}
		})
	}
}

3、编辑博客

editormd实现了前端的markdown编辑页面,具体流程:
1.加载样式

<link rel=“stylesheet” href=“editor/css/editormd.css”/>

2.加载is库

<script src=“editor/editormd.min.js”> </script>

3.在html中添加编辑页面

<div id=“test-editormd” v-show=“status=='blog edit”>
<textarea style=“display:none;”>
{{show blog.content}}</textarea>
</div>

4.在js中进行控制

testEditor = editormd({
   id:“test-editormd”,
   width :“90%”,
   height : 640,
   path : "editor/lib/
});

4、前端界面具体实现

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="description" content="">
    <meta name="keywords" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>项目 : 个人博客系统 Blog</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <link rel="icon" type="image/png" href="assets/i/favicon.png">
    <meta name="mobile-web-app-capable" content="yes">
    <link rel="icon" sizes="192x192" href="assets/i/app-icon72x72@2x.png">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="Amaze UI" />
    <link rel="apple-touch-icon-precomposed" href="assets/i/app-icon72x72@2x.png">
    <meta name="msapplication-TileImage" content="assets/i/app-icon72x72@2x.png">
    <meta name="msapplication-TileColor" content="#0e90d2">
    <link rel="stylesheet" href="assets/css/amazeui.min.css">
    <link rel="stylesheet" href="assets/css/app.css">
    <link rel="stylesheet" href="editor/css/editormd.css" />
    <link rel="stylesheet" href="editor/css/editormd.preview.css" />
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
</head>


<body id="blog">
    <div id="app">
        <!-- 自定义的容器, 为了能在容器中访问vue对象, 所以id等于vue对象的el字段值-->

        <!-- nav start -->
        <hr>
        <!-- nav end -->
        <!-- content srart -->
        <div class="am-g am-g-fixed blog-fixed">
            <div class="am-u-md-8 am-u-sm-12" v-show="status=='blog_list'" v-cloak>

                <article class="am-g blog-entry-article" v-for="blog in blog_list">
                    <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-img">
                        <img src="assets/i/f10.jpg" alt="" class="am-u-sm-12">
                    </div>
                    <div class="am-u-lg-6 am-u-md-12 am-u-sm-12 blog-entry-text">
                        <span><a class="blog-color" v-if="tag_list.length > 0">{{get_tagname_by_tag_id(blog.tag_id)}} &nbsp;</a></span>
                        <span> @{{author}} &nbsp;</span>
                        <span>{{blog.ctime}}</span>
                        <h1><a v-on:click="blog_view(blog.id)">{{blog.title}}</a></h1>
                        <span v-show="status=='blog_admin'">
                            <button type="button" class="am-btn am-btn-success" v-on:click="blog_edit(blog.id)">编辑</button>
                            <button type="button" class="am-btn am-btn-danger" v-on:click="blog_delete(blog.id)">删除</button>
                        </span>
                    </div>
                </article>


                <ul class="am-pagination">
                    <li class="am-pagination-prev"><a v-on:click="into_admin_status">&laquo; 管理</a></li>
                    <li class="am-pagination-next"><a v-on:click="new_blog">新增 &raquo;</a></li>
                </ul>
            </div>

            <div class="am-u-md-12 am-u-sm-12" v-show="status=='blog_edit'" v-cloak>//列表或管理状态展示博客
                <div class="am-input-group">
                    <span class="am-input-group-label">博客标题</span>
                    <input type="text" class="am-form-field" v-model="show_blog.title">
                </div>
                <div id="test-editormd">
                    <textarea style="display:none;">{{show_blog.content}}</textarea>
                </div>
                <span>
                    <button type="button" class="am-btn am-btn-success" v-on:click="blog_update()">提交</button>
                </span>
            </div>

            <div class="am-u-md-12 am-u-sm-12" v-show="status=='blog_show'" v-cloak>
                <div class="am-input-group">
                    <span class="am-input-group-label">博客标题</span>
                    <input type="text" class="am-form-field" v-model="show_blog.title">
                </div>
                <div id="show-editormd">
                    <textarea style="display:none;">{{show_blog.content}}</textarea>
                </div>
                <span>
                    <button type="button" class="am-btn am-btn-success" v-on:click="blog_quit()">返回</button>
                </span>
            </div>

            <div class="am-u-md-12 am-u-sm-12" v-show="status=='blog_new'" v-cloak>
                <div class="am-input-group">
                    <span class="am-input-group-label">博客标题</span>
                    <input type="text" class="am-form-field" v-model="show_blog.title">// v-model双向绑定
                </div>
                <div id="new-editormd">
                    <textarea style="display:none;"></textarea>
                </div>
                <span>
                    <button type="button" class="am-btn am-btn-success" v-on:click="blog_insert()">提交</button>
                </span>
            </div>



            <div class="am-u-md-4 am-u-sm-12 blog-sidebar" v-show="status=='blog_list'" v-cloak>
                <div class="blog-sidebar-widget blog-bor">
                    <h2 class="blog-text-center blog-title"><span>About Me</span></h2>
                    <img src="assets/i/myp.jpg" alt="about me" class="blog-entry-img">

                    <p>Yjk</p>
                    <p>你好,欢迎来访问我的博客系统</p>
                </div>

                <div class="blog-clear-margin blog-sidebar-widget blog-bor am-g ">
                    <h2 class="blog-title"><span>TAG cloud</span></h2>
                    <div class="am-u-sm-12 blog-clear-padding">
                        // 通过for循环,循环渲染
                        <a href="" class="blog-tag" v-for="tag in tag_list">{{tag.name}}</a>  
                    </div>
                </div>
            </div>
        </div>
        <!-- content end -->


        <footer class="blog-footer">
            <div class="am-g am-g-fixed blog-fixed am-u-sm-centered blog-footer-padding">

            </div>
            <div class="blog-text-center">© 2023 Author By {{author}} </div>
        </footer>

    </div> <!--自定义容器的结尾-->>


    <script src="assets/js/jquery.min.js"></script>
    <script src="assets/js/amazeui.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="editor/editormd.min.js"></script>
    <script src="editor/lib/marked.min.js"></script>
    <script src="editor/lib/prettify.min.js"></script>
    <script src="editor/lib/raphael.min.js"></script>
    <script src="editor/lib/underscore.min.js"></script>
    <script src="editor/lib/sequence-diagram.min.js"></script>
    <script src="editor/lib/flowchart.min.js"></script>
    <script src="editor/lib/jquery.flowchart.min.js"></script>
    <script src="editor/editormd.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {     //data: 数据对象
                author: 'zy',
                status: 'blog_list',  // blog_list列表展示  blog_edit 编辑  blog_new 新增 blog_admin 管理 blog_show 展示
                tag_list: [],  //标签信息 id name
                blog_list: [],  //博客信息 id tag_id title content ctime
                show_blog: { id: null, tag_id: null, title: null, content: null, ctime: null } //要编辑的博客数据
                },
            methods: { // methods,vue里面的函数
                get_blog_list: function () {
                    //发送获取全部博客数据的请求
                    $.ajax({
                        url: "/blog",  //请求中的path路径
                        type: "get",   //请求中的请求方法,get/post
                        context: this, //设置请求成功后回调函数中的this指针--当前赋值的this是vue对象,为了能够在成功回调函数中访问vue对象的数据
                        success: function (result) {
                            this.blog_list = result; //成功后将响应的正文的json串赋值
                        }
                    })
                },
                get_tag_list: function () {
                    //发送获取全部标签数据的请求
                    $.ajax({
                        url: "/tag",
                        type: "get",
                        context: this,
                        success: function (result) { //result就是响应数据的正文
                            this.tag_list = result; //成功后将响应的正文的json串赋值给vue对象中的tag_list对象
                        }
                    })
                },
                get_tagname_by_tag_id: function (tag_id) {
                    for (idx in this.tag_list) {
                        if (this.tag_list[idx].id == tag_id) 
                        { return this.tag_list[idx].name;}
                    }
                    return "未知";
                },
                into_admin_status: function () {
                    //将当前页面状态置为管理状态, 一定要使用this, 否则会被认为定义局部变量
                    this.status = 'blog_admin';
                },
                blog_delete: function (blog_id) {
                    //发送删除博客请求
                    $.ajax({
                        url: "/blog/" + blog_id,
                        type: "delete",
                        context: this,
                        success: function (result) {
                            //重新获取博客数据, 更新页面
                            this.get_blog_list();
                        }
                    })
                },
                blog_edit: function (blog_id) {
                    //发送获取一个博客内容的请求
                    $.ajax({
                        url: "/blog/" + blog_id,
                        type: "get",
                        context: this,
                        success: function (result) {
                            this.show_blog = result;
                            this.status = 'blog_edit';
                            //显示一篇博客的编辑页面, 其中具有博客的内容
                            testEditor = editormd({
                                id: "test-editormd",
                                width: "100%",
                                height: 640,
                                path: "editor/lib/"
                            });
                        }
                    })
                },
                blog_update: function () {
                    this.show_blog.content = testEditor.getMarkdown();
                    $.ajax({
                        url: "/blog/" + this.show_blog.id,
                        type: "put",
                        context: this,
                        data: JSON.stringify(this.show_blog),
                        success: function (result) {
                            this.get_blog_list();// 重新更新博客信息
                            this.status = 'blog_list';
                        }
                    })
                },
                blog_view: function (blog_id) {
                    $.ajax({
                        url: "/blog/" + blog_id,  
                        type: "get",   
                        context: this, 
                        success: function (result) {
                            this.show_blog = result;
                            this.status = 'blog_show';
                            testEditormdView = editormd.markdownToHTML("show-editormd", {
                                markdown: this.show_blog.content,
                                htmlDecode: "style,script,iframe",  
                                tocm: true,    
                                emoji: true,
                                taskList: true,
                                tex: true,  
                                flowChart: true,  
                                sequenceDiagram: true,  
                            });
                        }
                    })
                },
                new_blog: function () {
                    this.status = "blog_new";
                    //显示一篇博客的编辑页面, 其中具有博客的内容
                    newEditor = editormd({
                        id: "new-editormd",
                        width: "100%",
                        height: 640,
                        path: "editor/lib/"
                    });
                },
                blog_insert: function () {
                    this.show_blog.content = newEditor.getMarkdown();
                    var new_blog_info = {
                        tag_id: 1,
                        title: this.show_blog.title,
                        content: this.show_blog.content
                    }
                    $.ajax({
                        url: "/blog",
                        type: "post",
                        context: this,
                        data: JSON.stringify(new_blog_info),
                        success: function (result) {
                            this.get_blog_list();
                            this.status = 'blog_list';
                        }
                    })
                },
                blog_quit: function () {
                    this.get_blog_list();
                    this.status = 'blog_list';
                }
            }

        })
        //在vue对象定义之外, 要调用函数
        //浏览器获取到界面之后, 就会调用这两个函数, 向后台发送请求
        app.get_tag_list();
        app.get_blog_list();
    </script>
</body>
</html>

总结

至此,完成了一个简单的博客系统的全部内容,后续还会继续细化更改。感谢阅读。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值