Project description
function
- Users can access the server through the browser to obtain dish information and order meals;
- The administrator can access the server through the browser to realize the management of orders and dishes;
Technical point
- Multithreading, socket, http, json, mysql, stl;
frame
- Framework: simple MVC framework;
- M (Model): data management module, which manages dishes and order information. If the outside world wants to access data, it must be completed through this module and cannot be accessed directly;
- V (View): front end interface module, the interface displayed by the front end of the browser, and the operations of users or administrators are completed through the front end interface;
- C (Controller): service control module (server), which makes corresponding processing for the request of the front end;
detailed design
Data management module
- Data storage: MySQL database, reason: free, cross host access, thread safety;
- Database table design: dish table and order table;
Dish table: dish serial number, dish name, dish unit price and modification time;
Order table: order serial number, order dishes, order status and modification time; - Code design: dishes and orders;
Dishes: add, delete, modify, view (single / all);
Order type: add, delete, modify (dish information / dish status), view (single / all);
Service control module
- http server: Based on http protocol, use httplib library to build http server;
- Communication interface design: write the request and response interface with the help of httplib library, and what kind of request corresponds to what kind of business processing and response;
- Static page request: HTML page (CSS / JS file, mainly modified on some ready-made templates), then name the page index.html, and set the directory where the page is located as the static resource path;
- Dynamic data request: add, DELETE, modify and query dish / order data. The communication interface is designed in restful style. Based on HTTP protocol, the text serialization method is defined in json format, and the operation types are defined: add POST, DELETE, modify PUT and GET;
Request mode | Request resources | Response results |
---|
Get | /Or index html | Display menu information interface and order interface |
Post | /dish | Add dishes by administrator |
Delete | /dish / digital | Delete dishes with specified numerical sequence number |
Put | /dish / digital | Modify dishes with specified numerical sequence number |
Post | /order | Add new order |
Front end interface module
- Front end interface: Based on simple html, css and Vue JS and ajax realize the functions of static page display and dynamic data acquisition and rendering of the front-end interface;
- html: complete the layout of the page;
- css: style language, which modifies the label container to make simple html pages look better;
- vue.js: script language, so that the page can be rendered and displayed dynamically;
Knowledge points involved
JSON
Value
- This is an object class for data conversion between JSON and the outside world, which overloads many operators and contains a large number of type conversion functions;
//Serialize the following data
string name = "zhangjinrui";
int age = 18;
vector<double> score = {88.8, 99.9, 77.7};
//Define Value object
Json::Value val;
//Serialization of common types (string, integer)
val["name"] = name;
val["age"] = age;
//Array serialization requires the append interface
for(int i = 0; i < score.size(); i++){
val["score"].append(score[i]);
}
//The following is to convert JSON data format to normal data type
string name2 = val["name"].asString();
int age2 = val["age"].asInt();
double math_score = val["score"][1].asDouble();
Writer
- Implement the serialization class to serialize the data in the Json::Value object into a string in JSON format;
//The object defined by FastWriter is used for serialization. The serialization result is compact without spaces, which is inconvenient to watch, but the transmission efficiency is high
Json::FastWriter writer1;
string str1 = writer1.write(val);
//The object defined by StyledWriter is used for serialization. The result of serialization is easy to see, but there are many redundant spaces, so the transmission efficiency is not high
Json::StyledWriter writer2;
string str2 = writer2.write(val);
Reader
- Implement the deserialization class to convert the JSON format string into multiple data objects and store them in the Json::Value object;
//Use the parse interface to convert the serialized string into a JSON data object. Success returns true and failure returns false
Json::Reader read;
Json::Value val2;
//The deserialization result is stored in val2 and then used
read.parse(str, val2);
regular expression
- R "()": the purpose of escaping. Regular expressions can be used with the new C++11 feature syntax to remove the special meaning of some special characters in parentheses;
- There is no more introduction to regular expressions here. A big man's blog is attached. If you are interested, you can see: Most commonly used regular expressions
- (): in httplib, there is an array of matches in the request information const request & req. When we use () to enclose some information in the request resource path, the array stores the information captured in parentheses. matches[0] stores the whole request, and then the subsequent elements of the array are arranged in order from the information captured in parentheses;
MySQL statement
MySQL code
httplib Foundation
- There is a routing table, map < pair < string, string >, function > route, and the request and processing correspond one by one;
- Create a Server using Server, and then add the key value pairs of request and processing to the routing table;
- The server calls the listen interface to listen;
- After receiving the request from the client, the server throws the request into the thread pool, and the thread in the thread pool is responsible for communicating with the specified client;
- Instantiate the httplib:: requestobject req, the thread parses the request information according to the http protocol format, and fills the parsing results into the req object;
- Find out whether there is a corresponding processing function in the routing table according to the request information. If not, 404 is returned (the requested resource does not exist). If yes, instantiate an httplib::Response object res, and then pass req and res into the processing function for processing;
- After processing, fill the returned information into res, and then the server organizes the response according to the res object and returns the response result to the client;
- Register routing functions: Get(), Post(), Put(), Delete();
- Set the default path interface of static resources: Server::set_base_dir(char* path);
After setting this path, when the current end requests static resources, it will automatically go to this path first to find out whether there is a corresponding static resource file. If so, it will automatically read the file data for recovery;
Project summary
- In this order system, the user interacts with the background server through the browser to view the dish information and place an order. The administrator interacts with the background server through the browser to realize the management function of dishes and orders;
- This project adopts a less rigorous mvc framework, which is divided into three modules: data management, business processing and front-end page;
- Data management module: realize data storage management based on MySQL database, encapsulate database access classes, and provide data information separated from business;
- Business processing module: Based on http protocol, use httplib library to build a server to interact with the front end to realize the data business processing function of dishes and orders;
- Front end interface module: Based on simple html, css and Vue JS and ajax realize the static page display of the front end and the function of dynamic data acquisition and rendering;
Project code
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-Incomplete, 1-Completed',
mtime datetime
);
- Database management code:
#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"
//Database preparation
static MYSQL* MysqlInit(){
MYSQL* mysql = NULL;
//Initialization handle
mysql = mysql_init(NULL);
if(mysql == NULL){
std::cout << "mysql init failed!\n";
return NULL;
}
//Connect server
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;
}
//Set character set
if(mysql_set_character_set(mysql, "utf8") != 0){
std::cout << mysql_error(mysql) << std::endl;
return NULL;
}
//Select database
if(mysql_select_db(mysql, MYSQL_DBNAME) != 0){
std::cout << mysql_error(mysql) << std::endl;
return NULL;
}
return mysql;
}
//Database release operation
static void MysqlRelease(MYSQL* mysql){
if(mysql != NULL){
mysql_close(mysql);
}
}
//Statement execution interface
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;
}
//Dishes
class TableDish{
private:
MYSQL* _mysql;
std::mutex _mutex;
public:
//Constructor
TableDish(){
_mysql = MysqlInit();
if(_mysql == NULL)
exit(-1);
}
//Destructor
~TableDish(){
if(_mysql != NULL){
MysqlRelease(_mysql);
_mysql = NULL;
}
}
//insert data
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);
}
//Delete data
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);
}
//Modify data
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);
}
//Query all data
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;
}
//Query single line data
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;
}
};
//Order class
class TableOrder{
private:
MYSQL* _mysql;
std::mutex _mutex;
public:
//Constructor
TableOrder(){
_mysql = MysqlInit();
if(_mysql == NULL)
exit(-1);
}
//Destructor
~TableOrder(){
if(_mysql != NULL){
MysqlRelease(_mysql);
_mysql = NULL;
}
}
//insert data
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);
}
//Delete data
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);
}
//Modify data
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);
}
//Query all data
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;
}
//Query single line data
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; //Because the names in this namespace conflict less with those in the standard namespace, open the namespace to simplify the code
//Resource path
#define WWWROOT "./wwwroot"
//Define two pointers to two tables in the database
order_sys::TableDish* dishptr = NULL;
order_sys::TableOrder* orderptr = NULL;
//Dish insertion
void DishInsert(const Request& req, Response& rsp){
//Business processing
Json::Value dish;
Json::Reader reader;
bool ret = reader.parse(req.body, dish);
//If the parsing fails, an error message is returned
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;
}
//Insert data if parsing is successful
ret = dishptr->Insert(dish);
//If the insertion fails, an error message needs to be returned
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;
}
//Set response information
//The response status code httplib is set to 200 by default, so the following line can be written or not
rsp.status = 200;
return;
}
//Dish deletion
void DishDelete(const Request& req, Response& rsp){
//matches is an array used to store the information captured by the brackets in the request. matches[0] stores the entire request, and then the arranged captured information
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;
}
//Dish modification
void DishUpdate(const Request& req, Response& rsp){
int dish_id = std::stoi(req.matches[1]);
//Parse text
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;
}
//Dish query (all)
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;
}
//Dish query (single)
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;
}
//Order insertion
void OrderInsert(const Request& req, Response& rsp){
//Business processing
Json::Value order;
Json::Reader reader;
bool ret = reader.parse(req.body, order);
//If the parsing fails, an error message is returned
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;
}
//Insert data if parsing is successful
ret = orderptr->Insert(order);
//If the insertion fails, an error message needs to be returned
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;
}
//Set response information
//The response status code httplib is set to 200 by default, so the following line can be written or not
rsp.status = 200;
return;
}
//Order deletion
void OrderDelete(const Request& req, Response& rsp){
//matches is an array used to store the information captured by the brackets in the request. matches[0] stores the entire request, and then the arranged captured information
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;
}
//Order modification
void OrderUpdate(const Request& req, Response& rsp){
int order_id = std::stoi(req.matches[1]);
//Parse text
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;
}
//Order query (all)
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;
}
//Order query (single)
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);
//Dish request
server.Post("/dish", DishInsert);
//Regular expressions are used to verify some special characters
server.Delete(R"(/dish/(\d+))", DishDelete);
server.Put(R"(/dish/(\d+))", DishUpdate);
server.Get("/dish", DishGetAll);
server.Get(R"(/dish/(\d+))", DishGetOne);
//Order request
server.Post("/order", OrderInsert);
//Regular expressions are used to verify some special characters
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;
}