整体架构
- 服务器
- 顾客客户端
- 商家客户端
预计实现功能:
- 顾客扫描餐桌上的二维码进入顾客客户端, 进行点菜
- 订单提交到服务器上由商家客户端获取到
- 商家通过商家客户端可以进行订单及菜品的管理
数据库设计
创建数据库
create database if not exists order_system;
use order_system;
创建菜单表
每行记录一个菜品信息, price 单位为分,由商家维护, 由用户浏览
create table if not exists dish_table(dish_id int unsigned not null primary key
auto_increment,
name varchar(50),
price int);
插入几个测试数据,可自定义
insert into dish_table values(null, '宫保鸡丁', 1800);
insert into dish_table values(null, '红烧肉', 1800);
insert into dish_table values(null, '土豆丝',1800);
创建订单表
每行是一个订单,信息由用户提交,商家进行浏览
create table if not exists order_table(order_id int unsigned not null primary key
auto_increment,
table_id varchar(50),
time varchar(50),
dish_ids varchar(1024),
state int); // 表示订单状态. 0 表示订单进行中,
1 表示订单完结
table_id 桌号
time 下单时间, 形如 2019-05-15 12:00
dish_ids 用户点的菜品 id 列表, 为 json 格式字符串, 形如 [1, 2, 3]
插入测试数据
insert into order_table values(null, '1', '2019-05-10 12:00', '[1,2,3]', 1);
insert into order_table values(null, '2', '2019-05-15 13:00', '[1,3]', 0);
使用 MySQL API操作数据库
注意:
MySQL 提供的 API 除了 mysql_real_connect, 其他的都是线程安全的.
创建一个 mysql_test 目录
Makefile 编译选项
// -L /usr/lib64/mysql -lmysqlclient
插入 mysql_insert.cc
#include <cstdio>
#include <cstdlib>
#include <mysql/mysql.h>
int main() {
// 1. 初始化句柄
MYSQL* connect_fd = mysql_init(NULL);
// 2. 建立链接
// mysql_init 返回的指针
// 主机地址
// 用户名
// 密码
// 数据库名
// 端口号
// unix_socket
// client_flag
if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "",
"order_system", 3306, NULL, 0) == NULL) {
printf("连接失败! %s\n", mysql_error(connect_fd));
return 1;
}
// 3. 设置编码格式
mysql_set_character_set(connect_fd, "utf8");
// 4. 拼装 SQL 语句
char sql[1024 * 4] = {0};
char name[] = "京酱肉丝";
int price = 2000;
sprintf(sql, "insert into menu values(null, '%s', %d)", name, price);
// 5. 执行 SQL 语句
int ret = mysql_query(connect_fd, sql);
if (ret < 0) {
printf("执行 sql 失败! %s\n", mysql_error(connect_fd));
return 1;
}
// 6. 关闭句柄
mysql_close(connect_fd);
printf("执行成功!\n");
return 0; }
查询 mysql_select.cc
#include <cstdio>
#include <cstdlib>
#include <mysql/mysql.h>
int main() {
// 1. 初始化句柄
MYSQL* connect_fd = mysql_init(NULL);
// 2. 建立链接
// mysql_init 返回的指针
// 主机地址
// 用户名
// 密码
// 数据库名
// 端口号
// unix_socket
// client_flag
if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "",
"order_system", 3306, NULL, 0) == NULL) {
printf("连接失败! %s\n", mysql_error(connect_fd));
return 1;
}
// 3. 设置编码格式
mysql_set_character_set(connect_fd, "utf8");
// 4. 拼装 SQL 语句
char sql[1024 * 4] = {0};
int price = 2000;
sprintf(sql, "select * from menu");
// 5. 执行 SQL 语句
int ret = mysql_query(connect_fd, sql);
if (ret < 0) {
printf("执行 sql 失败! %s\n", mysql_error(connect_fd));
return 1;
}
// 6. 遍历查询结果
MYSQL_RES* result = mysql_store_result(connect_fd);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(connect_fd));
return 1;
}
// a) 获取行数和列数
int rows = mysql_num_rows(result);
int fields = mysql_num_fields(result);
printf("rows: %d, fields: %d\n", rows, fields);
// b) 打印结果
for (int i; i < rows; ++i) {
MYSQL_ROW row = mysql_fetch_row(result);
for (int j = 0; j < fields; ++j) {
printf("%s\t", row[j]);
}
printf("\n");
}
// 7. 关闭句柄
mysql_close(connect_fd);
printf("执行成功!\n");
return 0; }
服务器端设计
菜品管理 API 设计
新增菜品(商家)
//请求:
POST /dish
{
"name": "宫保鸡丁",
"price": 1800
}
//响应:
HTTP/1.1 200 OK
{
"ok": true,
"dish_id": 1,
}
查看所有菜品(商家/用户)
//请求:
GET /dish
//响应:
HTTP/1.1 200 OK
[
{
dish_id: 1,
name: "宫保鸡丁",
price: 1800
}
]
删除菜品(商家)
//请求:
DELETE /dish/:dish_id
//响应:
HTTP/1.1 200 OK
{
"ok": true
}
修改菜品(商家)
//请求:
PUT /dish/:dish_id
{
"name": "京酱肉丝",
"price": 1900
}
//响应:
HTTP/1.1 200 OK
{
"ok": true
}
订单管理 API 设计
提交订单(用户)
//请求:
POST /order
{
"table_id": "1",
"time": "2019-05-15 12:00",
"dish_ids": [1, 2]
}
//响应:
HTTP/1.1 200 OK
{
"ok": true
}
修改订单状态(商家)
//请求:
PUT /order/:order_id
{
"state": 0,
}
//响应:
HTTP/1.1 200 OK
{
"ok": true
}
获取订单(商家)
- 在实际的生活场景中,我们应该补充上相应的过滤功能(例如按时间, 桌号, 显示页数等规则过滤)。
- 否则由于和 MySQL 的交互量过大就会导致服务器相应速度很慢。
//请求:
GET /order
//响应:
HTTP/1.1 200 OK
[
{
"order_id": 1,
"table_id": "1",
"time": "2019-05-15 12:00",
"dishes": [
{
"dish_id": 1,
"name": "宫保鸡丁",
"price": 1800
},
{
"dish_id": 2,
"name": "京酱肉丝",
"price": 1900
}
],
"state": 0,
"consume": 3700, // 表示该订单的价格
},
{
"order_id": 2,
"table_id": "2",
"time": "2019-05-16 12:00",
"dishs": [
{
"dish_id": 1,
"name": "宫保鸡丁",
"price": 1800
},
],
"state": 1,
"consume": 1800, // 表示该订单的价格
}
]
服务端实现(一)
封装数据库操作
基本代码框架
db.hpp
namespace order_system {
static MYSQL* MySQLInit() {
MYSQL* connect_fd = mysql_init(NULL);
if (mysql_real_connect(connect_fd, "127.0.0.1", "root", "",
"order_system", 3306, NULL, 0) == NULL) {
printf("连接失败! %s\n", mysql_error(connect_fd));
return NULL;
}
mysql_set_character_set(connect_fd, "utf8");
return connect_fd;
}
static void MySQLRelease(MYSQL* mysql) {
mysql_close(mysql);
}
class DishTable {
public:
DishTable(MYSQL* mysql) : mysql_(mysql) { }
bool Insert(const Json::Value& dish) {
}
bool SelectAll(Json::Value* dishes) {
}
bool SelectOne(int32_t dish_id, Json::Value* dish) {
}
bool Update(const Json::Value& dish) {
}
bool Delete(int dish_id) {
}
private:
MYSQL* mysql_;
};
class OrderTable {
public:
OrderTable(MYSQL* mysql) : mysql_(mysql) { }
bool SelectAll(Json::Value* orders) {
}
bool Insert(const Json::Value& order) {
}
bool ChangeState(const Json::Value& order) {
}
private:
MYSQL* mysql_;
};
} // end order_system
使用 JSON 作为数据交互格式
json 出自 JavaScript, 是一种非常方便的键值对数据组织格式
C++ 中可以使用 jsoncpp这个库来解析和构造json数据
yum install jsoncpp-devel
实现 DishTable 类封装对菜品的数据库操作
class DishTable {
public:
DishTable(MYSQL* mysql) : mysql_(mysql) { }
bool Insert(const Json::Value& dish) {
char sql[1024 * 4] = {0};
sprintf(sql, "insert into dish_table values(null, '%s', %d)", dish["name"].asCString(),
dish["price"].asInt());
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
bool SelectAll(Json::Value* dishes) {
char sql[1024 * 4] = {0};
sprintf(sql, "select * from dish_table");
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! %s\n", mysql_error(mysql_));
return false;
}
MYSQL_RES* result = mysql_store_result(mysql_);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(mysql_));
return false;
}
int rows = mysql_num_rows(result);
for (int i = 0; i < rows; ++i) {
MYSQL_ROW row = mysql_fetch_row(result);
Json::Value dish;
dish["dish_id"] = atoi(row[0]);
dish["name"] = row[1];
dish["price"] = atoi(row[2]);
// 遍历结果依次加入到 dishes 中
dishes->append(dish);
}
return true;
}
bool SelectOne(int32_t dish_id, Json::Value* dish) {
char sql[1024 * 4] = {0};
sprintf(sql, "select * from dish_table where dish_id = %d", dish_id);
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! %s\n", mysql_error(mysql_));
return false;
}
MYSQL_RES* result = mysql_store_result(mysql_);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(mysql_));
return false;
}
int rows = mysql_num_rows(result);
if (rows != 1) {
printf("查找结果不为 1 条. rows = %d!\n", rows);
return false;
}
for (int i = 0; i < rows; ++i) {
MYSQL_ROW row = mysql_fetch_row(result);
(*dish)["dish_id"] = atoi(row[0]);
(*dish)["name"] = row[1];
(*dish)["price"] = atoi(row[2]);
break; // 只获取一条数据. 按理说数据库中相同 id 的就一个
}
return true;
}
bool Update(const Json::Value& dish) {
char sql[1024 * 4] = {0};
sprintf(sql, "update dish_table SET name='%s', price=%d where dish_id=%d",
dish["name"].asCString(),
dish["price"].asInt(), dish["dish_id"].asInt());
// DEBUG 用于调试
// printf("[SQL] %s\n", sql);
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
bool Delete(int dish_id) {
char sql[1024 * 4] = {0};
sprintf(sql, "delete from dish_table where dish_id=%d", dish_id);
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
private:
MYSQL* mysql_;
};
创建 OrderTable 类封装对订单的数据库操作
class OrderTable {
public:
OrderTable(MYSQL* mysql) : mysql_(mysql) { }
bool SelectAll(Json::Value* orders) {
char sql[1024 * 4] = {0};
sprintf(sql, "select * from order_table");
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! %s\n", mysql_error(mysql_));
return false;
}
MYSQL_RES* result = mysql_store_result(mysql_);
if (result == NULL) {
printf("获取结果失败! %s\n", mysql_error(mysql_));
return false;
}
int rows = mysql_num_rows(result);
for (int i = 0; i < rows; ++i) {
MYSQL_ROW row = mysql_fetch_row(result);
Json::Value order;
order["order_id"] = atoi(row[0]); // 注意, order_id 是数字, table_id 是字符串
order["table_id"] = row[1];
order["time"] = row[2];
// [重要] 需要在上层把 dish_ids 替换成 dishes(不光是菜品 ID 还有菜品详细信息)
order["dish_ids_str"] = row[3];
order["state"] = row[4];
// 遍历结果依次加入到 dishes 中
orders->append(order);
}
return true;
}
bool Insert(const Json::Value& order) {
char sql[1024 * 4] = {0};
// 此处 dish_ids 需要先转成字符串(本来是一个对象,
// 形如 [1, 2, 3]. 如果不转, 是无法 asCString)
sprintf(sql, "insert into order_table values(null, '%s', '%s', '%s', %d)",
order["table_id"].asCString(), order["time"].asCString(),
order["dish_ids_str"].asCString(), order["state"].asInt());
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
bool ChangeState(const Json::Value& order) {
char sql[1024 * 4] = {0};
sprintf(sql, "update order_table set state = %d where order_id = %d",
order["state"].asInt(), order["order_id"].asInt());
int ret = mysql_query(mysql_, sql);
if (ret != 0) {
printf("执行 sql 失败! sql=%s, %s\n", sql, mysql_error(mysql_));
return false;
}
return true;
}
private:
MYSQL* mysql_;
};
测试数据库操作
db_test.cc 测试上述数据库操作是否封装正确
void TestDishTable() {
bool ret = false;
// 更友好的格式化显示 Json
Json::StyledWriter writer;
MYSQL* mysql = MySQLInit();
Json::Value dish;
dish["name"] = "红烧肉";
dish["price"] = 2300;
std::cout << "==============测试插入=====================" << std::endl;
DishTable dish_table(mysql);
ret = dish_table.Insert(dish);
std::cout << "Insert: " << ret << std::endl;
std::cout << "==============测试查找=====================" << std::endl;
Json::Value dishes;
ret = dish_table.SelectAll(&dishes);
std::cout << "SelectAll: " << ret << std::endl
<< writer.write(dishes) << std::endl;
std::cout << "==============测试更新=====================" << std::endl;
dish["dish_id"] = 5;
dish["name"] = "毛家红烧肉";
dish["price"] = 2700;
Json::Value dish_out;
ret = dish_table.Update(dish);
std::cout << "Update: " << ret << std::endl;
ret = dish_table.SelectOne(5, &dish_out);
std::cout << "SelectOne: " << ret << std::endl
<< writer.write(dish_out) << std::endl;
std::cout << "==============测试删除=====================" << std::endl;
int dish_id = 6;
ret = dish_table.Delete(dish_id);
std::cout << "Delete: " << ret << std::endl;
MySQLRelease(mysql);
}
void TestOrderTable() {
bool ret = false;
Json::StyledWriter writer;
MYSQL* mysql = MySQLInit();
OrderTable order_table(mysql);
std::cout << "==============测试插入=====================" << std::endl;
Json::Value order;
order["table_id"] = "忠义堂";
order["time"] = "2019-05-17 12:00";
order["dish_ids"] = "1,2,3";
order["state"] = 0;
ret = order_table.Insert(order);
std::cout << "Insert: " << ret << std::endl;
std::cout << "==============测试查看=====================" << std::endl;
Json::Value orders;
ret = order_table.SelectAll(&orders);
std::cout << "SelectAll: " << ret << std::endl
<< writer.write(orders) << std::endl;
std::cout << "==============测试修改状态=====================" << std::endl;
Json::Value state;
state["order_id"] = 3;
state["state"] = 1;
ret = order_table.ChangeState(state);
std::cout << "ChangeState ret:" << ret << std::endl;
MySQLRelease(mysql);
}
int main() {
// TestDishTable();
TestOrderTable();
return 0;
}
Makefile
FLAGS=-std=c++11 -L/usr/lib64/mysql -lmysqlclient -ljsoncpp -lpthread -g
.PHONY:all
all:db_test
db_test:db_test.cc db.hpp
g++ db_test.cc -o db_test $(FLAGS)
.PHONY:clean
clean:
rm db_test
服务器端实现(二)
使用 httplib搭建服务器框架
使用 cpp-httplib实现一个 “hello world”
#include "httplib.h"
int main() {
using namespace httplib;
Server server;
server.Get("/", [](const Request& req,Response& resp) {
(void)req;
resp.set_content("<html>hello</html>", " text/html");
});
server.set_base_dir("./wwwroot");
server.listen("0.0.0.0", 9092);
return 0;
}
编译选项
g++ main.cc -lpthread -std=c++11
整体 API 框架
MYSQL* mysql = NULL;
int main() {
using namespace httplib;
using namespace order_system;
Server server;
// 1. 数据库客户端初始化和释放
mysql = MySQLInit();
signal(SIGINT, [](int) { MySQLRelease(mysql); exit(0);});
DishTable dish_table(mysql);
OrderTable order_table(mysql);
// 2. 设置路由
// 新增菜品
server.Post("/dish", [&dish_table](const Request& req, Response& resp) {
});
// 查看所有菜品
server.Get("/dish", [&dish_table](const Request& req, Response& resp) {
});
// 删除菜品
// raw string(c++ 11), 转义字符不生效. 用来表示正则表达式正好合适
// 关于正则表达式, 只介绍最基础概念即可. \d+ 表示匹配一个数字
// http://help.locoy.com/Document/Learn_Regex_For_30_Minutes.htm
server.Delete(R"(/dish/(\d+))", [&dish_table](const Request& req, Response& resp) {
});
// 修改菜品
server.Put(R"(/dish/(\d+))", [&dish_table](const Request& req, Response& resp) {
});
// 新增订单
server.Post("/order", [&order_table](const Request& req, Response& resp) {
});
// 修改订单状态
server.Put(R"(/order/(\d+))", [&order_table](const Request& req, Response& resp) {
});
// 获取订单
server.Get("/order", [&order_table, &dish_table](const Request& req, Response& resp) {
});
// 获取用户客户端, 匹配桌子 id
server.Get(R"(/client/table/(\S+))", [](const Request& req, Response& resp) {
});
// 设置静态文件目录
server.set_base_dir("./wwwroot");
server.listen("0.0.0.0", 9092);
return 0;
}
实现菜品管理 API
实现新增菜品
server.Post("/dish", [&dish_table](const Request& req, Response& resp) {
LOG(INFO) << "新增菜品: " << req.body << std::endl;
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
// 1. 请求解析成 Json 格式
bool ret = reader.parse(req.body, req_json);
if (!ret) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "parse Request failed!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 2. 进行参数校验
if (req_json["name"].empty() || req_json["price"].empty()) {
resp_json["ok"] = false;
resp_json["reason"] = "Request has no name or price field!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 3. 调用数据库接口进行操作数据
ret = dish_table.Insert(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "Insert failed!\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("/dish", [&dish_table](const Request& req, Response& resp) {
LOG(INFO) << "查看所有菜品: " << req.body << std::endl;
Json::Reader reader;
Json::FastWriter writer;
Json::Value resp_json;
// 对于查看菜品来说 API 没有请求参数, 不需要解析参数和校验了, 直接构造结果即可
// 1. 调用数据库接口查询数据
Json::Value dishes;
bool ret = dish_table.SelectAll(&dishes);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "SelectAll failed!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 2. 构造响应结果
resp.set_content(writer.write(dishes), "application/json");
return;
});
实现删除菜品
// 删除菜品
// raw string(c++ 11), 转义字符不生效. 用来表示正则表达式正好合适
// 关于正则表达式, 只介绍最基础概念即可. \d+ 表示匹配一个数字
// http://help.locoy.com/Document/Learn_Regex_For_30_Minutes.htm
server.Delete(R"(/dish/(\d+))", [&dish_table](const Request& req, Response& resp) {
Json::Value resp_json;
Json::FastWriter writer;
// 1. 解析获取 dish_id
// 使用 matches[1] 就能获取到 dish_id
// LOG(INFO) << req.matches[0] << "," << req.matches[1] << "\n";
int dish_id = std::stoi(req.matches[1]);
LOG(INFO) << "删除指定菜品: " << dish_id << std::endl;
// 2. 调用数据库接口删除菜品
bool ret = dish_table.Delete(dish_id);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "SelectAll failed!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
}
// 3. 包装正确的响应
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
});
实现修改菜品
// 修改菜品
server.Put(R"(/dish/(\d+))", [&dish_table](const Request& req, Response& resp) {
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
// 1. 获取到菜品 id
int dish_id = std::stoi(req.matches[1]);
LOG(INFO) << "修改菜品 " << dish_id << "|" << req.body << std::endl;
// 2. 解析菜品信息
bool ret = reader.parse(req.body, req_json);
if (!ret) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "parse Request failed!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// [注意!!] 一定要记得补充上 dish_id
req_json["dish_id"] = dish_id;
// 3. 校验菜品信息
if (req_json["name"].empty() || req_json["price"].empty()) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "Request has no name or price!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 4. 调用数据库接口进行修改
ret = dish_table.Update(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "Update failed!\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;
})
实现订单管理 API
实现新增订单
server.Post("/order", [&order_table](const Request& req, Response& resp) {
LOG(INFO) << "新增订单: " << req.body << std::endl;
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
// 1. 请求解析成 Json 格式
bool ret = reader.parse(req.body, req_json);
if (!ret) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "parse Request failed!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 2. 校验订单格式
if (req_json["table_id"].empty() || req_json["time"].empty()
|| req_json["dish_ids"].empty()) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "Request has no table_id or time or dish_ids!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 3. 转换接口, 把 dish_ids 先转成字符串, 存到 dish_ids_str 中.
// 后续的数据库插入拿着这个 dish_ids_str 来进行插入
const Json::Value& dish_ids = req_json["dish_ids"];
req_json["dish_ids_str"] = writer.write(dish_ids);
// 4. 调用数据库接口, 插入订单数据
ret = order_table.Insert(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "OrderTable Insert failed!\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");
})
实现修改订单状态
server.Put(R"(/order/(\d+))", [&order_table](const Request& req, Response& resp) {
Json::Reader reader;
Json::FastWriter writer;
Json::Value req_json;
Json::Value resp_json;
// 1. 获取到订单 id
int order_id = std::stoi(req.matches[1]);
LOG(INFO) << "修改菜品 " << order_id << "|" << req.body << std::endl;
// 2. 解析菜品信息
bool ret = reader.parse(req.body, req_json);
if (!ret) {
// 请求解析出错, 返回一个400响应
resp_json["ok"] = false;
resp_json["reason"] = "parse Request failed!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// [注意!!] 一定要记得补充上 order_id
req_json["order_id"] = order_id;
// 3. 校验请求格式
if (req_json["order_id"].empty() || req_json["state"].empty()) {
resp_json["ok"] = false;
resp_json["reason"] = "Request has no order_id or state!\n";
resp.status = 400;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
// 4. 调用数据库接口进行修改
ret = order_table.ChangeState(req_json);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "ChangeState failed\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
resp_json["ok"] = true;
resp.set_content(writer.write(resp_json), "application/json");
return;
})
实现获取订单
server.Get("/order", [&order_table, &dish_table](const Request& req, Response& resp) {
// [重要!!] orders 中包含很多 order. 每个 order 又包含若干个 dish
LOG(INFO) << "查看所有订单: " << req.body << std::endl;
Json::Reader reader;
Json::FastWriter writer;
Json::Value resp_json;
// 对于查看菜品来说 API 没有请求参数, 不需要解析参数和校验了, 直接构造结果即可
// 1. 调用数据库接口查询数据
Json::Value orders;
bool ret = order_table.SelectAll(&orders);
if (!ret) {
resp_json["ok"] = false;
resp_json["reason"] = "SelectAll failed!\n";
resp.status = 500;
resp.set_content(writer.write(resp_json), "application/json");
return;
}
for (uint32_t order_index = 0; order_index < orders.size(); ++order_index) {
LOG(INFO) << "处理第 " << order_index << " 个订单 ing" << std::endl;
// 循环一次, 处理一个订单
Json::Value& order = orders[order_index];
// 2. 转换格式
Json::Value dish_ids;
ret = reader.parse(order["dish_ids_str"].asString(), dish_ids);
if (!ret) {
// 用宽松一点的方式处理. 此处不要因为一个订单的错误就影响大部分
LOG(ERROR) << "order_id: " << order["order_id"].asInt()
<< " has error dish_ids_str!" << std::endl;
continue;
}
// 3. 将 order 中的 dish_ids 构造成 dishes (再次查询数据库, 获取到每个菜品的详细信息)
GetDishes(dish_ids, &order["dishes"], dish_table);
// 4. 构造 consume 字段, 求这个订单的总价
order["consume"] = GetConsume(order["dishes"]);
}
// 5. 构造响应结果
resp.set_content(writer.write(orders), "application/json");
return;
})
实现辅助函数
// 把一个订单中的 dish_ids 转换成详细信息 dishes
// order 作为输入输出参数
bool GetDishes(const Json::Value& dish_ids, Json::Value* dishes,
order_system::DishTable& dish_table) {
for (uint32_t i = 0; i < dish_ids.size(); ++i) {
LOG(INFO) << "处理第 " << i << " 个菜品" << std::endl;
int dish_id = dish_ids[i].asInt();
Json::Value dish_info;
bool ret = dish_table.SelectOne(dish_id, &dish_info);
if (!ret) {
LOG(ERROR) << "dish_id = " << dish_id << " not found!\n";
continue;
}
dishes->append(dish_info);
}
return true;
}
int GetConsume(const Json::Value& dishes) {
int consume = 0;
for (uint32_t i = 0; i < dishes.size(); ++i) {
consume += dishes[i]["price"].asInt();
}
return consume;
}
使用 Postman 测试
在 Postman 中构造请求, 并验证即可