项目: 博客系统 (设计思路 + 代码实现)

在学习了之前的许多知识之后, 这次我们来进行一个实战项目, 做一个自己的博客系统. 话不多说, 直接开始吧~



设计思路

项目名称: 博客系统

开发环境: Centos7.6 -vim/g++/makefile/git/gdb

使用技术: MVC框架, MySQL数据库, httplib, jsoncpp, vue.js, ajax

项目功能: 实现一个web服务器, 能够提供用户通过浏览器访问服务器, 实现博客的展示, 以及博客的增删查改管理

框架设计: 实现前端的页面, 后台的服务, 数据的管理 — 使用MVC框架进行实现


MVC框架

前端界面模块需要获取数据进行展示, 向服务控制模块发送请求, 服务控制模块从数据管理模块获取到数据, 组织后给前端

是一种服务, 数据, 页面分离的开发模式

M — model — 数据管理模块
基于MySQL数据库进行数据管理

C — controller — 服务控制管理模块(业务逻辑模块)
搭建http服务器针对不同的请求提供不同的服务(博客页面的获取以及博客数据的增删查改)

V — 前端界面模块
基于html+css+js实现前端界面的展示

在这里插入图片描述



详细设计

一. 数据管理模块

使用MySQL数据库进行数据管理

客户端将对应的语句发送给服务器, 服务器进行语句解析, 之后完成语句中各个元素所表示的操作.
我们通过MySQL提供的接口完成客户端的搭建.

数据库实际上存储介质也是文件, 但是相较于纯粹的文件存储多了数据管理 (更加安全, 查询更加高效)

外界客户端通过SQL语句完成对数据库中数据的增删查改

1. MySQL数据库

注意事项 :

  1. MySQL数据库对语句中大小写不敏感
  2. 库名, 表名, 表中字段不能使用关键字
  3. 每条语句最后都要以英文分号结尾

MySQL安装: CentOS 7 通过 yum 安装 MariaDB


库的操作

建库:
create database if not exists db_name;
如果db_name数据库不存在则创建

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

show databases;  显示所有数据库
use db_name; 选择要操作的数据库

表的操作

建表:
create table if not exists tb_name(字段信息);
如果tb_nname表不存在则创建

删表: 
drop table tb_name;
删除tb_name表

show tables; 显示数据库所有表
desc ta_name;  描述tb_name表的字段(属性)信息

数据的操作

增: insert tbname value(fields_value...);
删: delete from tbname where condition..;
查: select * from tbname;  
    select fields_name... from tbname;
改: update tbname set fields_name=fields_value  where condition;

2. 标签表和博客表的设计

1. tag标签表: 标签id, 标签名称

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

2. 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 '博客创建时间'
);

3. db.sql文件

创建一个db.sql文件, 将SQL语句写入其中.
在打开数据库时, mysql -uroot -p < db.sql 可以将其导入mysql中自动执行
如果操作失误数据丢失, 我们可以省去再敲一遍的时间

drop database if exists db_blog;

create database if not exists db_blog;
use db_blog;

drop table if exists tb_tag;

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

drop table if exists tb_blog;

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());


4. MySQL提供的接口

在项目中我们要通过数据管理模块完成对数据库的访问, 意味着我们要自己完成MySQL客户端. 我们这里通过MySQL提供的接口进行搭建.

1. 初始化mysql句柄

MYSQL* mysql_init(MYSQL* mysql)
参数通常为NULL, 表示要动态分配一块空间进行初始化

2. 连接mysql服务器

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

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

3. 设置客户端字符编码集

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

mysql: mysql句柄
csname: 字符编码集名称, 通常置为utf8
返回值: 成功返回0, 失败返回非0

4. 选择要使用的数据库

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

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

5. 表以及其中数据的各项操作(执行语句)

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

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

增删改三种操作只要执行语句成功即可, 但是查询不一样, 在语句执行成功之后还要获取结果

6. 从远程服务器获取结果集

1. 将查询结果获取到本地
MYSQL_RES* mysql_store_result(MYSQL *mysql)
mysql: mysql句柄

2. 获取结果集中结果的条数
uint64_t mysql_num_rows(MYSQL_RES *result)

3. 获取结果集中的结果的列数
unsigned int mysql_num_fields(MYSQL_RES *result)

4. 遍历结果集, 每次获取一条数据 (不会重复, 内部有读取位置维护)
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result)

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

MYSQL_ROW是一个二级指针char**
MYSQL_ROW row; row表示一行数据
row[0]表示第0列数据, row[1]表示第1列数据
注意: 结果集中的结果都是字符串, 与数据原类型无关

7. 关闭数据库释放资源

