实现服务器接口
我们用一个http
服务器作为底层,但是c++中并没有先成的http服务器,所以我在GitHub上找到一个牛人写的http
服务器,拿来直接用,节省本项目开发的时间
这是服务器的链接地址
上面有详细的使用方法,本文就不再解释具体如何实现,就直接用该cpp-httplib
这个库来进行开发
https://github.com/yhirose/cpp-httplib.git
然后我们就要实现相应的接口了
- 首先我们先要让我们的服务器和数据库连通,所以我们先要进行数据库客户端的初始化和释放
- 第二步我们就要设置一些自定义的路由(此处的路由并不是IP里的路由,而是指的是http中对应的方法+path对应到相应的处理函数上),要实现新增博客,查看博客等在数据库里所对应的相应的功能
- 第三步,我们要设置静态文件目录,把我们将来写好的网页放到这个目录里
和数据库建立链接
在这一步中,连接很简单,调用我们封装好的数据库API就行,但是断开连接就比较麻烦,因为整个服务器一但启动,他会进入到监听状态,所以必须等服务器关闭时,我们才能断开连接,而我们一般在Linux下关闭服务器都是使用ctrl+c
来进行关闭。ctrl+c
是一个信号,所以我们可以当触发这个信号时,就进行断开连接操作。
using namespace httplib; using namespace blog_system; //1. 先和数据库建立好链接 mysql = blog_system::MySQLInit(); signal(SIGINT,[](int){blog_system::MySQLRelease(mysql);exit(0);}); //2.创建相关数据库处理对象 BlogTable blog_table(mysql); TagTable tag_table(mysql);
我们在进行信号处理时,第二个参数,是一个回调函数,当第一个参数触发时,执行后面回调函数里的内容,但是要写一个函数太麻烦,这里使用c++11中提供的lambda表达式。这样可以使代码更简洁,更容易进行维护。
实现新增博客
我们要先接受请求消息中的body,并把它转化成json,也就是把HHTP中的我们看不懂或者是太难理解的请求,转化为j我们能看懂或是看起来比较方便的json格式。
而我们要实现新增博客必须要经历以下几个步骤
- 首先我们肯定要接受http的请求消息,这里就是一个Post请求
- 我们要对这个消息进行校验,但是http的格式并不好让我们进行校验,所以我们要先把http的格式转化为json格式,方便我们进行校验
- 校验完后,我们就可以调用我们封装好的mysql的API对数据进行操作
- 最后我们还要封装正确的返回结果,给用户友好的提示
- 这里我们并不使用异常处理,我们使用错误判定,这样更好,虽然代码比较乱,但是可以很容易理解。而且c++中异常处理比较差,功能比较有限
- 不光是这个接口,我们每个博客接口都必须捕捉到blog_table这个对象
server.Post("/blog", [&blog_table](const Request& req, Response& resp) {printf("新增博客!\n");//创建一些我们需要用到的对象Json::FastWriter writer; //写对象Json::Reader reader; //读取对象Json::Value req_json; //请求对象 Json::Value resp_json; //响应对象//1.获取到请求中的body并解析成json//parse函数中第一个参数是需要解析的字符串//第二个参数是把解析后的字符串放到哪个对象中//第三个参数是注释,默认是true,所以我就不管了bool ret = reader.parse(req.body,req_json);if(!ret){//解析出错,给用户提示printf("解析请求失败! %s\n",req.body.c_str());//构造一个相应对象,告诉客户端出错了resp_json["ok"] = false;resp_json["reason"] = "提交的数据格式有误!\n";resp.status = 400;//返回给客户端,第二个参数设置反回的数据类型resp.set_content(writer.write(resp_json), "application/json");return;}//2. 进行参数校验//这些字段如果缺任何一个,都代表有错if (req_json["title"].empty() || req_json["content"].empty()|| req_json["tag_id"].empty() || req_json["create_time"].empty()) {resp_json["ok"] = false;resp_json["reason"] = "博客的格式出错!\n";resp.status = 400;resp.set_content(writer.write(resp_json), "application/json");return; }//3.调用数据库接口进行操作ret = blog_table.Insert(req_json);if (!ret) {printf("插入博客失败!\n");resp_json["ok"] = false;resp_json["reason"] = "插入失败!\n";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}//4.封装正确的返回结果resp_json["ok"] = true;resp.set_content(writer.write(resp_json), "application/json");return;});
查看所有博客
server.Get("/blog", [&blog_table](const Request& req, Response& resp) { printf("查看所有博客!\n"); Json::Reader reader; Json::FastWriter writer; Json::Value resp_json; //如果没传tag_id,返回的是空字符串 const std::string& tag_id = req.get_param_value("tag_id"); // 对于查看博客来说 API 没有请求参数, 不需要解析参数和校验了, 直接构造结果即可 // 1. 调用数据库接口查询数据 Json::Value blogs; bool ret = blog_table.SelectAll(&blogs, tag_id); if (!ret) { resp_json["ok"] = false; resp_json["reason"] = "查看博客失败\n"; resp.status = 500; resp.set_content(writer.write(resp_json), "application/json"); return; } // 2. 构造响应结果 resp.set_content(writer.write(blogs), "application/json"); return; });
查看一篇博客
查看一篇博客内容
查看blog_id,如果这里只写blog_id,这个httplib的库并不能识别,我又仔细看了
这个库的文档,他用了正则表达式,所以我又学习了一些正则表达式的内容
我们可以用\d+
表示匹配一个数字,但是这里又可能会引发c++中的转义字符,所以我们需要c++11中提供的R()使转义字符不生效
学习正则表达式
server.Get(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {//1.解析获取到blog_idint32_t blog_id = std::stoi(req.matches[1].str());printf("查看id为 %d 的博客!\n",blog_id);Json::Value resp_json;Json::FastWriter writer;//2.直接调用数据库操作bool ret = blog_table.SelectOne(blog_id, &resp_json);if (!ret) {resp_json["ok"] = false;resp_json["reason"] = "查看指定博客失败!\n";resp.status = 404;resp.set_content(writer.write(resp_json), "application/json");return;}//3.包装正确的响应resp_json["ok"] = true;resp.set_content(writer.write(resp_json), "application/json");return;
});
删除博客
server.Delete(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {Json::Value resp_json;Json::FastWriter writer;// 1. 解析获取 blog_id//使用 matches[1] 就能获取到 blog_idint32_t blog_id = std::stoi(req.matches[1].str());printf("删除 id 为%d 的博客!\n",blog_id);// 2. 调用数据库接口删除博客bool ret = blog_table.Delete(blog_id);if (!ret) {resp_json["ok"] = false;resp_json["reason"] = "删除博客失败!\n";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}//3.包装正确的响应resp_json["ok"] = true;resp.set_content(writer.write(resp_json), "application/json");return;});
修改博客
修改博客就是重新插入新博客,所以我们又要校验博客信息,当然再校验之前还是需要对博客进行解析。
server.Put(R"(/blog/(\d+))", [&blog_table](const Request& req, Response& resp) {Json::Reader reader;Json::FastWriter writer;Json::Value req_json;Json::Value resp_json;// 1. 获取到博客 idint32_t blog_id = std::stoi(req.matches[1].str());printf("修改 id为 %d的博客!\n",blog_id);// 2. 解析博客信息bool ret = reader.parse(req.body, req_json);if (!ret) {resp_json["ok"] = false;resp_json["reason"] = "解析博客失败!\n";resp.status = 400;resp.set_content(writer.write(resp_json), "application/json");return ;}//一定要记得补充上 blog_id req_json["blog_id"] = blog_id; //从path中得到的id设置到json对象中// 3. 校验博客信息if (req_json["title"].empty() || req_json["content"].empty()|| req_json["tag_id"].empty()) {// 请求解析出错, 返回一个400响应resp_json["ok"] = false;resp_json["reason"] = "更新博客格式错误\n";resp.status = 400;resp.set_content(writer.write(resp_json), "application/json");return;}// 4. 调用数据库接口进行修改ret = blog_table.Update(req_json);if (!ret) {resp_json["ok"] = false;resp_json["reason"] = "更新失败!\n";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}// 5. 封装正确的数据resp_json["ok"] = true;resp.set_content(writer.write(resp_json), "application/json");return;});
新增标签
新增标签跟新增博客是一样的道理,都需要把依赖对象传进来,然后先进行格式校验,校验前先进行解析,校验完后再调用mysqlAPI,最后带着结果返回。
server.Post("/tag", [&tag_table](const Request& req, Response& resp) { Json::Reader reader; Json::FastWriter writer; Json::Value req_json; Json::Value resp_json; printf("新增标签!\n"); // 1. 请求解析成 Json 格式 bool ret = reader.parse(req.body, req_json); if (!ret) { resp_json["ok"] = false; resp_json["reason"] = "解析失败\n"; resp.status = 400; resp.set_content(writer.write(resp_json), "application/json"); return ; } // 2. 校验标签格式 if (req_json["tag_name"].empty()) { resp_json["ok"] = false; resp_json["reason"] = "标签格式有误!\n"; resp.status = 400; resp.set_content(writer.write(resp_json), "application/json"); return; } // 3. 调用数据库接口, 插入标签 ret = tag_table.Insert(req_json); if (!ret) { resp_json["ok"] = false; resp_json["reason"] = "插入标签失败!\n"; resp.status = 500; resp.set_content(writer.write(resp_json), "application/json"); return; } // 4. 返回正确的结果 resp_json["ok"] = true; resp.set_content(writer.write(resp_json), "application/json"); });
删除标签
server.Delete(R"(/tag/(\d+))", [&tag_table](const Request& req, Response& resp) { Json::Value resp_json; Json::FastWriter writer; // 1. 解析出 tag_id int tag_id = std::stoi(req.matches[1].str()); printf("要删除的标签id 为 %d\n",tag_id); // 2. 执行数据库操作删除标签 bool ret = tag_table.Delete(tag_id); if (!ret) { resp_json["ok"] = false; resp_json["reason"] = "删除所有标签失败\n"; resp.status = 500; resp.set_content(writer.write(resp_json), "application/json"); return; } // 3. 包装正确的结果 resp_json["ok"] = true; resp.set_content(writer.write(resp_json), "application/json"); return; });
获取所有标签
server.Get("/tag", [&tag_table](const Request& req, Response& resp) { printf("获取所有标签\n");Json::Reader reader;Json::FastWriter writer;Json::Value resp_json;// 1. 调用数据库接口查询数据Json::Value tags;bool ret = tag_table.SelectAll(&tags);if (!ret) {resp_json["ok"] = false;resp_json["reason"] = "查找所有标签失败\n";resp.status = 500;resp.set_content(writer.write(resp_json), "application/json");return;}// 2. 构造响应结果resp.set_content(writer.write(tags), "application/json");return;
});
获取静态文件目录
server.set_base_dir("./wwwroot");