项目名称:博客系统
项目功能:实现一个web服务器,能够提供用户通过浏览器访问服务器,实现博客的展示以及对博客的增删查改的操作。
项目开发环境:VS Code 、阿里云服务器、git、g++、makefile
项目开发所用技术:MVC框架、ajax、jsoncpp、httplib、vue.js、mysql
框架设计:实现前端的界面,后台的服务以及数据的管理,其实运用的框架是MVC框架,MVC框架中,M代表业务模型,V代表用户界面,C代表控制器,简单的来说,就是前端界面将后台数据在界面进行展示,用户在前端界面进行操作向服务控制模块发出请求,服务控制模块进行响应,从数据管理模块中获取相应的数据然后展示给前端界面,是一种页面、服务、数据分离的开发模式。MVC框架
下面开始具体的详细设计,首先是从最底层开始进行开发,对数据管理模块进行设计,数据管理模块主要是利用mysql对数据库进行各种数据的操作
在编写代码之前我们先得准备好环境的配置,首先要下载mysql数据库,详细步骤MySQL环境的配置
操作 | 数据库 | 数据表 |
---|---|---|
建立 | 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());
上面的工作已经实现了通过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序列化方式。
将多个数据对象序列化为json格式的字符串,或者将json格式的字符串反序列化为各个数据对象。在安装json库之前首先确保gcc出于高版本,否则会下载失败
yum -y install epel-release
yum install jsoncpp-devel
gcc升级版本
#include
#include
#include
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
#include
#include
#include
#include
#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的完整代码,需要注意的是我们的服务器对应的是多客户端,多个客户端可能同时发起查询操作,如果每个操作创建一个对象,产生一个句柄,多个操作使用同一个句柄,会造成很多问题,而且查询和保存结果集操作并不是原子操作,可能在客户端访问时由于上一个客户端还没来得及保存结果集下一个客户端进行查询导致发生紊乱,造成结果保存失败,得不到正确的结果,所以我们采用了加锁的方法,将查询操作和保存结果集放在锁里面,构成一个原子操作,这样就能确保不会发生上面的问题了。
添加博客信息 :
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<
}
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并且数据库中已经被新增,那么功能正确,否则要进行排查错误。
通过对业务管理的功能进行测试,下面开始我们的前端界面模块。
在开始前端界面开发之前,我们不是从0开始的,首先下载一个界面模板,随便在网上下载一个,将文件夹保存到project路径下,然后开始进行界面的修改完善。
我们所要用到的html+css+js
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>
<!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/[email protected]">
<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/[email protected]">
<meta name="msapplication-TileImage" content="assets/i/[email protected]">
<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)}} </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_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">« 管理</a></li>
<li class="am-pagination-next"><a v-on:click="">新增 »</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>
对于前端界面模块,有些地方还不够完善,一些功能还没有实现,下一步会不断地完善,而且由于前端的知识都是现学现卖,对于前端的不是特别的熟练,我会不断地学习,把功能完善。