博客系统

时间:2021-2-20 作者:admin

博客系统

文章目录

这篇博客主要是针对最近写的实战项目—博客系统进行一个详细的总结,在开发项目的过程中遇到了很多的问题以及解决的方法,通过这篇博客也是对自己所学知识的一个总结,以下是此项目的具体内容:

项目名称:博客系统
项目功能:实现一个web服务器,能够提供用户通过浏览器访问服务器,实现博客的展示以及对博客的增删查改的操作。
项目开发环境:VS Code 、阿里云服务器、git、g++、makefile
项目开发所用技术:MVC框架、ajax、jsoncpp、httplib、vue.js、mysql
框架设计:实现前端的界面,后台的服务以及数据的管理,其实运用的框架是MVC框架,MVC框架中,M代表业务模型,V代表用户界面,C代表控制器,简单的来说,就是前端界面将后台数据在界面进行展示,用户在前端界面进行操作向服务控制模块发出请求,服务控制模块进行响应,从数据管理模块中获取相应的数据然后展示给前端界面,是一种页面、服务、数据分离的开发模式。MVC框架
博客系统

  • model—数据管理模块:使用mysql数据库进行数据的增删查改等操作
  • controller–服务控制模块:搭建http服务器针对不同的请求提供不同的服务
    URL:统一资源定位符,定位网络中某台主机的某个资源或服务,一般格式为http://username:password@ip:port/path?query_string#ch
  • view—前端界面模块:基于html+css+js实现前端界面的展示

下面开始具体的详细设计,首先是从最底层开始进行开发,对数据管理模块进行设计,数据管理模块主要是利用mysql对数据库进行各种数据的操作

1.数据管理模块

在编写代码之前我们先得准备好环境的配置,首先要下载mysql数据库,详细步骤MySQL环境的配置

SQL结构化查询语言基本操作:

操作 数据库 数据表
建立 create database if not exists db_name create table if not exists tb_name
删除 drop database db_name drop table tb_name
展示 show databases show tables
调用 use db_name —-

以上是SQL结构化查询语言的简单操作,数据表的基本操作就不说了,都是一些很基础的,如有不懂的SQL语句请点SQL详解

搞懂了mysql数据库的基本操作之后,下面开始进行项目的第一个模块- – – 数据管理模块,设计数据库,根据我们所要设计的博客系统分析,我们要建立一个博客数据库—db_blog1,在博客数据库中建立两张表,博客标签表tb_tag以及博客信息表tb_blog,打开vs code,新建project文件夹,在文件夹下创建db.sql,进行建库与建表操作:

create database if not exists db_blog1;//创建数据库
use db_blog1;//调用数据库
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');
insert tb_blog values(
    null,1,'这是一个C++博客','##C++是面向对象的高级语言',now()),
    (null,1,'这是一个JAVA博客','##Java是最好的语言',now()),
    (null,1,'这是一个Linux博客','##Linux是最好的开发语言',now());

  • db.sql编写完成后,在终端中执行 “mysql -uroot -p < db.sql “语句,该语句的作用是导入db.sql文件并执行,执行完该语句后查看数据库就会发现建好的库以及数据表,db.sql文件在这里的作用就是将数据库的操作全部写在一个文件中,便于查看以及对数据库的操作, 打个比方,如果我们不小心将数据库给删除了,那么我们可以在执行上述语句导入,而不是在终端中一行一行的敲,大大节省了工作时间。

上面的工作已经实现了通过mysql客户端对服务器进行访问,但是在实际的项目中我们必须利用数据管理模块来对数据库进行操作,意味着我们自己必须在数据管理模块实现自己mysql客户端完成所需功能,mysql提供给我们的开发接口是C语言接口。
下面我将这些接口以及他们的参数功能用表格的形式展现出来:

数据库接口

