项目描述
功能
- 用户可以通过浏览器访问服务器获取菜品信息并进行点餐;
- 管理员可以通过浏览器访问服务器实现订单以及菜品的管理;
技术点
- 多线程、socket、http、json、mysql、stl;
框架
- 框架:简单 MVC 框架;
- M(Model):数据管理模块,管理菜品、订单信息,外界想要访问数据必须通过这个模块完成,不能直接访问;
- V(View):前端界面模块,浏览器的前端所展示的界面,用户或者管理员的操作都是通过前端界面来完成的;
- C(Controller):业务控制模块(服务器),针对前端的请求做出对应的处理;
详细设计
数据管理模块
- 数据存储:MySQL 数据库,原因:免费、可跨主机访问、线程安全;
- 数据库表的设计:菜品表、订单表;
菜品表:菜品序号、菜品名称、菜品单价、修改时间;
订单表:订单序号、订单菜品、订单状态、修改时间; - 代码设计:菜品类、订单类;
菜品类:添加、删除、修改、查看(单个 / 全部);
订单类:添加、删除、修改(菜品信息 / 菜品状态)、查看(单个 / 全部);
业务控制模块
- HTTP 服务器:基于 http 协议,使用 httplib 库搭建 http 服务器;
- 通信接口设计:借助 httplib 库编写请求与响应接口,什么样的请求对应什么样的业务处理和响应;
- 静态页面请求:HTML 页面(CSS / JS 文件,在一些现成的模板上以修改为主),然后将该页面命名为 index.html,将该页面所在目录设置为静态资源路径;
- 动态数据请求:菜品 / 订单数据增、删、改、查,通信接口采用 restful 风格接口设计,基于 HTTP 协议,使用 json 格式定义正文序列化方式,定义操作类型:新增-POST,删除-DELETE,修改-PUT,获取-GET;
请求方式 | 请求资源 | 响应结果 |
---|
Get | / 或者 index.html | 展示菜品信息界面、下单界面 |
Post | /dish | 管理员添加菜品 |
Delete | /dish/数字 | 删除指定数字序号的菜品 |
Put | /dish/数字 | 修改指定数字序号的菜品 |
Post | /order | 添加新的订单 |
前端界面模块
- 前端界面:基于简单的 html、css、vue.js 以及 ajax 实现前端界面的静态页面展示以及动态数据获取渲染功能;
- html:完成页面的布局;
- css:样式语言,对标签容器进行样式修饰,让简单的 html 页面更加好看;
- vue.js:脚本语言,让页面可以动态渲染展示;
涉及知识点
JSON
Value
- 这是 JSON 与外界进行数据转换的一个对象类,这其中重载了很多的运算符,包含了大量的类型转换函数;
string name = "zhangjinrui";
int age = 18;
vector<double> score = {88.8, 99.9, 77.7};
Json::Value val;
val["name"] = name;
val["age"] = age;
for(int i = 0; i < score.size(); i++){
val["score"].append(score[i]);
}
string name2 = val["name"].asString();
int age2 = val["age"].asInt();
double math_score = val["score"][1].asDouble();
Writer
- 实现序列化类,将 Json::Value 对象中的数据序列化成为 JSON 格式的字符串;
Json::FastWriter writer1;
string str1 = writer1.write(val);
Json::StyledWriter writer2;
string str2 = writer2.write(val);
Reader
- 实现反序列化类,将 JSON 格式字符串转换为多个数据对象,存储在 Json::Value 对象中;
Json::Reader read;
Json::Value val2;
read.parse(str, val2);
正则表达式
R"()"
:去转义用途,正则表达式搭配该 C++11 新特性语法,可以去除括号中一些特殊字符的特殊含义;- 关于正则表达式这里就不过多介绍,附上一个大佬的博客,感兴趣的可以看看:最全常用正则表达式大全
()
:在 httplib 中,请求信息const Request& req
中存在一个matches
数组,当我们使用()
将请求资源路径中的某些信息括起来时,该数组中就存放了这些使用括号捕捉到的信息,matches[0]
存放的是整个请求,然后数组后续元素是从括号中捕捉到的信息,按顺序排列好的;
MySQL语句
MySQL代码
httplib基础
- 存在一张路由表,
map<pair<string, string>, function> route
,请求与处理一一对应; - 使用
Server
创建一个服务端,然后向路由表中添加请求与处理的键值对; - 服务端调用
listen
接口进行监听; - 当服务端收到客户端的请求后,将该请求抛入线程池中,线程池中的线程负责与指定客户端进行通信;
- 实例化
httplib::Requert
对象 req,线程按照 http 协议格式解析请求信息,将解析结果填充到 req 对象中; - 根据请求信息在路由表中查找是否有对应处理函数,如果没有则返回 404(请求资源不存在),如果有,则再实例化一个
httplib::Response
对象 res,然后将 req 与 res 传入处理函数中,进行处理; - 处理结束后,将返回信息填入 res 中,然后服务端根据 res 对象组织响应,将响应结果返回给客户端;
- 注册路由函数:Get()、Post()、Put()、Delete();
- 设置静态资源默认路径接口:
Server::set_base_dir(char* path);
设置这个路径后,当前端在请求静态资源的时候,就会自动先到这个路径下查找有没有对应的静态资源文件,如果有则自动读取文件数据进行恢复;
项目总结
- 这个订单系统中,用户通过浏览器与后台服务器进行交互,实现查看菜品信息以及下单功能,管理员通过浏览器与后台服务器进行交互,实现对菜品和订单的管理功能;
- 这个项目在实现的时候采用了一个不太严谨的 mvc 框架实现,将项目实现总体分为三个模块:数据管理、业务处理、前端页面;
- 数据管理模块:基于 MySQL 数据库实现数据存储管理,并且封装数据库访问类,向外提供与业务分离的数据信息;
- 业务处理模块:基于 http 协议,使用 httplib 库搭建服务器与前端进行交互,实现菜品以及订单的数据业务处理功能;
- 前端界面模块:基于简单的 html、css、vue.js 和 ajax 实现前端的静态页面展示,以及动态数据获取渲染功能;
项目代码
create database if not exists order_sys;
use order_sys;
create table if not exists tb_dish(
id int primary key auto_increment,
name varchar(32) unique not null,
price int not null,
ctime datetime
);
create table if not exists tb_order(
id int primary key auto_increment,
dishes varchar(255) comment '[1, 2]',
status int comment '0-未完成,1-已完成',
mtime datetime
);
#include<iostream>
#include<string>
#include<mutex>
#include<mysql/mysql.h>
#include<jsoncpp/json/json.h>
namespace order_sys{
#define MYSQL_SERVER "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PASSWD ""
#define MYSQL_DBNAME "order_sys"
static MYSQL* MysqlInit(){
MYSQL* mysql = NULL;
mysql = mysql_init(NULL);
if(mysql == NULL){
std::cout << "mysql init failed!\n";
return NULL;
}
if(mysql_real_connect(mysql, MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWD, MYSQL_DBNAME, 0, NULL, 0) == NULL){
std::cout << mysql_error(mysql) << std::endl;
return NULL;
}
if(mysql_set_character_set(mysql, "utf8") != 0){
std::cout << mysql_error(mysql) << std::endl;
return NULL;
}
if(mysql_select_db(mysql, MYSQL_DBNAME) != 0){
std::cout << mysql_error(mysql) << std::endl;
return NULL;
}
return mysql;
}
static void MysqlRelease(MYSQL* mysql){
if(mysql != NULL){
mysql_close(mysql);
}
}
static bool MysqlQuery(MYSQL* mysql, const std::string& sql){
if(mysql_query(mysql, sql.c_str()) != 0){
std::cout << sql << std::endl;
std::cout << mysql_error(mysql) << std::endl;
return false;
}
return true;
}
class TableDish{
private:
MYSQL* _mysql;
std::mutex _mutex;
public:
TableDish(){
_mysql = MysqlInit();
if(_mysql == NULL)
exit(-1);
}
~TableDish(){
if(_mysql != NULL){
MysqlRelease(_mysql);
_mysql = NULL;
}
}
bool Insert(const Json::Value& dish){
#define DISH_INSERT "insert tb_dish values(null, '%s', %d, now());"
char str_sql[4096] = {0};
sprintf(str_sql, DISH_INSERT, dish["name"].asString(), dish["price"].asInt());
return MysqlQuery(_mysql, str_sql);
}
bool Delete(int dish_id){
#define DISH_DELETE "delete from tb_dish where id = %d;"
char str_sql[4096] = {0};
sprintf(str_sql, DISH_DELETE, dish_id);
return MysqlQuery(_mysql, str_sql);
}
bool Update(const Json::Value& dish){
#define DISH_UPDATE "update tb_dish set name = '%s', price = %d where id = %d;"
char str_sql[4096] = {0};
sprintf(str_sql, DISH_UPDATE, dish["name"].asString(), dish["price"].asInt(), dish["id"].asInt());
return MysqlQuery(_mysql, str_sql);
}
bool SelectAll(Json::Value* dishes){
#define DISH_SELECTALL "select * from tb_dish;"
_mutex.lock();
if(MysqlQuery(_mysql, DISH_SELECTALL) == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
_mutex.unlock();
if(res == NULL){
std::cout << "store result failed!\n";
return false;
}
int num = mysql_num_rows(res);
for(int i = 0; i < num; i++){
MYSQL_ROW row = mysql_fetch_row(res);
Json::Value dish;
dish["id"] = std::stoi(row[0]);
dish["name"] = row[1];
dish["price"] = std::stoi(row[2]);
dish["ctime"] = row[3];
dishes->append(dish);
}
mysql_free_result(res);
return true;
}
bool SelectOne(int dish_id, Json::Value* dish){
#define DISH_SELECTONE "select * from tb_dish where id = %d;"
char str_sql[4096] = {0};
sprintf(str_sql, DISH_SELECTONE, dish_id);
_mutex.lock();
if(MysqlQuery(_mysql, str_sql) == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
_mutex.unlock();
if(res == NULL){
std::cout << "store result failed!\n";
return false;
}
int num = mysql_num_rows(res);
if(num != 1){
std::cout << "store result failed!\n";
mysql_free_result(res);
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
(*dish)["id"] = std::stoi(row[0]);
(*dish)["name"] = row[1];
(*dish)["price"] = std::stoi(row[2]);
(*dish)["ctime"] = row[3];
mysql_free_result(res);
return true;
}
};
class TableOrder{
private:
MYSQL* _mysql;
std::mutex _mutex;
public:
TableOrder(){
_mysql = MysqlInit();
if(_mysql == NULL)
exit(-1);
}
~TableOrder(){
if(_mysql != NULL){
MysqlRelease(_mysql);
_mysql = NULL;
}
}
bool Insert(const Json::Value& order){
#define ORDER_INSERT "insert tb_order values(null, '%s', 0, now());"
char str_sql[4096] = {0};
Json::FastWriter writer;
std::string dishes = writer.write(order["dishes"]);
dishes[dishes.size() - 1] = '\0';
sprintf(str_sql, ORDER_INSERT, dishes.c_str());
return MysqlQuery(_mysql, str_sql);
}
bool Delete(int order_id){
#define ORDER_DELETE "delete from tb_order where id = %d;"
char str_sql[4096] = {0};
sprintf(str_sql, ORDER_DELETE, order_id);
return MysqlQuery(_mysql, str_sql);
}
bool Update(const Json::Value& order){
#define ORDER_UPDATE "update tb_order set dishes = '%s', status = %d where id = %d;"
char str_sql[4096] = {0};
Json::FastWriter writer;
std::string dishes = writer.write(order["dishes"]);
dishes[dishes.size() - 1] = '\0';
sprintf(str_sql, ORDER_UPDATE, dishes.c_str(), order["status"].asInt(), order["id"].asInt());
return MysqlQuery(_mysql, str_sql);
}
bool SelectAll(Json::Value* orders){
#define ORDER_SELECTALL "select * from tb_order;"
_mutex.lock();
if(MysqlQuery(_mysql, ORDER_SELECTALL) == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
_mutex.unlock();
if(res == NULL){
std::cout << mysql_error(_mysql) << std::endl;
return false;
}
int num = mysql_num_rows(res);
for(int i = 0; i < num; i++){
MYSQL_ROW row = mysql_fetch_row(res);
Json::Value order, dishes;
Json::Reader reader;
order["id"] = std::stoi(row[0]);
reader.parse(row[1], dishes);
order["dishes"] = dishes;
order["status"] = std::stoi(row[2]);
order["mtime"] = row[3];
orders->append(order);
}
mysql_free_result(res);
return true;
}
bool SelectOne(int order_id, Json::Value* order){
#define ORDER_SELECTONE "select * from tb_order where id = %d;"
char str_sql[4096] = {0};
sprintf(str_sql, ORDER_SELECTONE, order_id);
_mutex.lock();
if(MysqlQuery(_mysql, str_sql) == false){
_mutex.unlock();
return false;
}
MYSQL_RES* res = mysql_store_result(_mysql);
_mutex.unlock();
if(res == NULL){
std::cout << mysql_error(_mysql) << std::endl;
return false;
}
int num = mysql_num_rows(res);
if(num != 1){
std::cout << "store result failed!\n";
mysql_free_result(res);
return false;
}
Json::Reader reader;
Json::Value dish;
MYSQL_ROW row = mysql_fetch_row(res);
(*order)["id"] = std::stoi(row[0]);
reader.parse(row[1], dish);
(*order)["name"] = dish;
(*order)["price"] = std::stoi(row[2]);
(*order)["ctime"] = row[3];
mysql_free_result(res);
return true;
}
};
}
#include"db.hpp"
#include"httplib.h"
using namespace httplib;
#define WWWROOT "./wwwroot"
order_sys::TableDish* dishptr = NULL;
order_sys::TableOrder* orderptr = NULL;
void DishInsert(const Request& req, Response& rsp){
Json::Value dish;
Json::Reader reader;
bool ret = reader.parse(req.body, dish);
if(ret == false){
rsp.status = 400;
Json::Value reason;
Json::FastWriter writer;
reason["result"] = false;
reason["reason"] = "dish info parse failed!";
rsp.body = writer.write(reason);
rsp.set_header("Content-Type", "application/json");
std::cout << "dish insert parse failed!\n";
return;
}
ret = dishptr->Insert(dish);
if(ret == false){
rsp.status = 500;
Json::Value reason;
Json::FastWriter writer;
reason["result"] = false;
reason["reason"] = "mysql insert failed!";
rsp.body = writer.write(reason);
rsp.set_header("Content-Type", "application/json");
std::cout << "mysql dish insert failed!\n";
return;
}
rsp.status = 200;
return;
}
void DishDelete(const Request& req, Response& rsp){
int dish_id = std::stoi(req.matches[1]);
bool ret = dishptr->Delete(dish_id);
if(ret == false){
std::cout << "mysql dish delete failed!\n";
rsp.status = 500;
return;
}
return;
}
void DishUpdate(const Request& req, Response& rsp){
int dish_id = std::stoi(req.matches[1]);
Json::Value dish;
Json::Reader reader;
bool ret = reader.parse(req.body, dish);
if(ret == false){
rsp.status = 400;
std::cout << "dish update parse failed!\n";
return;
}
dish["id"] = dish_id;
ret = dishptr->Update(dish);
if(ret == false){
rsp.status = 500;
std::cout << "mysql dish update failed!\n";
return;
}
return;
}
void DishGetAll(const Request& req, Response& rsp){
Json::Value dishes;
bool ret = dishptr->SelectAll(&dishes);
if(ret == false){
rsp.status = 500;
std::cout << "mysql dish selectall failed!\n";
return;
}
Json::FastWriter writer;
rsp.body = writer.write(dishes);
return;
}
void DishGetOne(const Request& req, Response& rsp){
int dish_id = std::stoi(req.matches[1]);
Json::Value dish;
bool ret = dishptr->SelectOne(dish_id, &dish);
if(ret == false){
rsp.status = 500;
std::cout << "mysql dish selectone failed!\n";
return;
}
Json::FastWriter writer;
rsp.body = writer.write(dish);
return;
}
void OrderInsert(const Request& req, Response& rsp){
Json::Value order;
Json::Reader reader;
bool ret = reader.parse(req.body, order);
if(ret == false){
rsp.status = 400;
Json::Value reason;
Json::FastWriter writer;
reason["result"] = false;
reason["reason"] = "order info parse failed!";
rsp.body = writer.write(reason);
rsp.set_header("Content-Type", "application/json");
std::cout << "order insert parse failed!\n";
return;
}
ret = orderptr->Insert(order);
if(ret == false){
rsp.status = 500;
Json::Value reason;
Json::FastWriter writer;
reason["result"] = false;
reason["reason"] = "mysql insert failed!";
rsp.body = writer.write(reason);
rsp.set_header("Content-Type", "application/json");
std::cout << "mysql order insert failed!\n";
return;
}
rsp.status = 200;
return;
}
void OrderDelete(const Request& req, Response& rsp){
int order_id = std::stoi(req.matches[1]);
bool ret = orderptr->Delete(order_id);
if(ret == false){
std::cout << "mysql order delete failed!\n";
rsp.status = 500;
return;
}
return;
}
void OrderUpdate(const Request& req, Response& rsp){
int order_id = std::stoi(req.matches[1]);
Json::Value order;
Json::Reader reader;
bool ret = reader.parse(req.body, order);
if(ret == false){
rsp.status = 400;
std::cout << "order update parse failed!\n";
return;
}
order["id"] = order_id;
ret = orderptr->Update(order);
if(ret == false){
rsp.status = 500;
std::cout << "mysql order update failed!\n";
return;
}
return;
}
void OrderGetAll(const Request& req, Response& rsp){
Json::Value orders;
bool ret = orderptr->SelectAll(&orders);
if(ret == false){
rsp.status = 500;
std::cout << "mysql order selectall failed!\n";
return;
}
Json::FastWriter writer;
rsp.body = writer.write(orders);
return;
}
void OrderGetOne(const Request& req, Response& rsp){
int order_id = std::stoi(req.matches[1]);
Json::Value order;
bool ret = orderptr->SelectOne(order_id, &order);
if(ret == false){
rsp.status = 500;
std::cout << "mysql order selectone failed!\n";
return;
}
Json::FastWriter writer;
rsp.body = writer.write(order);
return;
}
int main(){
dishptr = new order_sys::TableDish();
orderptr = new order_sys::TableOrder();
Server server;
server.set_base_dir(WWWROOT);
server.Post("/dish", DishInsert);
server.Delete(R"(/dish/(\d+))", DishDelete);
server.Put(R"(/dish/(\d+))", DishUpdate);
server.Get("/dish", DishGetAll);
server.Get(R"(/dish/(\d+))", DishGetOne);
server.Post("/order", OrderInsert);
server.Delete(R"(/order/(\d+))", OrderDelete);
server.Put(R"(/order/(\d+))", OrderUpdate);
server.Get("/order", OrderGetAll);
server.Get(R"(/order/(\d+))", OrderGetOne);
server.listen("0.0.0.0", 9000);
return 0;
}