void mysql_close(MYSQL *mysql)

8. 获取mysql接口调用失败原因

const char* mysql_error(MYSQL *mysql)

5. Json数据类型

在数据管理模块, 我们要考虑数据对象作为参数的传递

我们可能要组织很多的数据对象, 但是参数过多会降低效率, 且结果过于复杂

考虑到后面网络通信, 我们这里选择Json数据序列化方式

我们要安装jsoncpp库, 安装之前我们首先要升级一下gcc
升级过程 : 升级高版本gcc

升级完成后, 执行如下命令安装jsoncpp库

yum -y install epel-release
yum install jsoncpp-devel

jsoncpp库的认识: 将多个数据对象序列化为json格式的字符串, 或者将json格式的字符串反序列化得到各个数据对象

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

在这里插入图片描述

下面给一个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);
  cout << str  << endl;  
  
  Json::Value value1;
  Json::Reader reader;

  reader.parse(str, value1);

  cout << value1["name"].asString() << endl;
  cout << value1["age"].asInt() << endl;
  cout << value1["score"].asFloat() << endl;


  return 0;
}


6. 代码实现数据管理模块

创建db.hpp文件, 代码如下

#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 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) {
      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;
    }

    //选择数据库
    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) {
        //从blog中取出博客信息, 组织sql语句 
#define INSERT_BLOG "insert tb_blog values(null, '%d', '%s', '%s', now());"
        //因为博客正文长度不懂, 有可能会很长, 因此如果直接定义固定长度tmp, 有可能访问越界
        int len = blog["content"].asString().size() + 4096;
        //string sql;
        //sql.resize(len);
        char* sql = (char*)malloc(len);
        sprintf(sql, INSERT_BLOG, blog["tag_id"].asInt(), blog["title"].asCString(), 
            blog["content"].asCString());
        bool ret = MysqlQuery(_mysql, sql);

        free(sql);
        return ret;
      }


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


      bool Update(Json::Value& blog) {
        //从blog中取出博客信息, 组织sql语句,更新数据库中的数据
        //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* sql = (char*)malloc(len);
        sprintf(sql, UPDATE_BLOG, blog["tag_id"].asInt(), blog["title"].asCString(), 
            blog["content"].asCString(), blog["id"].asInt());

        bool ret = MysqlQuery(_mysql, sql);
        free(sql);
        return ret;
      }


      bool GetAll(Json::Value* blogs) {
        //通过blog返回所有的博客信息(通常是列表展示, 不返回正文)

        //执行查询语句
#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;
          blog["id"] = stoi(row[0]);
          blog["tag_id"] = stoi(row[1]);
          blog["title"] = row[2];
          blog["ctime"] = row[3];

          blogs->append(blog); //添加json数组元素
        }

        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;"
        char sql[1024] = {0};
        sprintf(sql, GETONE_BLOG, (*blog)["id"].asInt());

        g_mutex.lock();
        bool ret = MysqlQuery(_mysql, sql);
        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) {
          printf("get one blog result error\n");
          mysql_free_result(res);
          return false;
        }
        MYSQL_ROW row = mysql_fetch_row(res);
        (*blog)["tag_id"] = 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');"
        char sql[1024] = {0};
        sprintf(sql, INSERT_TAG, tag["name"].asCString());
        return MysqlQuery(_mysql, sql);
      }

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

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

      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"] = 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 sql[1024] = {0};
        sprintf(sql, GETONE_TAG, (*tag)["id"].asInt());

        g_mutex.lock();
        bool ret = MysqlQuery(_mysql, sql);
        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句柄, 那么就有可能出现来没来得及保存结果集, 下一个对象就要执行查询语句的情况, 导致数据混乱.

这里我们通过互斥锁解决问题, 在执行语句前加锁, 失败或者成功保存结果集后解锁, 保证两步操作是原子操作



二. 业务逻辑模块

1. 网络通信功能

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

http服务器: 采用http协议实现的服务, http协议只是一个应用层协议 (只是一种数据格式, 采用了http协议进行通信的进程可以称为http服务进程)
http是应用层协议, 在传输层使用的是tcp协议
http服务器就是一个tcp服务器, 只是在应用层, 完成http协议格式的请求与响应的解析以及针对请求提供服务.

如何搭建http服务器
1. 搭建tcp服务端
2. 等待客户端请求, 解析请求, 得到http协议格式中的各个要素
(请求方法, url(path资源路径以及查询字符串), 头部字段, 正文)

3. 根据客户端的需要, 完成业务处理, 组织http响应数据进行响应

http服务器我们不从头开始搭建, 而是借助httplib库进行搭建