功能 语句 参数含义
初始化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 cilent_flag); mysql: 数据库操作句柄 host:要连接的mysql服务器ip, user: 用户名 passwd:密码 , db: 调用的数据库名, port:端口号,为0则默认表示3306端口 , socket: 指定socket或管道,通常置为NULL, cilent_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:数据库名称,通常设为utf8,返回值:成功返回0,失败返回非0
执行语句 int mysql_query(MYSQL mysql,const char stmt_str); 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)–释放结果集空间 mysql:数据库操作句柄,result:获取的结果集注意:MYSQL_ROW是一个char*-*,row[0]表示第0列,row[1]表示第一列, 类型结果集中的数据都是字符集,与原类型无关
获取错误信息 char* mysql_error(MYSQL* mysql) mysql:数据库操作句柄
关闭数据库释放资源 void mysql_close(MYSQL* mysql) mysql:数据库操作句柄

数据管理模块:与数据库交互访问数据库的内容,对内与数据库交互,对外提供接口获取各项数据,封装一个数据库接口的访问类,一个类实现对一个表的访问。
在数据参数传递过程中,考虑到后面的网络部分,我们引入一个新的知识—–json,json是一种网络通信的数据序列化方式,在参数传递时各种对象作为参数传递,效率低,增加了函数栈帧的开销,会大大耗费资源,影响用户体验,所以采用json序列化方式。

jsoncpp库的认识

将多个数据对象序列化为json格式的字符串,或者将json格式的字符串反序列化为各个数据对象。在安装json库之前首先确保gcc出于高版本,否则会下载失败
yum -y install epel-release
yum install jsoncpp-devel
gcc升级版本

  • json的数据交换类
    Json::Value
  • json的序列化
    Json::Writer:实现序列化,将Json::Value对象中的数据组织为一个json字符串数据
  • json的反序列化
    Json::Reader:实现反序列化,将json字符串组织成为Json::Value对象
    下面是对于json的使用测试用例,为了更加了解json的语法,代码如下:
#include<iostream>
#include<string>
#include<json/json.h>

int main()
{
    Json::Value value;
    value["name"]="张三";
    value["age"]=18;
    value["score"]=88.88;
    //json的序列化
    Json::StyledWriter writer;
    std::string str=writer.write(value);
    std::cout<<str<<std::endl;
    
    //json的反序列化
    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;
}

博客系统

运行结果:
博客系统

数据的插入流程:

博客系统

#include<iostream>
#include<json/json.h>
#include<mysql/mysql.h>
#include<mutex>
#include<string>
#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PSWD "******"
#define MYSQL_DB "db_blog1"
namespace blog_system
{
    static std::mutex g_mutex;
    //向外提供接口返回初始化的mysql句柄(连接服务器,选择数据库,设置字符集)
    MYSQL *MysqlInit()
    {
        MYSQL *mysql;
        //1.初始化数据库
        mysql=mysql_init(NULL);
        if(mysql==NULL)
        {
            printf("mysql init error\n");
            return NULL;
        }
        //2.连接服务器
        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;
        }

        //3.创建客户端字符集
        if(mysql_set_character_set(mysql,"utf8")!=0)
        {
            printf("set client character error:%s\n",mysql_error(mysql));
            mysql_close(mysql);
            return NULL;
        }

