项目名称:点餐系统
项目描述:使用户可以通过浏览器访问服务器获取菜品信息并进行点餐;以及可以使管理员通过浏览访问服务器实现订单以及菜品的管理。
概要设计:框架设计——不太严谨的MVC框架
model:数据管理模块——管理数据(菜品,订单),外界想要访问数据必须通过这个模块完成,不能直接访问。
view:视图界面模块——浏览器前端界面,用户或者管理员的操作都是通过前端界面完成。
controller:业务控制模块——搭建服务器针对前端的请求进行对应业务处理。
详细设计
数据管理模块:
1.数据的存储:MySQL数据库
2.数据库表的设计:
菜品信息表:菜品ID;菜品名称;菜品单价;添加时间
订单信息表:订单ID;订单菜品;订单状态;修改时间
3.数据管理模块代码的设计
菜品数据类:添加菜品,删除菜品,修改菜品,获取菜品(单个、所有)
订单数据类:添加订单,删除订单,修改订单(菜单,状态),获取订单(所有、指定ID)
业务控制模块:
接收客户端请求,进行处理满足用户需求
1.搭建服务器:HTTP服务器(采用httplib库搭建)
2.通信接口设计:什么样的请求对应什么样的业务处理和响应
1.静态页面请求:html页面(以及依赖的css/js文件)
index.html——菜品信息的展示以及下单界面
2.动态数据请求:菜品数据,订单数据
通信接口采用restful风格接口设计
基于http协议,使用json格式定义正文序列化方式
定义操作类型:新增—POST;删除—DELETE;修改—PUT;获取—GET
前端界面模块:
html的编写渲染
实现方法:html+css+js
环境搭建
1.MySQL服务安装:按照博客一步步走下去即可
博客链接:https://zhuanlan.zhihu.com/p/49046496
2.gcc/g++升级
sudo yum -y install epel-release
sudo yum install centos-release
yum install devtoolset-7-gcc devtoolset-7-gcc-c++
source /opt/rh/devtoolset-7/enable
最好将source加载配置这句指令,写入到~/.bashrc这个文件中
3.安装jsoncpp开发包
sudo yum install jsoncpp-devel
MySQL代码操作
初始化操作:
1.初始化操作句柄
//初始化句柄,mysql_init(句柄地址)
MYSQL* mysql = NULL;
mysql = mysql_init(NULL);
if(mysql == NULL)
{
std::cout << "mysql init failed!\n";
return NULL;
}
2.通过句柄连接mysql服务器
#define MYSQL_SERVER "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PASSWD "158470"
#define MYSQL_DBNAME "order_sys"
//连接服务器,mysql_real_connect(句柄,服务器ip,用户名,密码,数据库名称,端口(默认为0),套接字文件(通常置NULL),客户端标志(通常置0))
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;
}
3.设置客户端字符集(utf8)
//设置字符集,mysql_set_char_set(句柄,字符集名称)
if (mysql_set_character_set(mysql,"utf8")!=0){
std::cout << mysql_error(mysql)<<std::endl;
return NULL;
}
4.选择使用的数据库
//选择数据库,mysql_select_db(句柄,数据库名称)
//mysql_select_db(mysql,MYSQL_DBNAME),连接服务器时已经默认选择了,所以不需要执行该语句
数据操作(以菜品管理为例):
5.执行语句
static bool MysqlQuery(MYSQL *mysql,const std::string &sql)
{
//mysql_query(句柄,sql语句)
//函数c_str()就是将C++的string转化为C的字符串数组,生成一个const char*指针,指向字符串的首地址
if(mysql_query(mysql,sql.c_str())!=0)
{
std::cout<<sql<<std::endl;
std::cout<<mysql_error(mysql)<<std::endl;
return false;
}
return true;
}
新增
bool Insert(const Json::Value &dish){
//组织sql语句,insert tbname values()
#define DISH_INSERT "insert tb_dish values(null,'%s',%d,now());"
char str_sql[4096]={0};
//sprintf将dish的name和price放入DISH_INSERT中,再将DISH_INSERT保存在str_sql内。
sprintf(str_sql,DISH_INSERT,dish["name"].asCString(),
dish["price"].asInt());
//执行sql语句,_mysql为初始化完成的句柄
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"].asCString(),
dish["price"].asInt(),
dish["id"].asInt());
return MysqlQuery(_mysql,str_sql);
}
查(ALL)
bool SelectAll(Json::Value *dishes){
#define DISH_SELECTALL "select * from tb_dish;"
_mutex.lock();//互斥锁
bool ret = MysqlQuery(_mysql,DISH_SELECTALL);
if(ret == NULL){
_mutex.unlock();
return false;
}
MYSQL_RES *res = mysql_store_result(_mysql);//保存查询结果到本地,_mysql为初始化完成的句柄
_mutex.unlock();
if(res == NULL){
std::cout<<"store result failed!\n";
return false;
}
int num = mysql_num_rows(res);//获取结果集中数据的条数(行数),res为保存到本地的结果集地址
for(int i = 0;i<num;i++){
MYSQL_ROW row = mysql_fetch_row(res);//遍历结果集
//返回值是个char**的指针,将每一条数据做成了字符串指针数组,例如row[0]为第0列;
//并且这个接口会保存当前读取结果位置,每次获取的都是下一条数据
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);//把遍历到的结果放入dishes中
}
mysql_free_result(res);//释放结果集
return true;
}
查(ONE)
bool SelectOne(int dish_id,Json::Value *dish){
#define DISH_SELECTONE "select * from tb_dish where id = %d;"
char sql_str[4096] = {0};
sprintf(sql_str,DISH_SELECTONE,dish_id);
_mutex.lock();
bool ret = MysqlQuery(_mysql,sql_str);
if(ret == 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<<"result error\n";
mysql_free_result(res);
return false;
}
MYSQL_ROW row = mysql_fetch_row(res);
(*dish)["id"] = dish_id;
(*dish)["name"] = row[1];
(*dish)["price"] = std::stoi(row[2]);
(*dish)["ctime"] = row[3];
mysql_free_result(res);
return true;
}
};
6.关闭句柄,释放资源
static void MysqlRelease(MYSQL *mysql){
if(mysql != NULL){
mysql_close(mysql);
}
return;
}
7.获取接口执行失败原因
const char *mysql_error(MYSQL *mysql)
//例如连接服务器时就使用了这个接口
业务处理模块
使用httplib搭建服务器框架(以菜品管理为例)
Server server;//用httplib构建服务器
auto ret = server.set_mount_point("/","./www");//设置静态文件目录
server.Post("/dish",DishInsert);
server.Delete(R"(/dish/(\d+))",DishDelete);
//正则表达式,\d表示0-9,+表示匹配多个字符,\d+表示匹配一个数字字符一次或多次
//正则表达式 ()表示捕捉匹配括号中规则的文本
//R"()" 表示括号中的字符串去除特殊字符的特殊含义,比如"\"
server.Put(R"(/dish/(\d+))",DishUpdate);
server.Get(R"(/dish)",DishGetAll);
server.Get(R"(/dish/(\d+))",DishGetOne);
新增
void DishInsert(const Request &req,Response &rsp){
//1.业务处理
// (1)解析正文(json反序列化),得到菜品信息
Json::Value dish;
Json::Reader reader;
bool ret = reader.parse(req.body,dish);//将请求中的正文反序列化,输入到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");//设置数据类型为Json格式
std::cout<<"insert dish parse error\n";
}
// (2)将菜品信息插入数据库
ret = tbdish->Insert(dish);//tbdish为菜品信息表
if(ret == false){
Json::Value reason;
Json::FastWriter writer;
reason["result"] = false;
reason["reason"] = "mysql insert dish failed";
rsp.status = 500;
rsp.body = writer.write(reason);
rsp.set_header("Content-Type","application/json");
}
//2.设置响应信息,响应状态码httplib默认设置200
rsp.status = 200;
}
删除
void DishDelete(const Request &req,Response &rsp){
//1.获取要删除的菜品ID /dish/id
int dish_id = std::stoi(req.matches[1]);//matches[0]里放的是/dish/id,matches[1]里放的是/id
//2.数据库删除操作
bool ret = tbdish->Delete(dish_id);
if(ret == false){
std::cout << "delete dish mysql error\n";
rsp.status = 500;
}
}
修改
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<<"update parse dish info failed\n";
}
dish["id"] = dish_id;
ret = tbdish->Update(dish);
if(ret == false){
std::cout << "update mysql dish error\n";
rsp.status = 500;
}
}
查看(ALL)
void DishGetAll(const Request &req,Response &rsp){
Json::Value dishes;
bool ret = tbdish->SelectAll(&dishes);
if(ret == false){
std::cout<<"selectall mysql dish error\n";
rsp.status = 500;
}
Json::FastWriter writer;
rsp.body = writer.write(dishes);
rsp.set_header("Content-Type","application/json");
}
查看(ONE)
void DishGetOne(const Request &req,Response &rsp){
int dish_id = std::stoi(req.matches[1]);//获取请求中的菜品id
Json::Value dishes;
bool ret = tbdish->SelectOne(dish_id,&dishes);//将菜品id为dish_id的菜品信息输入到dishes中
if(ret == false){
std::cout<<"selectone mysql dish error\n";
rsp.status = 500;
}
Json::FastWriter writer;
rsp.body = writer.write(dishes);//把dishes中的信息序列化为Json格式到回应的正文中
}
下面是用postman进行的测试。
前端界面模块
这里下载了一个免费的html模板。下面是主要改动的代码,以用户端为例。
<h1 style="text-align:center">菜馆</h1>
<div class="mt-3 mb-5">
<h3 style="text-align:center">菜单</h3>
<table class="table table-hover">
<thead>
<tr>
<th>菜名</th>
<th>单价</th>
<th>勾选下单</th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="3" style="text-align:right">//合并3个单元格
<button type="button" class="btn btn-primary" v-on:click="insert_order()">下单</button>//创建下单按钮,按下后即执行insert_order函数
总价:{{get_total()}}//计算总价函数
</th>
</tr>
</tfoot>
<tbody>
<tr v-for="dish in dishes">//v-for循环指令,dishes会在data中查找对应名字的属性,dish代表每个访问到的元素,通过该指令将dishes对象绑定到表格内容中
<td>{{dish.name}}</td>
<td>{{dish.price/100}}</td>
<td>
<div class="from-check">
<label class="from-check-label">
<input class="from-check-input" type="checkbox" v-model="dish.selected">下单
</label>//创建复选框,v-model为表单绑定指令,该指令表示将下单按钮与表单内的菜品数据绑定,勾选下单后会将对应菜品数据也选上,为get_total函数提供参数
</div>
</td>
</tr>
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>//引入Vue.js
<script>
//实例化vue对象
var app = new Vue({
el:'#myorder',//选择器,提供一个页面上已存在的DOM元素作为Vue实例的挂载目标
data:{
dishes:[
{id:1,name:"红烧肉",price:"1700",ctime:"2021-9-9 8:00:00"},
{id:2,name:"红烧茄子",price:"1200",ctime:"2021-9-9 8:00:00"},
{id:3,name:"宫保鸡丁",price:"1700",ctime:"2021-9-9 8:00:00"},
]
},
methods:{
get_total:function()
{
var total = 0;
for(i=0;i<this.dishes.length;i++)
{
if(this.dishes[i].selected)
{
total+=(this.dishes[i].price/100);
}
}
return total;
},
insert_order:function(){
var order = {
dishes:[]
}
for(i=0;i<this.dishes.length;i++){
if(this.dishes[i].selected){
order.dishes.push(this.dishes[i].id);
}
}
//ajax 就是一个http客户端
$.ajax({
url:"/order",
type:"post",
data:JSON.stringify(order),//序列化成为json格式的字符串
//res:包含来自请求的结果数据
//status:包含请求的状态
//xhr:包含 XMLHttpRequest 对象,该对象用于在后台与服务器交换数据
success:function(res,status,xhr){
if(xhr.status==200)
alert("下单成功");
}
})
},
get_alldishes:function(){
$.ajax({
url:"/dish",
type:"get",
context:this,
//success方法是一个回调函数,获取从后台传来的数据
success:function(res,status,xhr){
this.dishes=JSON.parse(res);
}
})
}
}
});
app.get_alldishes();//在全局增加一个获取菜单的调用,使网页加载的时候就可获得菜品信息
</script>
页面展示如下
在数据库中查看订单信息表验证下单成功。