2. httplib库

首先需要拿到httplib的代码, 可以去github上搜索 "cpp-httplib"关键字即可

也可以执行下面指令直接拿到

git clone https://github.com/yhirose/cpp-httplib.git

下载好之后我们只需要httplib.h这个头文件, 把他复制一份到当前目录, 我们之后写代码包含这个头文件

注意:

  1. httplib使用时要使用高版本的gcc进行编译, 否则运行时会崩溃
  2. 如果使用的是云服务器, 要去控制台开通端口
  3. 如果使用是虚拟机, 要关闭虚拟机的防火墙

gcc升级高版本
由于云服务器各家的不一样, 这里不做统一描述.
虚拟机关闭防火墙

httplib中主要的类 : Server, Client, Request, Response

基本使用:

  1. 实例化一个Server对象
  2. 注册请求-处理路由 (给对应的请求设置对应的处理函数)
  3. 开始服务器监听

httplib处理流程
在这里插入图片描述


3. 网络通信接口的设计

采用restful风格的接口设计
基于http通信, http正文只用xml或者json对数据进行序列化和反序列化
动作 : GET – 获取, POST – 新增, PUT – 更新, DELETE – 删除

在这里插入图片描述


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[0] = 123
  int blog_id = std::stoi(req.matches[1]);
  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) {
    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]);
  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); 
  //正则表达式: \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;
}

5. Makefile文件

blog_system:main.cpp db.hpp
	g++ -std=c++11 $^ -o $@ -L/usr/lib64/mysql -lmysqlclient -ljsoncpp -lpthread

6. 使用Postman测试

下载安装Postman, 进行业务逻辑模块+数据管理模块的测试

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



三. 前端页面模块

由于博主前端也不是很熟悉, 所以这里不从0开始编写, 而是下载了一个博客页面的模板进行改写. 实现我们自己想要的功能.

在前端, 我们使用 前端三剑客: html+css+js 进行编写

html: 超文本标签语言(通过浏览器渲染成为一个页面)
css: 样式语言 (针对html空间使用一个特定的样式进行修饰, 让页面更好看)
js-javascript: 脚本语言(使html更加灵动, 让页面动态起来)


1. Vue.js库的使用

Vue.js 类似于一个js库, 使用起来十分方便

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

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

定义vue对象

<script>
        var app = new Vue({
            el: '#app',
            data: {     //data: 数据对象
                author: 'YJK',
                tag_list: [],  //标签信息
                blog_list: [],  //博客信息
            },
            methods: {
				
			}
            
        })
        </script>

el中的数据要和容器的id一致才能使用vue中定义的数据和方法
data就是vue对象中定义的变量
methods就是vue对象中定义的函数(方法)

在一个html容器标签中加入id, id的内容与vue对象的el元素相同, 才可以在对应的html容器中直接访问vue对象的数据


2. jQuery ajax的使用

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

可以去网上查询链接, 这里提供一个

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

在页面中想要获取后台的数据, 我们使用ajax进行获取

使用示例如下

$.ajax({
	url: "/blog",  //请求中的path路径
	type: "get",   //请求中的请求方法
	context: this, //设置请求成功后回调函数中的this指针--当前赋值的this是vue对象
	success: function (result) {
		this.blog_list = result; //成功后将响应的正文的json串赋值	
	}
})

3. 编辑页面

editormd 实现了前端的markdown页面

1. 加载样式

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

2. 加载js库

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

3. 在html中添加编辑页面

< div d=“test-editormd” -show=“status==‘blog edit’”>
< texta rea style=" display:none;"> {show blog.content}< /textarea>
< /div>

4. 在js中进行控制

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


4. 代码实现前端页面模块

下面是一个index.html文件

<!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_tagid(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">
                </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>
                    <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">
                        <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">© 2021 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: 'YJK',
                status: 'blog_list',  // blog_list列表展示  blog_edit 编辑  blog_new 新增 blog_admin 管理 blog_show 展示
                tag_list: [],  //标签信息
                blog_list: [],  //博客信息
                show_blog: { id: null, tag_id: null, title: null, content: null, ctime: null } //要编辑的博客数据
            },
            methods: {
                get_blog_list: function () {
                    //发送获取全部博客数据的请求
                    $.ajax({
                        url: "/blog",  //请求中的path路径
                        type: "get",   //请求中的请求方法
                        context: this, //设置请求成功后回调函数中的this指针--当前赋值的this是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_tagid: 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>

对于前端页面模块, 我也不是很熟悉, 很多都是用到的知识去查资料
写的不好大家见谅, 页面代码后续会不断更新的.
大家一起学习一起进步~

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇&璃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值