        //4.选择数据库
        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);
        }
        return;
    }
    //执行语句的共有接口
    bool MysqlQuery(MYSQL *mysql,const char *sql)
    {
        int ret=mysql_query(mysql,sql);
        if(ret!=0)
        {
            printf("mysql query:[%s] error:%s\n",sql,mysql_error(mysql));
            return false;
        }
        return true;
    }
    class TableBlog
    {
        public:
            TableBlog(MYSQL *mysql)
             :_mysql(mysql)
              {
              }

           //从blog中取出博客信息,组织sql语句,将数据插入数据库
            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语句,更新数据库的数据
            //id  tag_id   title   content ctime
            bool Update(Json::Value &blog)
            {
#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(),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;"
                char tmp[1024]={0};
                sprintf(tmp,GETALL_BLOG);
                //执行查询语句
                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);
                for(int i=0;i<row_num;i++)
                {
                    MYSQL_ROW row=mysql_fetch_row(res);//自动获取下一行的数据
                    Json::Value blog;
                    blog["id"]=std::stoi(row[0]);
                    blog["tag_id"]=std::stoi(row[1]);
                    blog["title"]=row[2];
                    blog["ctime"]=row[3];
                    blogs->append(blog);//向博客集合中添加新的数据

                }
                mysql_free_result(res);
                return ret;
            }

            //返回单个博客信息
            bool GetOne(Json::Value *blog)
            {
#define GETONE_BLOG "select tag_id,title,content,ctime from tb_blog where id=%d;"
                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 one blog result failed:%s\n",mysql_error(_mysql));
                    return false;
                }
                //遍历结果集
                int row_num=mysql_num_rows(res);
                if(row_num!=1)
                {
                    printf("get one blog result error:%s\n",mysql_error(_mysql));
                    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;

            }
            ~TableBlog()
            {
                
            }
        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 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 error:%s\n",mysql_error(_mysql));
                return false;
            }
            //遍历结果集
            int num_row=mysql_num_rows(res);
            for(int i=0;i<num_row;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 error:%s\n",mysql_error(_mysql));
                    return false;
                }
                //遍历结果集
                int row_num=mysql_num_rows(res);
                if(row_num!=1)
                {
                    printf("get one tag result error:%s\n",mysql_error(_mysql));
                    return false;
                }
                MYSQL_ROW row=mysql_fetch_row(res);
                (*tag)["name"]=row[0];
                mysql_free_result(res);
                return true;
            }
            ~TableTag()
            {
                    
            }
        private:
            MYSQL *_mysql;
    };
}

以上是db.hpp的完整代码,需要注意的是我们的服务器对应的是多客户端,多个客户端可能同时发起查询操作,如果每个操作创建一个对象,产生一个句柄,多个操作使用同一个句柄,会造成很多问题,而且查询和保存结果集操作并不是原子操作,可能在客户端访问时由于上一个客户端还没来得及保存结果集下一个客户端进行查询导致发生紊乱,造成结果保存失败,得不到正确的结果,所以我们采用了加锁的方法,将查询操作和保存结果集放在锁里面,构成一个原子操作,这样就能确保不会发生上面的问题了。

2.业务逻辑模块(根据客户端提出的请求进行业务处理的功能)

1.网络通信功能(提供客户端与服务器的网络通信功能)

  • 后台搭建http服务器,客户端就是浏览器
  • http服务器:就是采用http协议实现的服务,http协议是一种应用层协议,说白了就是一种数据格式,采用http协议通信的进程可以被称之为http服务进程,http服务器也就是tcp服务器,只不过是在应用层,主要是对完成http协议格式的请求与响应的解析以及针对请求提供服务。
  • 如何搭建http服务器:1.搭建tcp服务端 2.等待客户端请求,解析请求,得到http协议格式中的各个要素(请求方法,url(path资源路径以及查询字符串),头部字段,正文)3.根据客户端的请求,完成业务处理,组织http响应数据进行响应。http服务器我们并不自己搭建,而是通过httplib来进行搭建,因为这样效率更高,更加的方便。httplib的下载
  • httplib的基本使用:1.实例化server对象;2.对server注册请求-业务处理路由(不同的请求不同的处理函数);3.开始监听处理;注意:httplib在使用时必须使用高版本的gcc,否则会运行崩溃,如果服务器使用的是虚拟机,别忘记关闭虚拟机的防火墙sudo systemctl stop firewalld;如果使用的是云服务器,记得开通端口。
  • httplib处理流程
    1.程序员编写不同请求的处理函数,void(*hander)(Request &,Response&)
    2.实例化server对象,对象中包含两个重要部分,请求与处理函数映射表,线程池。
    3.根据不同的请求及业务处理,在Server对象中建立新的映射关系,Server.Get() /Server.Put()等
    4.搭建tcp服务器开始监听
    5.有新连接到来,将新连接送入线程池
    6.线程池中进行处理
  • 具体线程池的实现流程:
    博客系统

