设计思路
项目名称:个人博客系统
开发环境:Centos7.6 -vim/makefile
使用技术:MVC框架,MySQL数据库,httplib,jsoncpp,vue.js,ajax
项目功能:实现一个web服务器,能够提供用户通过浏览器访问服务器,实现博客的展示,以及博客的增删改查管理
框架设计:实现前端的页面,后台的服务,数据的管理—通过MVC框架进行实现
MVC框架
前端界面模块需要获取数据进行展示,向服务控制模块发送请求,服务控制模块从数据管理模块获取到数据,组织后发送给前端,是一种服务,数据,页面分离的开发方式
M–model–数据管理模块
基于MySQL数据库进行数据管理
C–controller–服务控制模块
搭建http服务器针对不同的请求提供不同的服务(博客页面的获取以及博客数据的增删改查操作)
V–view–前端界面模块
基于html+css+js实现前端界面的展示
详细设计
一、数据管理模块
使用MySQL数据库进行数据管理
客户端将对应的语句发送给服务器,服务器进行语句解析,然后完成对应的操作;通过MySQL提供的接口完成客户端的搭建
数据库实际上存储介质也是文件,但相较于普通的文件存储多了数据管理,更加安全,查询更加高效,外界客户端通过SQL语句完成对数据库中数据的增删查改
1.MySQL数据库
特点:数据库对语句中的大小写不敏感,库名,表名,表中字段不能使用关键字,每条语句都要以分号结尾
库的操作:
建库:
create database if not exists db_name;
如果db_name 不存在则创建
删库:
drop database db_name;
删除db_name数据库
show database;
显示所有数据库
use database;
选择要操作的数据库
表的操作:
建表:
create table if not exists db_name(字段信息);
删表:
drop table tb_name;
删除tb_name表
show tables;
显示数据库所有表
desc tb_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.标签表和博客表的设计
tag标签表:标签id,标签名称
create table if not exists tb_tag (
id int primary key auto_increment comment '标签ID',
name varchar(32) comment '标签名称'
);
id定义为主键,并且包含有自增属性
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 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
3.设置客户端字符编码集
int mysql_set_character_set(MYSQL *mysql,const char* csname)
mysql:mysql句柄
csname:utf-8
返回值:成功返回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)
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[0]表示第0列数据,row[1]表示第1列数据
7.关闭数据库释放资源
void mysql_close(MYSQL *mysql)
8.获取mysql接口调用失败的原因
const char* mysql_error(MYSQL *mysql)
5.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"] = 19;
value["score"]=95;
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.代码实现数据管理模块
#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服务器:搭建tcp服务端;等待客户端(浏览器)请求,解析请求,得到http协议格式中的各个要素(请求方法,url(资源路径以及查询字符串),头部字段,正文);根据客户端的需要,完成业务处理,组织http响应数据进行响应
对于http服务器,采用httplab库进行搭建
httplab库介绍
通过github下载httplab的相关代码,在之后的文件中引用httplab.h的头文件就可以使用此库。httplab需要使用gcc进行编译;虚拟机使用时应该关闭防火墙
systemctl stop firewalld
httplab中主要包含4类:
Server,Client,Request,Response
基本使用:
实例化一个server对象;注册请求-处理路由(给对应的请求设置对应的处理函数);开始服务器监听
httplab处理流程:
1.先提前编写不同请求对应的处理函数,void(*hander)(Request &,Response &)
2.实例化Server对象,(对象中包含请求与处理函数映射表,线程池)
3.根据不同请求以及建立的业务处理,在Server中建立对应的映射关系
Server.Get()/.Post()/.Put()/.Delete()…
4.搭建tcp服务器开始监听
5.有新连接到来,则将新连接抛入线程池进行处理
6.线程池中的线程的处理流程:
1)接受http请求头部,进行解析,并且根据头部中的content-length接受正文,用正文中包含的信息实例化一个Request对象
2)根据请求方法+资源路径,在映射表中查找对应请求的处理函数,找到之后执行这个函数,并且传入请求信息
3)我们自己编写的处理函数中根据Request中的请求信息,进行业务处理,填充Response对象
4)处理函数执行完毕后,线程根据Response对象中的数据组织http响应,发送给客户端
5)如果是短连接则关闭套接字,处理完毕,如果是长连接则等待下一条请求
3.网络通信接口设计
代码实现业务逻辑模块
#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
三.前端页面模块
本模块使用一个博客页面的模板进行改写,使用了html+css+js进行编写
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:{
author:'hlmz',
tag_list:[],
blog_list:[],
},
methods:{}
})
</script>
el中的数据要和容器id一致才能使用vue中定义的数据和方法,data就是定义的变量,methods就是定义的方法
在一个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({
url: "/blog", //请求中的path路径
type: "get", //请求中的请求方法
context: this, //设置请求成功后回调函数中的this指针--当前赋值的this是vue对象
success: function (result) {
this.blog_list = result; //成功后将响应的正文的json串赋值
}
})
3.前端代码实现
<!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)}} </a></span>
<span> @{{author}} </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">« 管理</a></li>
<li class="am-pagination-next"><a v-on:click="new_blog">新增 »</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: 'hlmz',
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>
至此,完成了博客系统的全部撰写,后续还会继续细化更改。谢谢阅读。