2.业务处理功能(针对不同的请求提供不同的服务)

  • 采用Restful风格的接口设计:网络通信接口设计风格—基于HTTP通信,http正文采用xml或者json对数据进行序列化或者反序列化。
  • 特点:GET: 获取、 POST: 新增、 PUT:更新、 DELETE:删除

添加博客信息 :

POST/blog HTTP1.1 ————– ———————–HTTP1.1 200 OK
ContentLength: ————————————————ContentLength:0
ContentType:application/json ————————–HTTP1.1 500 Server error
———————————————————————{“OK”:false,“reason”:“博客信息数据库插入失败”}
{”tag_id“:1,“title”:“博客标题”,“content”:“博客正文”};

获取博客列表:

GET/blogHTTP1.1 ————– ———————–HTTP1.1 200 OK
{{id:1,tag_id=1,title、content、ctime},{id: tag_id,title,content,ctime}};

获取指定博客信息:

GET/blog/blog_id HTTP1.1 ————– ———————–HTTP1.1 200 OK
{id:1,tag_id=1,title、content、ctime};

删除博客信息:

DELETE/blog/blog_idHTTP1.1 ————– ———————–HTTP1.1 200 OK

修改博客信息:

PUT/blog/blog_id HTTP1.1 ————– ———————–HTTP1.1 200 OK
ContentLength: ————————————————ContentLength:0
ContentType:application/json ————————–HTTP1.1 500 Server error
———————————————————————{“OK”:false,“reason”:“博客信息数据库插入失败”}
{”tag_id“:1,“title”:“博客标题”,“content”:“博客正文”};

标签表的接口与上面类似。

POST/tag HTTP1.1
{“name”:“C++”}
.
DELETE/tag/tag_idHTTP1.1
.
PUT/blog/blog_idHTTP1.1
{“name”:“C++”}
.
GET/blogHTTP1.1
GET/blog/blog_idHTTP1.1

下面开始具体代码实现:

#include "db.hpp"
#include "httplib.h"
using namespace httplib;
void test()
{

    MYSQL *mysql=blog_system::MysqlInit();
    blog_system::TableBlog table_blog(mysql);
    Json::Value blog;
    blog["id"]=3;
    // blog["title"]="JAVA是语言";
    //  blog["tag_id"]=2;
    //  blog["content"]="java语言是最牛逼的语言";
    //table_blog.Insert(blog);
    //table_blog.Delete(2);
    //table_blog.Update(blog);
      
    table_blog.GetOne(&blog);
   Json::StyledWriter writer;
   std::cout<<writer.write(blog)<<std::endl;
   



    blog_system::TableTag table_tag(mysql);

    //Json::Value tag;
    //tag["name"]="php";
   // tag["id"]=2;
    //table_tag.Insert(tag);
   // table_tag.Delete(3);
   //table_tag.Update(tag);
//     table_tag.GetOne(&tag);
//    Json::StyledWriter writer;
//    std::cout<<writer.write(tag)<<std::endl;
}

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);
   if(ret==false)
    {
        printf("InsertBlog parse blog json failed!\n");
        rsp.status=400;
        errmsg["ok"]=false;
        errmsg["reason"]="parse 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]);
    bool ret=table_blog->Delete(blog_id);
    if(ret==false)
    {
        printf("DeleteBlog 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 parse 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)
{
    Json::Value blog;
    int blog_id=std::stoi(req.matches[1]);
    blog["id"]=blog_id;
    bool ret=table_blog->GetOne(&blog);
    if(ret==false)
    {
        printf("GetOneBlog 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格式字符串组织的
    req.body;
    //将json格式的字符串进行反序列化,得到各个博客的信息
    Json::Reader reader;
    Json::Value tag;
    bool ret=reader.parse(req.body,tag);
   if(ret==false)
    {
        printf("InsertTag parse blog json failed!\n");
        rsp.status=400;
        return;
    } 
    //将得到的博客信息插入到数据库中
    ret=table_tag->Insert(tag);
    if(ret==false)
    {
        printf("InsertTag insert into database failed!\n");
        rsp.status=500;
        return;
    } 
    rsp.status=200;
    return;
}
void DeleteTag(const Request &req,Response &rsp)
{
    // /blog/123  /blog(\d+)  req.matches[0]=/blog/123   req.matches[1]=123;
    int tag_id=std::stoi(req.matches[1]);
    bool ret=table_tag->Delete(tag_id);
    if(ret==false)
    {
        printf("DeleteTag 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("UpdateTag parse json failed!\n");
        rsp.status=400;
        return;
    }
    tag["id"]=tag_id;
    ret=table_tag->Update(tag);
    if(ret==false)
    {
        printf("UpdateTag 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)
{
    Json::Value tag;
    int tag_id=std::stoi(req.matches[1]);
    tag["id"]=tag_id;
    bool ret=table_tag->GetOne(&tag);
    if(ret==false)
    {
        printf("GetOneTag 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会直接根据路径读取数据并直接响应
    server.set_base_dir("./www");//设置url中资源的相对根目录,并在请求/的时候自动加上index.html
    //网络通信--搭建http服务器
    //博客信息的增删改查
    server.Post("/blog",InsertBlog);
    //正则表达式: \d--匹配数字字符  +---表示匹配前面的字符一次或多次  () 表示临时保存的数据
    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);//R"()"取出所有的特殊含义
    server.Put(R"(/tag/(\d+))",UpdateTag);
    server.Get("/tag",GetAllTag);
    server.Get(R"(/tag/(\d+))",GetOneTag);
    
    server.listen("0.0.0.0",2580);
    
    
    
    return 0;
}

功能测试

在写代码的过程中,我们每次完成一个功能需要进行测试,这就用到了postman软件,软件在官网上直接下载即可,
下面是简单的测试实例,比如新增一个博客信息,在客户端发送请求,如果响应状态码为200并且数据库中已经被新增,那么功能正确,否则要进行排查错误。
博客系统
通过对业务管理的功能进行测试,下面开始我们的前端界面模块。

3.前端界面模块

在开始前端界面开发之前,我们不是从0开始的,首先下载一个界面模板,随便在网上下载一个,将文件夹保存到project路径下,然后开始进行界面的修改完善。
我们所要用到的html+css+js

  • html:超文本标签语言,通过浏览器渲染成一个界面,相当于素颜
  • css:样式语言(针对html控件使用一个特定的样式进行修饰)美颜
  • js:也就是常说的JavaScript,是一种脚本语言,使界面动态起来

Vue.js:类似于一个js库,使用时直接调用即可
使用vue:
1.加入vue链接
< script src=“https://cdn.jsdelivr.net/npm/vue/dist/vue.js”>
vue使用
2.在代码中定义vue对象
< script>
var app = new Vue({
el: ‘#app’,
data: {
message: ‘Hello Vue!’
}
})
< /script>
要想获取数据,就得向服务端发送请求,而ajax可以实现一个客户端向后台发送请求,获取响应ajax语法使用

博客系统

编辑博客:
editormd 实现了前端的markdown页面
1 . 加载样式
< link rel=“stylesheet” href=“editor/css/editormd.css” / >
2 .加载js 库
< script src= “editor/editormd.min.js” >
3 .在htm| 中添加编辑页面
< div d=“test-editormd” -show=“status==‘blog edit’”>
< texta rea style=” display:none;”> {show blog.content}< /textarea>
< /div>

4.在js 中进行控制
博客系统
下面代码展示:

<!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 index with sidebar & slider  | Amaze UI Examples</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>
  <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>

</head>

<body id="blog">

<div id="app">
<!-- nav start -->
<nav class="am-g am-g-fixed blog-fixed blog-nav">
<button class="am-topbar-btn am-topbar-toggle am-btn am-btn-sm am-btn-success am-show-sm-only blog-button" data-am-collapse="{target: '#blog-collapse'}" ><span class="am-sr-only">导航切换</span> <span class="am-icon-bars"></span></button>

  <div class="am-collapse am-topbar-collapse" id="blog-collapse">
    <ul class="am-nav am-nav-pills am-topbar-nav">
      <li class="am-active"><a href="lw-index.html">首页</a></li>
      <li class="am-dropdown" data-am-dropdown>
        <a class="am-dropdown-toggle" data-am-dropdown-toggle href="javascript:;">
          首页布局 <span class="am-icon-caret-down"></span>
        </a>
        <ul class="am-dropdown-content">
          <li><a href="lw-index.html">1. blog-index-standard</a></li>         
          <li><a href="lw-index-nosidebar.html">2. blog-index-nosidebar</a></li>
          <li><a href="lw-index-center.html">3. blog-index-layout</a></li>
          <li><a href="lw-index-noslider.html">4. blog-index-noslider</a></li>
        </ul>
      </li>
      <li><a href="lw-article.html">标准文章</a></li>
      <li><a href="lw-img.html">图片库</a></li>
      <li><a href="lw-article-fullwidth.html">全宽页面</a></li>
      <li><a href="lw-timeline.html">存档</a></li>
    </ul>
    <form class="am-topbar-form am-topbar-right am-form-inline" role="search">
      <div class="am-form-group">
        <input type="text" class="am-form-field am-input-sm" placeholder="搜索">
      </div>
    </form>
  </div>
</nav>
<hr>
<!-- nav end -->
<!-- banner start -->

<!-- banner 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_edit'"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/f19.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-show="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_delete(blog.id)">删除</button>
                  <button type="button" class="am-btn am-btn-danger" v-on:click="blog_edit(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="">新增 &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-danger" v-on:click="blog_update()">提交</button></span>
    </div>

    <div class="am-u-md-4 am-u-sm-12 blog-sidebar" v-show="status!='blog_edit'">
        <div class="blog-sidebar-widget blog-bor">
            <h2 class="blog-text-center blog-title"><span>About ME</span></h2>
            <img src="assets/i/f14.jpg" alt="about me" class="blog-entry-img" >
            <p>妹纸</p>
            <p>
        我是妹子UI,中国首个开源 HTML5 跨屏前端框架
        </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>

<!--[if (gte IE 9)|!(IE)]><!-->
<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>
  var app = new Vue({
  el: '#app',
  data: {
    author:'刘猛',
    status:'blog_list',
    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",//请求中的path路径
        type:"get",//设置请求方法
        context:this,//请求成功后回调函数的this指针----当前复制的this是vue对象
        success:function(result)
        {
          this.tag_list=result;//成功后响应正文的json字符串
        }

      })
    },
    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.status ='blog_admin';//将当前页面置为管理页面,一定要使用this
    },
    blog_delete:function(blog_id)
    {
      $.ajax({
        url:"/blog/"+blog_id,//请求中的path路径
        type:"delete",//设置请求方法
        context:this,//请求成功后回调函数的this指针----当前复制的this是vue对象
        success:function(result)
        {
          this.get_blog_list();//更新博客数据
        }

      })
    },
    blog_edit:function(blog_id)
    {
      
      $.ajax({
        url:"/blog/"+blog_id,//请求中的path路径
        type:"get",//设置请求方法
        context:this,//请求成功后回调函数的this指针----当前复制的this是vue对象
        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,//请求中的path路径
        type:"put",//设置请求方法
        context:this,//请求成功后回调函数的this指针----当前复制的this是vue对象
        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;//成功后响应正文的json字符串
          this.status='blog_edit'
          testEditormdView = editormd.markdownToHTML("test-editormd", {
                  markdown        : this.show_blog.content ,
                  htmlDecode      : "style,script,iframe",  
                  tocm            : true,   
                  emoji           : true,
                  taskList        : true,
                  tex             : true,  // 默认不解析
                  flowChart       : true,  // 默认不解析
                  sequenceDiagram : true,  // 默认不解析
            });
        }
      })

    }
      }
});

app.get_blog_list();
app.get_tag_list();
</script>

</body>
</html>

对于前端界面模块,有些地方还不够完善,一些功能还没有实现,下一步会不断地完善,而且由于前端的知识都是现学现卖,对于前端的不是特别的熟练,我会不断地学习,把功能完善。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。