基于SpringBoot的外卖项目(详细开发过程)

时间:2023-02-23 15:53:09

基于SpringBoot+MyBatisPlus的外卖项目

申明: 未经许可,禁止以任何形式转载,若要引用,请标注链接地址。 全文共计86935字,阅读大概需要3分钟
更多学习内容, 欢迎关注我的个人公众号:不懂开发的程序猿

写在前面的几句话:
【警告】本篇博客较长,若引起阅读不适,建议收藏,稍后再读
【说明】该外卖项目是基于SpringBoot + MyBatisPlus为框架来开发的,前端页面框架都是现成的,只需要Java后端开发程序员编写对应的接口功能和服务,是一个很不错的练手项目。项目也非常适合作为大学生的课设,毕设
【文档】本篇博客详细介绍了该外卖项目的开发步骤,如果需要写课程设计或本科毕业论文文档,建议参考我下面这篇博客,内有详细的文档说明

点餐平台文档说明

1、软件开发整体介绍

软件开发流程

基于SpringBoot的外卖项目(详细开发过程)

角色分工

基于SpringBoot的外卖项目(详细开发过程)

2、外卖项目介绍

项目介绍

分为后台系统管理移动端两部分

后台系统管理供商家:对菜品、套餐、订单等进行管理维护等

移动端供消费者:在线浏览,添加购物车,下单 等

3步开发思路:

第一:主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。

第二:主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。

第三:主要针对系统进行优化升级,提高系统的访问性能。

产品展示

后台系统管理

登录页
基于SpringBoot的外卖项目(详细开发过程)

员工管理页
基于SpringBoot的外卖项目(详细开发过程)

分类管理
基于SpringBoot的外卖项目(详细开发过程)

菜品管理
基于SpringBoot的外卖项目(详细开发过程)

套餐管理
基于SpringBoot的外卖项目(详细开发过程)

订单明细
基于SpringBoot的外卖项目(详细开发过程)

移动端

登录页
基于SpringBoot的外卖项目(详细开发过程)

首页
基于SpringBoot的外卖项目(详细开发过程)

下单确认页
基于SpringBoot的外卖项目(详细开发过程)

下单成功页
基于SpringBoot的外卖项目(详细开发过程)

个人中心页
基于SpringBoot的外卖项目(详细开发过程)

地址管理页
基于SpringBoot的外卖项目(详细开发过程)

历史订单页
基于SpringBoot的外卖项目(详细开发过程)

技术选型

基于SpringBoot的外卖项目(详细开发过程)

功能结构

基于SpringBoot的外卖项目(详细开发过程)

角色

基于SpringBoot的外卖项目(详细开发过程)

3、开发环境的搭建

开发环境说明

工具 版本
后台 SpringBoot + MyBatisPlus
服务器 Tomcat 8.5.73
数据库 MySQL 8.0.28
Build Tools Maven 3.8.5
前端 Vue + ElementUI
开发工具 IDEA 2022.3
版本管理工具 Git

建库

基于SpringBoot的外卖项目(详细开发过程)

建表

/*
SQLyog Ultimate v12.08 (64 bit)
MySQL - 8.0.27 : Database - reggie
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`reggie` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `reggie`;

/*Table structure for table `address_book` */

DROP TABLE IF EXISTS `address_book`;

CREATE TABLE `address_book` (
  `id` bigint NOT NULL COMMENT '主键',
  `user_id` bigint NOT NULL COMMENT '用户id',
  `consignee` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '收货人',
  `sex` tinyint NOT NULL COMMENT '性别 0 女 1 男',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '手机号',
  `province_code` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '省级区划编号',
  `province_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '省级名称',
  `city_code` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '市级区划编号',
  `city_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '市级名称',
  `district_code` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '区级区划编号',
  `district_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '区级名称',
  `detail` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '详细地址',
  `label` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '标签',
  `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认 0 否 1是',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  `is_deleted` int NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='地址管理';

/*Data for the table `address_book` */

insert  into `address_book`(`id`,`user_id`,`consignee`,`sex`,`phone`,`province_code`,`province_name`,`city_code`,`city_name`,`district_code`,`district_name`,`detail`,`label`,`is_default`,`create_time`,`update_time`,`create_user`,`update_user`,`is_deleted`) values (1417414526093082626,1417012167126876162,'小明',1,'13812345678',NULL,NULL,NULL,NULL,NULL,NULL,'昌平区金燕龙办公楼','公司',1,'2021-07-20 17:22:12','2021-07-20 17:26:33',1417012167126876162,1417012167126876162,0),(1417414926166769666,1417012167126876162,'小李',1,'13512345678',NULL,NULL,NULL,NULL,NULL,NULL,'测试','家',0,'2021-07-20 17:23:47','2021-07-20 17:23:47',1417012167126876162,1417012167126876162,0),(1628270733663694849,1627997218788163586,'金阳',1,'17671789248',NULL,NULL,NULL,NULL,NULL,NULL,'湖北工业大学','学校',1,'2023-02-22 13:49:29','2023-02-22 13:49:32',1627997218788163586,1627997218788163586,0);

/*Table structure for table `category` */

DROP TABLE IF EXISTS `category`;

CREATE TABLE `category` (
  `id` bigint NOT NULL COMMENT '主键',
  `type` int DEFAULT NULL COMMENT '类型   1 菜品分类 2 套餐分类',
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '分类名称',
  `sort` int NOT NULL DEFAULT '0' COMMENT '顺序',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='菜品及套餐分类';

/*Data for the table `category` */

insert  into `category`(`id`,`type`,`name`,`sort`,`create_time`,`update_time`,`create_user`,`update_user`) values (1397844263642378242,1,'湘菜',1,'2021-05-27 09:16:58','2023-02-19 16:51:09',1,1),(1397844303408574465,1,'川菜',2,'2021-05-27 09:17:07','2021-06-02 14:27:22',1,1),(1397844391040167938,1,'粤菜',3,'2021-05-27 09:17:28','2021-07-09 14:37:13',1,1),(1413341197421846529,1,'饮品',11,'2021-07-09 11:36:15','2021-07-09 14:39:15',1,1),(1413342269393674242,2,'商务套餐',5,'2021-07-09 11:40:30','2021-07-09 14:43:45',1,1),(1413384954989060097,1,'主食',12,'2021-07-09 14:30:07','2021-07-09 14:39:19',1,1),(1413386191767674881,2,'儿童套餐',6,'2021-07-09 14:35:02','2021-07-09 14:39:05',1,1),(1627130608250593281,1,'湖北菜',4,'2023-02-19 10:19:02','2023-02-19 10:19:02',1,1);

/*Table structure for table `dish` */

DROP TABLE IF EXISTS `dish`;

CREATE TABLE `dish` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '菜品名称',
  `category_id` bigint NOT NULL COMMENT '菜品分类id',
  `price` decimal(10,2) DEFAULT NULL COMMENT '菜品价格',
  `code` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '商品码',
  `image` varchar(200) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '图片',
  `description` varchar(400) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
  `status` int NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
  `sort` int NOT NULL DEFAULT '0' COMMENT '顺序',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  `is_deleted` int NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_dish_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='菜品管理';

/*Data for the table `dish` */

insert  into `dish`(`id`,`name`,`category_id`,`price`,`code`,`image`,`description`,`status`,`sort`,`create_time`,`update_time`,`create_user`,`update_user`,`is_deleted`) values (1628019384179052546,'红烧肉',1397844263642378242,'3900.00','','90c9f385-5c18-491a-90f2-a4c8df198376.jpg','红烧肉',1,0,'2023-02-21 21:10:43','2023-02-21 21:10:43',1627997218788163586,1627997218788163586,0),(1628019727558332417,'麻辣鸡丝',1397844303408574465,'3900.00','','b19c64b2-378d-43d9-975f-2367bcc99e70.jpg','麻辣鸡丝',1,0,'2023-02-21 21:12:05','2023-02-21 21:12:05',1627997218788163586,1627997218788163586,0),(1628020011776954369,'辣子鸡',1627130608250593281,'4900.00','','d79ac164-8e43-4478-9d57-539764d1c5a8.jpg','来自鲜嫩美味的小鸡,值得一尝',1,0,'2023-02-21 21:13:13','2023-02-22 14:42:06',1627997218788163586,1627997218788163586,0),(1628020274659151874,'基围虾',1397844263642378242,'5900.00','','05010fb3-c055-41ff-8da8-4d941daea332.jpg','基围虾',1,0,'2023-02-21 21:14:15','2023-02-21 21:14:15',1627997218788163586,1627997218788163586,0),(1628020414488858625,'麻辣兔头',1397844303408574465,'12800.00','','703fb335-5593-49be-a629-e2522344212d.jpg','麻辣兔头',1,0,'2023-02-21 21:14:49','2023-02-21 21:14:49',1627997218788163586,1627997218788163586,0),(1628020624501854210,'邵阳猪血丸子',1397844391040167938,'5900.00','','79f6db2d-99d9-40f0-adee-a6750036d40e.jpg','邵阳猪血丸子',1,0,'2023-02-21 21:15:39','2023-02-21 21:15:39',1627997218788163586,1627997218788163586,0),(1628020855322791938,'烤乳猪',1397844391040167938,'9900.00','','8197826f-8bcd-44a1-9d0e-e742b0265e7d.jpeg','白切鸡',1,0,'2023-02-21 21:16:34','2023-02-21 21:19:39',1627997218788163586,1627997218788163586,0),(1628020978719215617,'脆皮烧鹅',1627130608250593281,'15800.00','','3d6188da-68f1-4299-89fd-04c7ab744110.jpeg','脆皮烧鹅',1,0,'2023-02-21 21:17:03','2023-02-22 14:41:44',1627997218788163586,1627997218788163586,0),(1628021120151146497,'上汤焗龙虾',1627130608250593281,'15800.00','','d2dbe897-3b8b-4e5c-99d8-b7ca159ba5b9.jpeg','上汤焗龙虾',1,0,'2023-02-21 21:17:37','2023-02-22 14:41:25',1627997218788163586,1627997218788163586,0),(1628021265471197185,'宫保鸡丁',1397844303408574465,'6900.00','','95f8b479-ae76-4107-9b08-3c62ae7f7fd0.jpg','宫保鸡丁',1,0,'2023-02-21 21:18:12','2023-02-22 14:40:23',1627997218788163586,1627997218788163586,0),(1628021771191013377,'白切鸡',1397844391040167938,'7900.00','','b5d537cf-0d6c-42ae-9210-94c981087d52.jpeg','白切鸡',1,0,'2023-02-21 21:20:12','2023-02-21 21:20:12',1627997218788163586,1627997218788163586,0),(1628022122845655041,'青椒炖鸡丁',1627130608250593281,'9900.00','','9b7494ee-1714-40ee-a94e-18c769678671.jpg','青椒炖鸡丁',1,0,'2023-02-21 21:21:36','2023-02-21 21:21:36',1627997218788163586,1627997218788163586,0),(1628022255993835522,'老火靓汤',1627130608250593281,'10900.00','','a72af50a-264c-4cf1-9da0-28e1eb98aa5c.jpeg','老火靓汤',1,0,'2023-02-21 21:22:08','2023-02-21 21:22:08',1627997218788163586,1627997218788163586,0),(1628022421907918850,'清蒸河鲜海鲜',1397844303408574465,'2900.00','','06b6c68f-db38-4bff-a8f4-131830291cec.jpg','清蒸河鲜海鲜',1,0,'2023-02-21 21:22:47','2023-02-21 21:22:47',1627997218788163586,1627997218788163586,0),(1628022523112280066,'王老吉',1413341197421846529,'500.00','','3e7ab2fe-01fa-4eb6-828e-b7998583a4e1.png','王老吉',1,0,'2023-02-21 21:23:11','2023-02-21 21:23:11',1627997218788163586,1627997218788163586,0),(1628022754352648193,'麻辣水煮鱼',1397844391040167938,'6500.00','','5f614bfa-f62c-4d5d-a4e3-852d6ce53d62.jpeg','麻辣水煮鱼',1,0,'2023-02-21 21:24:07','2023-02-22 14:40:56',1627997218788163586,1627997218788163586,0),(1628022918689673218,'清炒素食',1627130608250593281,'1900.00','','d8783e07-a8da-4e4e-8826-0c0e6990a08f.jpg','清炒素食',1,0,'2023-02-21 21:24:46','2023-02-21 21:24:46',1627997218788163586,1627997218788163586,0),(1628023021122965506,'啤酒',1413341197421846529,'1000.00','','bbfe22ba-9bd5-486a-ae83-22e108dddc47.png','啤酒',1,0,'2023-02-21 21:25:10','2023-02-21 21:25:10',1627997218788163586,1627997218788163586,0),(1628023133450620930,'麻辣鱼片',1397844303408574465,'3900.00','','ffdbeb37-0fbe-4190-a52a-3a16478f366e.jpg','麻辣鱼片',1,0,'2023-02-21 21:25:37','2023-02-21 21:25:37',1627997218788163586,1627997218788163586,0),(1628023363927625729,'烤乳鸽',1397844391040167938,'7900.00','','a1848e46-eb33-4957-bd77-039caaee79c2.jpeg','烤乳鸽',1,0,'2023-02-21 21:26:32','2023-02-21 21:26:32',1627997218788163586,1627997218788163586,0),(1628023490318782465,'大米饭',1413384954989060097,'500.00','','d19db29f-c016-410d-ac01-a3742ea1ea3c.png','大米饭',1,0,'2023-02-21 21:27:02','2023-02-21 21:27:02',1627997218788163586,1627997218788163586,0),(1628023694518472706,'辣子鸡丁',1397844263642378242,'3900.00','','c67657d7-4cbf-4d0c-b20c-7ab66ad52514.jpg','辣子鸡丁',1,0,'2023-02-21 21:27:51','2023-02-21 21:27:51',1627997218788163586,1627997218788163586,0),(1628023841423970305,'口味蛇',1627130608250593281,'8800.00','','3d486c18-6dd8-4464-a087-bd93cfc987bf.jpg','口味蛇',1,0,'2023-02-21 21:28:26','2023-02-21 21:28:26',1627997218788163586,1627997218788163586,0);

/*Table structure for table `dish_flavor` */

DROP TABLE IF EXISTS `dish_flavor`;

CREATE TABLE `dish_flavor` (
  `id` bigint NOT NULL COMMENT '主键',
  `dish_id` bigint NOT NULL COMMENT '菜品',
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '口味名称',
  `value` varchar(500) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '口味数据list',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  `is_deleted` int NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='菜品口味关系表';

/*Data for the table `dish_flavor` */

insert  into `dish_flavor`(`id`,`dish_id`,`name`,`value`,`create_time`,`update_time`,`create_user`,`update_user`,`is_deleted`) values (1628019384321658881,1628019384179052546,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:10:43','2023-02-21 21:10:43',1627997218788163586,1627997218788163586,0),(1628019384321658882,1628019384179052546,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:10:43','2023-02-21 21:10:43',1627997218788163586,1627997218788163586,0),(1628019727621246978,1628019727558332417,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:12:05','2023-02-21 21:12:05',1627997218788163586,1627997218788163586,0),(1628019727621246979,1628019727558332417,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:12:05','2023-02-21 21:12:05',1627997218788163586,1627997218788163586,0),(1628019727621246980,1628019727558332417,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:12:05','2023-02-21 21:12:05',1627997218788163586,1627997218788163586,0),(1628020274726260738,1628020274659151874,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:14:15','2023-02-21 21:14:15',1627997218788163586,1627997218788163586,0),(1628020274726260739,1628020274659151874,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:14:15','2023-02-21 21:14:15',1627997218788163586,1627997218788163586,0),(1628020274726260740,1628020274659151874,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:14:15','2023-02-21 21:14:15',1627997218788163586,1627997218788163586,0),(1628020414488858626,1628020414488858625,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:14:49','2023-02-21 21:14:49',1627997218788163586,1627997218788163586,0),(1628020414488858627,1628020414488858625,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:14:49','2023-02-21 21:14:49',1627997218788163586,1627997218788163586,0),(1628020624573157378,1628020624501854210,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:15:39','2023-02-21 21:15:39',1627997218788163586,1627997218788163586,0),(1628020624573157379,1628020624501854210,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:15:39','2023-02-21 21:15:39',1627997218788163586,1627997218788163586,0),(1628020855389900802,1628020855322791938,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:19:39','2023-02-21 21:19:39',1627997218788163586,1627997218788163586,0),(1628020855389900803,1628020855322791938,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:19:39','2023-02-21 21:19:39',1627997218788163586,1627997218788163586,0),(1628021771191013378,1628021771191013377,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:20:12','2023-02-21 21:20:12',1627997218788163586,1627997218788163586,0),(1628021771191013379,1628021771191013377,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:20:12','2023-02-21 21:20:12',1627997218788163586,1627997218788163586,0),(1628022122971484162,1628022122845655041,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:21:36','2023-02-21 21:21:36',1627997218788163586,1627997218788163586,0),(1628022122971484163,1628022122845655041,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:21:36','2023-02-21 21:21:36',1627997218788163586,1627997218788163586,0),(1628022256186773505,1628022255993835522,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:22:08','2023-02-21 21:22:08',1627997218788163586,1627997218788163586,0),(1628022256186773506,1628022255993835522,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:22:08','2023-02-21 21:22:08',1627997218788163586,1627997218788163586,0),(1628022421975027713,1628022421907918850,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:22:47','2023-02-21 21:22:47',1627997218788163586,1627997218788163586,0),(1628022421975027714,1628022421907918850,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:22:47','2023-02-21 21:22:47',1627997218788163586,1627997218788163586,0),(1628022523175194626,1628022523112280066,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:23:11','2023-02-21 21:23:11',1627997218788163586,1627997218788163586,0),(1628022918823890945,1628022918689673218,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:24:46','2023-02-21 21:24:46',1627997218788163586,1627997218788163586,0),(1628022918823890946,1628022918689673218,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:24:46','2023-02-21 21:24:46',1627997218788163586,1627997218788163586,0),(1628023021190074370,1628023021122965506,'温度','[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]','2023-02-21 21:25:10','2023-02-21 21:25:10',1627997218788163586,1627997218788163586,0),(1628023133576450049,1628023133450620930,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:25:37','2023-02-21 21:25:37',1627997218788163586,1627997218788163586,0),(1628023133576450050,1628023133450620930,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:25:37','2023-02-21 21:25:37',1627997218788163586,1627997218788163586,0),(1628023364061843457,1628023363927625729,'甜味','[\"无糖\",\"少糖\",\"半糖\",\"多糖\",\"全糖\"]','2023-02-21 21:26:32','2023-02-21 21:26:32',1627997218788163586,1627997218788163586,0),(1628023364061843458,1628023363927625729,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:26:32','2023-02-21 21:26:32',1627997218788163586,1627997218788163586,0),(1628023490448805890,1628023490318782465,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:27:02','2023-02-21 21:27:02',1627997218788163586,1627997218788163586,0),(1628023694585581569,1628023694518472706,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:27:51','2023-02-21 21:27:51',1627997218788163586,1627997218788163586,0),(1628023694585581570,1628023694518472706,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:27:51','2023-02-21 21:27:51',1627997218788163586,1627997218788163586,0),(1628023841553993729,1628023841423970305,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-21 21:28:26','2023-02-21 21:28:26',1627997218788163586,1627997218788163586,0),(1628023841553993730,1628023841423970305,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-21 21:28:26','2023-02-21 21:28:26',1627997218788163586,1627997218788163586,0),(1628283539888816129,1628021265471197185,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-22 14:40:23','2023-02-22 14:40:23',1627997218788163586,1627997218788163586,0),(1628283539888816130,1628021265471197185,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-22 14:40:23','2023-02-22 14:40:23',1627997218788163586,1627997218788163586,0),(1628283681786314754,1628022754352648193,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-22 14:40:56','2023-02-22 14:40:56',1627997218788163586,1627997218788163586,0),(1628283681786314755,1628022754352648193,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-22 14:40:56','2023-02-22 14:40:56',1627997218788163586,1627997218788163586,0),(1628283800552226817,1628021120151146497,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-22 14:41:25','2023-02-22 14:41:25',1627997218788163586,1627997218788163586,0),(1628283800552226818,1628021120151146497,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-22 14:41:25','2023-02-22 14:41:25',1627997218788163586,1627997218788163586,0),(1628283883154849793,1628020978719215617,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-22 14:41:44','2023-02-22 14:41:44',1627997218788163586,1627997218788163586,0),(1628283883154849794,1628020978719215617,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-22 14:41:44','2023-02-22 14:41:44',1627997218788163586,1627997218788163586,0),(1628283974536151041,1628020011776954369,'忌口','[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]','2023-02-22 14:42:06','2023-02-22 14:42:06',1627997218788163586,1627997218788163586,0),(1628283974536151042,1628020011776954369,'辣度','[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]','2023-02-22 14:42:06','2023-02-22 14:42:06',1627997218788163586,1627997218788163586,0);

/*Table structure for table `employee` */

DROP TABLE IF EXISTS `employee`;

CREATE TABLE `employee` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '姓名',
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户名',
  `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '密码',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '手机号',
  `sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '性别',
  `id_number` varchar(18) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '身份证号',
  `status` int NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='员工信息';

/*Data for the table `employee` */

insert  into `employee`(`id`,`name`,`username`,`password`,`phone`,`sex`,`id_number`,`status`,`create_time`,`update_time`,`create_user`,`update_user`) values (1,'管理员','admin','e10adc3949ba59abbe56e057f20f883e','13812312312','1','110101199001010047',1,'2021-05-06 17:20:07','2021-05-10 02:24:09',1,1),(1626857776597762049,'张三1','zhangsan','e10adc3949ba59abbe56e057f20f883e','17671789248','1','421181199805171311',1,'2023-02-18 16:14:54','2023-02-18 22:06:50',1,1),(1626945547559514113,'小李','test001','e10adc3949ba59abbe56e057f20f883e','17612345678','1','421181123456781234',1,'2023-02-18 22:04:04','2023-02-19 08:52:25',1,1);

/*Table structure for table `order_detail` */

DROP TABLE IF EXISTS `order_detail`;

CREATE TABLE `order_detail` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '名字',
  `image` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
  `order_id` bigint NOT NULL COMMENT '订单id',
  `dish_id` bigint DEFAULT NULL COMMENT '菜品id',
  `setmeal_id` bigint DEFAULT NULL COMMENT '套餐id',
  `dish_flavor` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
  `number` int NOT NULL DEFAULT '1' COMMENT '数量',
  `amount` decimal(10,2) NOT NULL COMMENT '金额',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='订单明细表';

/*Data for the table `order_detail` */

insert  into `order_detail`(`id`,`name`,`image`,`order_id`,`dish_id`,`setmeal_id`,`dish_flavor`,`number`,`amount`) values (1628281691748474882,'儿童套餐A','17fb2dcf-c06a-46c4-8ba6-8cb461d84031.jpg',1628281691555536898,NULL,1628024994765295618,NULL,1,'59.00'),(1628281691748474883,'辣子鸡丁','c67657d7-4cbf-4d0c-b20c-7ab66ad52514.jpg',1628281691555536898,1628023694518472706,NULL,'不要香菜,中辣',1,'39.00'),(1628281691748474884,'麻辣鱼片','ffdbeb37-0fbe-4190-a52a-3a16478f366e.jpg',1628281691555536898,1628023133450620930,NULL,'不要蒜,中辣',1,'39.00'),(1628281691748474885,'宫保鸡丁','95f8b479-ae76-4107-9b08-3c62ae7f7fd0.jpg',1628281691555536898,1628021265471197185,NULL,NULL,1,'69.00'),(1628281691748474886,'麻辣兔头','703fb335-5593-49be-a629-e2522344212d.jpg',1628281691555536898,1628020414488858625,NULL,'去冰,中辣',1,'128.00'),(1628281691811389441,'商务套餐A','fb706fe0-b57f-46da-9f70-f209cc489f39.jpg',1628281691555536898,NULL,1628024830898032642,NULL,1,'99.00'),(1628302223755788290,'辣子鸡丁','c67657d7-4cbf-4d0c-b20c-7ab66ad52514.jpg',1628302223562850306,1628023694518472706,NULL,'不要蒜,微辣',1,'39.00'),(1628302223755788291,'商务套餐A','fb706fe0-b57f-46da-9f70-f209cc489f39.jpg',1628302223562850306,NULL,1628024830898032642,NULL,1,'99.00'),(1628302223755788292,'儿童套餐A','17fb2dcf-c06a-46c4-8ba6-8cb461d84031.jpg',1628302223562850306,NULL,1628024994765295618,NULL,1,'59.00');

/*Table structure for table `orders` */

DROP TABLE IF EXISTS `orders`;

CREATE TABLE `orders` (
  `id` bigint NOT NULL COMMENT '主键',
  `number` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '订单号',
  `status` int NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消',
  `user_id` bigint NOT NULL COMMENT '下单用户',
  `address_book_id` bigint NOT NULL COMMENT '地址id',
  `order_time` datetime NOT NULL COMMENT '下单时间',
  `checkout_time` datetime NOT NULL COMMENT '结账时间',
  `pay_method` int NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝',
  `amount` decimal(10,2) NOT NULL COMMENT '实收金额',
  `remark` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
  `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `consignee` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='订单表';

/*Data for the table `orders` */

insert  into `orders`(`id`,`number`,`status`,`user_id`,`address_book_id`,`order_time`,`checkout_time`,`pay_method`,`amount`,`remark`,`phone`,`address`,`user_name`,`consignee`) values (1628281691555536898,'1628281691555536898',2,1627997218788163586,1628270733663694849,'2023-02-22 14:33:02','2023-02-22 14:33:02',1,'433.00','','17671789248','湖北工业大学',NULL,'金阳'),(1628302223562850306,'1628302223562850306',2,1627997218788163586,1628270733663694849,'2023-02-22 15:54:37','2023-02-22 15:54:37',1,'197.00','','17671789248','湖北工业大学',NULL,'金阳');

/*Table structure for table `setmeal` */

DROP TABLE IF EXISTS `setmeal`;

CREATE TABLE `setmeal` (
  `id` bigint NOT NULL COMMENT '主键',
  `category_id` bigint NOT NULL COMMENT '菜品分类id',
  `name` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '套餐名称',
  `price` decimal(10,2) NOT NULL COMMENT '套餐价格',
  `status` int DEFAULT NULL COMMENT '状态 0:停用 1:启用',
  `code` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '编码',
  `description` varchar(512) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
  `image` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  `is_deleted` int NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_setmeal_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='套餐';

/*Data for the table `setmeal` */

insert  into `setmeal`(`id`,`category_id`,`name`,`price`,`status`,`code`,`description`,`image`,`create_time`,`update_time`,`create_user`,`update_user`,`is_deleted`) values (1628024830898032642,1413342269393674242,'商务套餐A','9900.00',1,'','商务套餐A','fb706fe0-b57f-46da-9f70-f209cc489f39.jpg','2023-02-21 21:32:22','2023-02-21 21:32:22',1627997218788163586,1627997218788163586,0),(1628024994765295618,1413386191767674881,'儿童套餐A','5900.00',1,'','儿童套餐A','17fb2dcf-c06a-46c4-8ba6-8cb461d84031.jpg','2023-02-21 21:33:01','2023-02-21 21:33:01',1627997218788163586,1627997218788163586,0);

/*Table structure for table `setmeal_dish` */

DROP TABLE IF EXISTS `setmeal_dish`;

CREATE TABLE `setmeal_dish` (
  `id` bigint NOT NULL COMMENT '主键',
  `setmeal_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '套餐id ',
  `dish_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '菜品id',
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '菜品名称 (冗余字段)',
  `price` decimal(10,2) DEFAULT NULL COMMENT '菜品原价(冗余字段)',
  `copies` int NOT NULL COMMENT '份数',
  `sort` int NOT NULL DEFAULT '0' COMMENT '排序',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint NOT NULL COMMENT '创建人',
  `update_user` bigint NOT NULL COMMENT '修改人',
  `is_deleted` int NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='套餐菜品关系';

/*Data for the table `setmeal_dish` */

insert  into `setmeal_dish`(`id`,`setmeal_id`,`dish_id`,`name`,`price`,`copies`,`sort`,`create_time`,`update_time`,`create_user`,`update_user`,`is_deleted`) values (1628024830960947201,'1628024830898032642','1628023694518472706','辣子鸡丁','3900.00',1,0,'2023-02-21 21:32:22','2023-02-21 21:32:22',1627997218788163586,1627997218788163586,0),(1628024830960947202,'1628024830898032642','1628023490318782465','大米饭','500.00',1,0,'2023-02-21 21:32:22','2023-02-21 21:32:22',1627997218788163586,1627997218788163586,0),(1628024830960947203,'1628024830898032642','1628023021122965506','啤酒','1000.00',1,0,'2023-02-21 21:32:22','2023-02-21 21:32:22',1627997218788163586,1627997218788163586,0),(1628024830960947204,'1628024830898032642','1628023363927625729','烤乳鸽','7900.00',1,0,'2023-02-21 21:32:22','2023-02-21 21:32:22',1627997218788163586,1627997218788163586,0),(1628024994832404481,'1628024994765295618','1628023490318782465','大米饭','500.00',1,0,'2023-02-21 21:33:01','2023-02-21 21:33:01',1627997218788163586,1627997218788163586,0),(1628024994832404482,'1628024994765295618','1628022523112280066','王老吉','500.00',1,0,'2023-02-21 21:33:01','2023-02-21 21:33:01',1627997218788163586,1627997218788163586,0),(1628024994832404483,'1628024994765295618','1628022918689673218','清炒素食','1900.00',1,0,'2023-02-21 21:33:01','2023-02-21 21:33:01',1627997218788163586,1627997218788163586,0),(1628024994832404484,'1628024994765295618','1628023363927625729','烤乳鸽','7900.00',1,0,'2023-02-21 21:33:01','2023-02-21 21:33:01',1627997218788163586,1627997218788163586,0);

/*Table structure for table `shopping_cart` */

DROP TABLE IF EXISTS `shopping_cart`;

CREATE TABLE `shopping_cart` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '名称',
  `image` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
  `user_id` bigint NOT NULL COMMENT '主键',
  `dish_id` bigint DEFAULT NULL COMMENT '菜品id',
  `setmeal_id` bigint DEFAULT NULL COMMENT '套餐id',
  `dish_flavor` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
  `number` int NOT NULL DEFAULT '1' COMMENT '数量',
  `amount` decimal(10,2) NOT NULL COMMENT '金额',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='购物车';

/*Data for the table `shopping_cart` */

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` bigint NOT NULL COMMENT '主键',
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
  `phone` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '手机号',
  `sex` varchar(2) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '性别',
  `id_number` varchar(18) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '身份证号',
  `avatar` varchar(500) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '头像',
  `status` int DEFAULT '0' COMMENT '状态 0:禁用,1:正常',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin COMMENT='用户信息';

/*Data for the table `user` */

insert  into `user`(`id`,`name`,`phone`,`sex`,`id_number`,`avatar`,`status`) values (1627997218788163586,NULL,'17612349248',NULL,NULL,NULL,1);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

数据表

基于SpringBoot的外卖项目(详细开发过程)

Maven项目搭建

项目的目录结构

基于SpringBoot的外卖项目(详细开发过程)

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.jerry</groupId>
  <artifactId>reggie</artifactId>
  <version>1.0</version>
  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <scope>compile</scope>
    </dependency>

    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.2</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.20</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.76</version>
    </dependency>

    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.23</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.4.5</version>
      </plugin>
    </plugins>
  </build>
</project>

application.yml

server:
  port: 8080
spring:
  application:
    # 应用名称,可选项
    name: reggie
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: root
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

ReggieApplication启动类

package com.jerry.reggie;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * ClassName: ReggieApplication
 * Package: com.jerry.reggie
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 13:50
 * @Version 1.0
 */
@Slf4j
@SpringBootApplication
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class, args);
        log.info("项目启动成功...");
    }
}

配置静态资源映射

SpringBoot访问静态资源默认会去resources/static或resources/templates目录下,如果不需要static或templates目录,那就手动使用配置类进行配置访问路径

package com.jerry.reggie.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * ClassName: WebMvcConfig
 * Package: com.jerry.reggie.config
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 14:16
 * @Version 1.0
 */
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源的映射...");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
}

访问:http://localhost:8080/backend/index.html

基于SpringBoot的外卖项目(详细开发过程)

4、登录功能

4.1、后台登录功能

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码编写

vo类:将服务器和前端页面传递的数据封装好

R类是一个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端页面

R.java

package com.jerry.reggie.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

/**
 * 通用返回结果,服务器端响应的数据最终都会封装成此对象
 * @param <T>
 */
@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}

实体类

package com.jerry.reggie.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 员工实体类
 */
@Data
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String username;

    private String name;

    private String password;

    private String phone;

    private String sex;

    private String idNumber;//身份证号

    private Integer status;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

}

EmployeeMapper

package com.jerry.reggie.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jerry.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

/**
 * ClassName: EmployeeMApper
 * Package: com.jerry.reggie.mapper
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 14:45
 * @Version 1.0
 */
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

EmployeeService

package com.jerry.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jerry.reggie.entity.Employee;

/**
 * ClassName: EmployeeService
 * Package: com.jerry.reggie.service
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 14:46
 * @Version 1.0
 */
public interface EmployeeService extends IService<Employee> {
}

EmployeeServiceImpl

package com.jerry.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jerry.reggie.entity.Employee;
import com.jerry.reggie.mapper.EmployeeMapper;
import com.jerry.reggie.service.EmployeeService;
import org.springframework.stereotype.Service;

/**
 * ClassName: EmployeeServiceImpl
 * Package: com.jerry.reggie.service.impl
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 14:46
 * @Version 1.0
 */

@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}

EmployeeController

基于SpringBoot的外卖项目(详细开发过程)

package com.jerry.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jerry.reggie.common.R;
import com.jerry.reggie.entity.Employee;
import com.jerry.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * ClassName: EmployeeController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 14:52
 * @Version 1.0
 */
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @PostMapping("/login")
    public R<Employee> login(@RequestBody Employee employee, HttpServletRequest request) {
        //@RequestBody用来接收前端传递给后端的`json`字符串中的数据的(请求体中的数据的),所以前端只能发送POST请求

        //1、将页面提交的密码password进行md5加密处理
        String pwd = employee.getPassword();
        pwd = DigestUtils.md5DigestAsHex(pwd.getBytes());

        // 2、根据页面提交的用户名username查询数据库]
        /**
         * 我自己写的是
         *         QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
         *         employeeService.getOne(queryWrapper.select(employee.getName()));
         * 查询出来的是null值
         */
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        //方法引用的语法格式(语法糖)
        wrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(wrapper);

        // 3、如果没有查询到则返回登录失败结果
        if (emp == null) {
            return R.error("登录失败");
        }
        //4、密码比对,如果不一致则返回登录失败结果
        if (!pwd.equals(emp.getPassword())) {
            return R.error("登录失败");
        }

        //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if (emp.getStatus()!=1){
            return R.error("员工账号已禁用");
        }

        // 6、登录成功,将员工id存入Session并返回登录成功结果
        Long empId = emp.getId();
        request.getSession().setAttribute("empId",empId);
        return R.success(emp);
    }
}

页面展示

http://localhost:8080/backend/page/login/login.html

http://localhost:8080/backend/index.html

4.2、后台登出功能

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码编写

/**
 * 员工后台登出功能
 * @param request
 * @return
 */
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request) {
    //1、清理Session中的用户id
    request.getSession().removeAttribute("empId");
    // 2、返回结果
    return R.success("登出成功");
}

页面展示

4.3、完善登录功能

问题分析

基于SpringBoot的外卖项目(详细开发过程)

代码编写

基于SpringBoot的外卖项目(详细开发过程)

LoginCheckFilter

package com.jerry.reggie.filter;

import com.alibaba.fastjson.JSON;
import com.jerry.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * ClassName: LoginCheckFilter
 * Package: com.jerry.reggie.filter
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-16 21:50
 * @Version 1.0
 */
@Slf4j
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    //路径匹配,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;


        //1、获取本次请求的URI
        String uri = request.getRequestURI(); //   backend/index.html
        log.info("拦截到请求:{}", uri);

        //定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2、判断本次请求是否需要处理
        boolean check = check(urls, uri);

        // 3、如果不需要处理,则直接放行
        if (check == true) {
            log.info("本次请求{}不需要处理" + uri);
            filterChain.doFilter(request, response);
            return;
        }

        //4、判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("empId") != null) {
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("empId"));
            filterChain.doFilter(request, response);
            return;
        }

        log.info("用户未登录");
        // 5、如果未登录则返回未登录结果,通过输出流方式向客户端响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     *
     * @param urls
     * @param uri
     * @return
     */
    public boolean check(String[] urls, String uri) {
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, uri);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

基于SpringBoot的外卖项目(详细开发过程)

5、员工管理

5.1、新增员工

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

代码编写

/**
 * 新增员工
 * @param employee
 * @return
 */
@PostMapping
public R<String> save(@RequestBody Employee employee, HttpServletRequest request){
    log.info("新增员工,员工信息:{}",employee.toString());

    //设置员工的初始密码,需要进行MD5 加密处理
    employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));


    employee.setCreateTime(LocalDateTime.now());
    employee.setUpdateTime(LocalDateTime.now());

    //获得当前登录对象的id
    Long empId = (Long) request.getSession().getAttribute("empId");
    employee.setCreateUser(empId);
    employee.setUpdateUser(empId);

    employeeService.save(employee);
    return R.success("添加员工成功");
}

异常捕获

基于SpringBoot的外卖项目(详细开发过程)

GlobalExceptionHandler

package com.jerry.reggie.common;

/**
 * ClassName: GlobalExceptionHandler
 * Package: com.jerry.reggie.common
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-18 16:21
 * @Version 1.0
 */

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLIntegrityConstraintViolationException;

/**
 * 全局异常捕获
 */
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody //要返回json数据时就写
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 异常处理方法
     * @return
     */
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
        log.error(exception.getMessage());

        if (exception.getMessage().contains("Duplicate entry")){
            String[] strings = exception.getMessage().split(" ");
            String msg = strings[2] + "已存在";
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

小结

基于SpringBoot的外卖项目(详细开发过程)

5.2、员工信息分页查询

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

MyBatisPlusConfig配置分页插件

package com.jerry.reggie.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ClassName: MyBatisPlusConfig
 * Package: com.jerry.reggie.config
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-18 17:16
 * @Version 1.0
 */

/**
 * 配置MP的分页插件
 */
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
/**
 * 员工信息分页查询
 *
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
    log.info("page = {}, pageSize = {}, name = {}", page, pageSize, name);

    //这里:只需要new page对象和构造好lambdaQueryWrapper
    //构造分页构造器
    Page<Employee> pageInfo = new Page<>(page, pageSize);

    //构造条件构造器
    LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //添加一个过滤条件
    lambdaQueryWrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name);
    //添加一个排序条件
    lambdaQueryWrapper.orderByDesc(Employee::getUpdateTime);
    //执行查询
    employeeService.page(pageInfo, lambdaQueryWrapper);

    return R.success(pageInfo);
}

5.3、启用/禁用员工账号

需求分析

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

代码编写

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

编写一个通用的update方法

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 根据id修改员工信息
 *
 * @param employee
 * @return
 */
@PutMapping
public R<String> update(HttpServletRequest request, @RequestBody Employee employee) {
    log.info(employee.toString());
    Long empId = (Long) request.getSession().getAttribute("empId");
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(empId);
    employeeService.updateById(employee);
    return R.success("更新成功");
}

功能测试

原因:JS在处理Long型数据时,只能处理16位,也就是说,2^53次方,超过后就四舍五入,精度损失

基于SpringBoot的外卖项目(详细开发过程)

功能修复

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 扩展mvc消息框架的转换器
 * @param converters
 */
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    log.info("扩展消息转换器...");
    //创建消息转换器对象
    MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    //设置对象转换器,底层使用Jackson将Java对象转为json
    messageConverter.setObjectMapper(new JacksonObjectMapper());
    //将上面的消息转换器对象追加到mvc框架的转换器集合中
    converters.add(0, messageConverter);
}

5.3、编辑员工

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码编写

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 根据id查询员工信息
 *
 * @param id
 * @return
 */

@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id) {
    log.info("根据id 查询员工信息...");
    Employee employee = employeeService.getById(id);
    if (employee != null) {

        return R.success(employee);
    }
    return R.error("没有查询到对应的员工信息");
}

5.4、公共字段自动填充

问题分析

基于SpringBoot的外卖项目(详细开发过程)

代码实现

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

功能完善

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

ThreadLocal

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

BaseContext

package com.jerry.reggie.common;

/**
 * ClassName: BaseContext
 * Package: com.jerry.reggie.common
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 9:00
 * @Version 1.0
 */

/**
 * 基于ThreadLocal封装的工具类,用于保存和获取当前登录用户的id
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal= new ThreadLocal<>();

    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }

    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

6、分类管理

6.1、新增菜品分类

需求分析

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

6.1、新增菜品的分页查询

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 菜品分页查询
 * @param page
 * @param pageSize
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize){
    //分页构造器
    Page<Category> categoryPage = new Page<>();
    //条件构造器
    LambdaQueryWrapper<Category> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加排序条件
    lambdaQueryWrapper.orderByAsc(Category::getSort);

    categoryService.page(categoryPage, lambdaQueryWrapper);

    return R.success(categoryPage);
}

6.2、删除分类

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

功能完善

基于SpringBoot的外卖项目(详细开发过程)

GlobalExceptionHandler

/**
 * 异常处理方法
 * @return
 */
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException exception){
    log.error(exception.getMessage());

    return R.error(exception.getMessage());
}

CustomException

package com.jerry.reggie.common;

/**
 * ClassName: CustomException
 * Package: com.jerry.reggie.common
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 11:55
 * @Version 1.0
 */

/**
 * 自定义业务异常类
 */
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

CategoryServiceImpl

package com.jerry.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jerry.reggie.common.CustomException;
import com.jerry.reggie.entity.Category;
import com.jerry.reggie.entity.Dish;
import com.jerry.reggie.entity.Setmeal;
import com.jerry.reggie.mapper.CategoryMapper;
import com.jerry.reggie.service.CategoryService;
import com.jerry.reggie.service.DishService;
import com.jerry.reggie.service.SetmealService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * ClassName: CategoryServiceImpl
 * Package: com.jerry.reggie.service.impl
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 9:30
 * @Version 1.0
 */
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
    @Autowired
    private DishService dishService;

    @Autowired
    private SetmealService setmealService;
    /**
     * 根据id删除分类,删除之前需要进行判断
     * @param id
     */
    @Override
    public void remove(Long id) {
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类id查询
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        int count1 = dishService.count(dishLambdaQueryWrapper);
        //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
        if (count1>0){
            //已经关联,抛出一个业务异常
            throw new CustomException("当前分类下关联了菜品,不能删除");
        }

        //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
        int count2 = setmealService.count(setmealLambdaQueryWrapper);
        if (count2>0){
            //已经关联套餐,抛出一个业务异常
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }
        //正常删除分类
        super.removeById(id);

    }
}

    /**
     * 根据id删除分类
     * @param id
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long id){
        log.info("删除分类:{}",id);

//        categoryService.removeById(id);
        categoryService.remove(id);

        return R.success("分类信息删除成功");
    }

6.3、修改分类

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码编写

/**
 * 根据id修改分类信息
 * @param category
 * @return
 */
@PutMapping
public R<String> update(@RequestBody Category category){
    log.info("修改分类信息:{}",category);
    categoryService.updateById(category);
    return R.success("修改分类信息成功");
}

7、菜品管理

7.1、文件上传下载

文件上传介绍

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

文件下载介绍

基于SpringBoot的外卖项目(详细开发过程)

文件上传代码实现

基于SpringBoot的外卖项目(详细开发过程)

package com.jerry.reggie.controller;

import com.jerry.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * ClassName: CommonController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 17:19
 * @Version 1.0
 */

/**
 * 文件上传下载
 */
@RestController
@Slf4j
@RequestMapping("/common")
public class CommonController {
    @Value("${reggie.path}")//这里的value不要导包成了lombok,用${}动态取值
    private String basePath;

    /**
     * 文件上传
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> uploadFile(MultipartFile file) {
        //file是临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());

        //获取原始文件名
        String originalFilename = file.getOriginalFilename();
        //获取原始文件名的后缀名,这里是带点的
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成的文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;

        //创建一个目录对象
        File dir = new File(basePath);
        if (!dir.exists()){
            //目录不存在,创建一个
            dir.mkdirs();
        }

        try {
            //将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return R.success(fileName);
    }
}

文件下载代码实现

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 文件下载
 *
 * @param name
 * @param response
 */
@GetMapping("/download")
public void download(String name, HttpServletResponse response) {

    try {
        // 输入流,通过输入流读取文件内容
        FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));

        //输出流,通过输出流将文件写回浏览器,在浏览器显示图片
        ServletOutputStream outputStream = response.getOutputStream();

        //设置输出流的类型为图片
        response.setContentType("image/jpeg");

        int len = 0;
        byte[] bytes = new byte[1024];
        while ((len = fileInputStream.read(bytes)) != -1) {
           outputStream.write(bytes, 0, len);
           outputStream.flush();
        }
        // 关闭资源
        outputStream.close();
        fileInputStream.close();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }

}

7.2、新增菜品

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

由于新增菜品涉及到多张表的插入操作,因此需要在Service业务层中单独写一个save方法

DishServiceImpl

package com.jerry.reggie.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jerry.reggie.dto.DishDto;
import com.jerry.reggie.entity.Dish;
import com.jerry.reggie.entity.DishFlavor;
import com.jerry.reggie.mapper.DishFlavorMapper;
import com.jerry.reggie.mapper.DishMapper;
import com.jerry.reggie.service.DishFlavorService;
import com.jerry.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * ClassName: DishServiceImpl
 * Package: com.jerry.reggie.service.impl
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 10:53
 * @Version 1.0
 */
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
    @Autowired
    private DishFlavorService dishFlavorService;

    /**
     * 新增菜品同时保存口味数据
     * @param dishDto
     */
    @Transactional
    @Override
    public void saveWithFlavor(DishDto dishDto) {
        //保存菜品基本信息到菜品表dish
        this.save(dishDto);

        Long dishId = dishDto.getId();

        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
        }).collect(Collectors.toList());

        //保存菜品口味到到菜品口味表dish_flavor
        dishFlavorService.saveBatch(flavors);
    }
}

DishController

package com.jerry.reggie.controller;

import com.jerry.reggie.common.R;
import com.jerry.reggie.dto.DishDto;
import com.jerry.reggie.entity.Dish;
import com.jerry.reggie.service.DishFlavorService;
import com.jerry.reggie.service.DishService;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * ClassName: DishController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-19 19:11
 * @Version 1.0
 */
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    DishFlavorService dishFlavorService;

    @Autowired
    DishService dishService;

    /**
     * 新增菜品
     * @param dishDto
     * @return
     */
    @PostMapping
    public R<String> addMeal(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());

        dishService.saveWithFlavor(dishDto);
        return R.success("保存成功");
    }
}

7.3、菜品信息分页查询

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

难点

这里的分页查询涉及到2个对象的拷贝复赋值问题,使用BeanUtils.copyProperties()来完成操作的

把Dish对象赋值给DishDto对象,并设置上categoryName

流式处理的表达式是重点!!!

/**
 * 菜品信息--分页查询
 *
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
    //分页构造器
    Page<Dish> pageInfo = new Page<>(page, pageSize);
    Page<DishDto> dishDtoPage = new Page<>();
    //条件构造器
    LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //添加过滤条件
    lambdaQueryWrapper.like(name != null, Dish::getName, name);
    //添加过滤条件
    lambdaQueryWrapper.orderByDesc(Dish::getUpdateTime);

    dishService.page(pageInfo, lambdaQueryWrapper);

    //对象拷贝
    BeanUtils.copyProperties(pageInfo, dishDtoPage,"records");
    List<Dish> records = pageInfo.getRecords();
    List<DishDto> list =  records.stream().map((item) -> {
        DishDto dishDto = new DishDto();

        //对象拷贝
        BeanUtils.copyProperties(item, dishDto);

        Long categoryId = item.getCategoryId();//分类id
        Category category = categoryService.getById(categoryId);//分类对象

        if (category!=null){
            String categoryName = category.getName();
            dishDto.setCategoryName(categoryName);
        }
        
        return dishDto;
    }).collect(Collectors.toList());

    dishDtoPage.setRecords(list);
    return R.success(dishDtoPage);
}

7.4、修改菜品

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

controller

/**
 *
 * @param dishDto
 * @return
 */
@PutMapping
public R<String> updateMeal(@RequestBody DishDto dishDto){
    log.info(dishDto.toString());

    dishService.updateWithFlavor(dishDto);
    return R.success("保存菜品成功");
}

DishServiceImpl

/**
 * 更新菜品信息,同时更新对应的口味信息
 *
 * @param dishDto
 */
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
    //更新dish表基本信息
    this.updateById(dishDto);

    //先清理当前菜品对应的口味信息---dish_flavor表的 delete 操作
    LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
    dishFlavorService.remove(lambdaQueryWrapper);

    //再添加当前提交过来的口味数据--dish_flavor表的 insert 操作
    List<DishFlavor> flavors = dishDto.getFlavors();

    flavors = flavors.stream().map((item) -> {
        item.setDishId(dishDto.getId());
        return item;
    }).collect(Collectors.toList());

    dishFlavorService.saveBatch(flavors);
}

7.5、修改菜品的停/起售状态

DishController

    /**
     * 修改菜品的 停/启 售状态
     *
     * @param ids
     * @return
     */
    @PostMapping("/status/{status}")
    public R<Dish> status(long ids) {
        Dish dish = dishService.getById(ids);
        dishService.setStatus(dish);
        return R.success(dish);
    }

DishServiceImpl

/**
 * 修改菜品的停/启售状态
 * @param dish
 */
@Override
public void setStatus(Dish dish) {
    if (dish.getStatus()==1){
        dish.setStatus(0);
    }else {
        dish.setStatus(1);
    }
    this.updateById(dish);
}

7.6、删除菜品

    /**
     * 菜品管理--批量删除菜品--跟单个删除一样的,复用同一份代码
     * @param ids
     * @return
     */
    @DeleteMapping
    public R<String> delete(Long[] ids){
        for (Long id : ids) {
            dishService.delete(id);
        }
        return R.success("删除菜品成功!");
    }

8、套餐管理

8.1、新增套餐

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

DishController

/**
 * 根据条件来查询对应的菜品数据
 * @param dish
 * @return
 */
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){
    //构造查询条件
    LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
    //查询状态为1,启售状态
    lambdaQueryWrapper.eq(Dish::getStatus,1);
    //添加排序条件
    lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

    List<Dish> list = dishService.list(lambdaQueryWrapper);
    return R.success(list);
}

SetmealDishController

/**
 * 新增套餐
 * @param setmealDto
 * @return
 */
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){

    log.info("套餐信息:{}",setmealDto.toString());
    setmealService.saveWithDish(setmealDto);
    return R.success("新增套餐成功");
}

SetmealServiceImpl

/**
 * 新增套餐,同时需要保存套餐和菜品的关联关系
 * @param setmealDto
 */
@Override
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
    //保存套餐的基本信息 操作setmeal  执行insert
    this.save(setmealDto);

    List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
    setmealDishes.stream().map((item)->{
        item.setSetmealId(setmealDto.getId());
        return item;
    }).collect(Collectors.toList());

    //保存套餐和菜品的关联信息,操作setmeal_dish,执行insert
    setmealDishService.saveBatch(setmealDishes);
}

8.2、套餐信息分页查询

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

/**
 * 套餐管理--套餐信息分页查询
 * @param page
 * @param pageSize
 * @param name
 * @return
 */
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
    //分页构造器
    Page<Setmeal> pageInfo = new Page<>(page, pageSize);
    Page<SetmealDto> dtoPage = new Page<>();

    //条件构造器
    LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //添加过滤条件
    lambdaQueryWrapper.like(name != null, Setmeal::getName, name);
    //添加过滤条件
    lambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);

    setmealService.page(pageInfo, lambdaQueryWrapper);

    //对象拷贝
    BeanUtils.copyProperties(pageInfo, dtoPage, "records");
    List<Setmeal> records = pageInfo.getRecords();

    List<SetmealDto> list = records.stream().map((item) -> {
        SetmealDto setmealDto = new SetmealDto();

        //对象拷贝
        BeanUtils.copyProperties(item, setmealDto);

        //分类id
        Long categoryId = item.getCategoryId();
        //根据分类id查询对象
        Category category = categoryService.getById(categoryId);
        if (category != null) {
            //获取分类名称
            String categoryName = category.getName();
            setmealDto.setCategoryName(categoryName);
        }
        return setmealDto;
    }).collect(Collectors.toList());

    dtoPage.setRecords(list);

    return R.success(dtoPage);
}

8.3、删除套餐

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

SetmealController

/**
 * (批量)删除套餐
 *
 * @param ids
 * @return
 */
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids) {
    log.info("id: {}", ids);
    setmealService.removeWithDish(ids);
    return R.success("套餐数据删除成功");
}

SetmealServiceImpl

/**
 * 删除套餐,同时输出套餐和菜品关联的数据
 *
 * @param ids
 */
@Override
public void removeWithDish(List<Long> ids) {
    // 先查询套餐状态,确实是否可以删除
    // select count(*) from setmeal where id in (1, 2, 3) and status = 1;
    LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.in(Setmeal::getId, ids);
    lambdaQueryWrapper.eq(Setmeal::getStatus, 1);

    int count = this.count(lambdaQueryWrapper);
    if (count > 0) {
        // 如果不能删除,抛出一个业务异常
        throw new CustomException("套餐正在售卖中,不能删除");
    }


    // 如果可以删除,先删除套餐表中的数据-- setmeal
    this.removeByIds(ids);

    //再删除关系表中的数据-- setmeal_dish
    // delete from setmeal_dish where id in (1, 2, 3);
    LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.in(SetmealDish::getSetmealId, ids);
    setmealDishService.remove(queryWrapper);
}

8.4、修改套餐

SetmealController

    /**
     * 根据id查询套餐信息和对应的套餐内容---回显套餐信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R<SetmealDto> getMealDtoById(@PathVariable long id){
        SetmealDto setmealDto = setmealService.getByIdWithSetmeal(id);
        return R.success(setmealDto);
    }

    /**
     * 修改套餐信息,并保存
     * @param setmealDto
     * @return
     */
    @PutMapping
    public R<String> updateSetmeal(@RequestBody SetmealDto setmealDto) {
        log.info(setmealDto.toString());

        setmealService.updateWithSetmeal(setmealDto);
        return R.success("保存菜品成功");
    }

SetmealService

//根据id查询套餐信息和对应的套餐内容---回显套餐信息
SetmealDto getByIdWithSetmeal(long id);

//修改套餐信息,并保存
void updateWithSetmeal(SetmealDto setmealDto);

SetmealServiceImpl

    /**
     * 根据id查询套餐信息和对应的套餐内容---回显套餐信息
     *
     * @param id
     * @return
     */
    @Override
    public SetmealDto getByIdWithSetmeal(long id) {
        //查询套餐基本信息,从setmeal表查询
        Setmeal setMeal = this.getById(id);
        SetmealDto setmealDto = new SetmealDto();
        BeanUtils.copyProperties(setMeal, setmealDto);

        //查询当前套餐对应的套餐信息,从setmeal_dish表中查
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SetmealDish::getSetmealId, setMeal.getId());
        List<SetmealDish> setmealDishes = setmealDishService.list(lambdaQueryWrapper);

        setmealDto.setSetmealDishes(setmealDishes);
        return setmealDto;
    }

    /**
     * 修改套餐信息,并保存
     *
     * @param setmealDto
     */
    @Override
    @Transactional
    public void updateWithSetmeal(SetmealDto setmealDto) {
        //更新 setmeal 表基本信息
        this.updateById(setmealDto);

        //先清理当前套餐对应的套餐信息--- setmeal_dish 表的 delete 操作
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(SetmealDish::getSetmealId, setmealDto.getId());
        setmealDishService.remove(lambdaQueryWrapper);

        //再添加当前提交过来的套餐数据-- setmeal_dish表的 insert 操作
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes = setmealDishes.stream().map((item) -> {
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

        setmealDishService.saveBatch(setmealDishes);
    }

8.5、停售、启售套餐

SetmealController

/**
 * 修改套餐的 停/启 售状态
 * @param ids
 * @return
 */
@PostMapping("/status/{status}")
public R<Setmeal> status(long ids) {
    Setmeal setmeal = setmealService.getById(ids);
    setmealService.setStatus(setmeal);
    return R.success(setmeal);
}

SetmealServiceImpl

/**
 * 修改套餐的 停/启 售状态
 * @param setmeal
 */
@Override
public void setStatus(Setmeal setmeal) {
    if (setmeal.getStatus() == 1) {
        setmeal.setStatus(0);
    } else {
        setmeal.setStatus(1);
    }
    this.updateById(setmeal);
}

9、前端–手机验证码登录

9.1、短信发送

基于SpringBoot的外卖项目(详细开发过程)

阿里云短信服务

https://www.aliyun.com/product/sms?spm=5176.19720258.J_3207526240.37.4cf376f4PiAUnY

基于SpringBoot的外卖项目(详细开发过程)

https://dysms.console.aliyun.com/quickstart?spm=5176.25163407.domtextsigncreate-index-1ec3c_58c50_0.1.5097bb6euk5OnF

基于SpringBoot的外卖项目(详细开发过程)

设置签名

https://dysms.console.aliyun.com/domestic/text/sign

基于SpringBoot的外卖项目(详细开发过程)

切换到【模板管理】标签页

基于SpringBoot的外卖项目(详细开发过程)

添加模板详情

基于SpringBoot的外卖项目(详细开发过程)

设置AccessKey

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

创建用户

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

自己保存好【AccessKey Secret】

添加权限

基于SpringBoot的外卖项目(详细开发过程)

购买短信免费试用包

基于SpringBoot的外卖项目(详细开发过程)

代码开发

pom

    <!--阿里云短信服务-->
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
        <version>4.5.16</version>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
        <version>2.1.0</version>
    </dependency>

导入utils工具类

基于SpringBoot的外卖项目(详细开发过程)

9.2、手机验证码登录

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

移动端页面放行的请求

LoginCheckFilter

基于SpringBoot的外卖项目(详细开发过程)

//4-2、判断移动端用户登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("user") != null) {
    log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));

    Long userId = (Long) request.getSession().getAttribute("user");
    BaseContext.setCurrentId(userId);

    filterChain.doFilter(request, response);
    return;
}

UserController

package com.jerry.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jerry.reggie.common.R;
import com.jerry.reggie.entity.User;
import com.jerry.reggie.service.UserService;
import com.jerry.reggie.utils.SMSUtils;
import com.jerry.reggie.utils.ValidateCodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.util.Map;

/**
 * ClassName: UserController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-21 18:00
 * @Version 1.0
 */
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 发送手机验证码短信
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session){
        //获取手机号
        String phone = user.getPhone();
        if (StringUtils.isNotEmpty(phone)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}",code);
            //调用阿里云提供的短信服务API完成短信发送
            // 没有买短信包,就不发手机短信了
//            SMSUtils.sendMessage("reggie外卖","SMS_270890116",phone,code);

            // 需要将生成的验证码保存到 session 中
            session.setAttribute(phone,code);
            return R.success("手机短信验证码发送成功");
        }
        return R.error("短信发送失败");
    }

    /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());
        //获取手机号
        String phone = map.get("phone").toString();

        // 获取验证码
        String code = map.get("code").toString();

        // 从session中获取保存的验证码
        Object codeInSession = session.getAttribute(phone);

        //进行验证码的比对 (页面提交过来的验证码和session中保存的验证码进行比对)
        if (codeInSession != null && codeInSession.equals(code)){
            // 如果比对成功,说明登录成功
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getPhone,phone);

            User user = userService.getOne(lambdaQueryWrapper);

            if (user==null){
                // 判断当前手机号是否为新用户,如果是新用户就能自动完成注册
               user= new User();
               user.setPhone(phone);
               user.setStatus(1);
               userService.save(user);
            }

            session.setAttribute("user", user.getId());
           return R.success(user);
        }
        return R.error("登录失败");
    }
}

10、前端–导入用户地址簿

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

AddressBookController

package com.jerry.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jerry.reggie.common.BaseContext;
import com.jerry.reggie.common.R;
import com.jerry.reggie.entity.AddressBook;
import com.jerry.reggie.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * ClassName: AddressBookController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-21 20:42
 * @Version 1.0
 */

/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {

    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);
        addressBookService.save(addressBook);
        return R.success(addressBook);
    }

    /**
     * 设置默认地址
     */
    @PutMapping("default")
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
        log.info("addressBook:{}", addressBook);
        LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        wrapper.set(AddressBook::getIsDefault, 0);
        //SQL:update address_book set is_default = 0 where user_id = ?
        addressBookService.update(wrapper);

        addressBook.setIsDefault(1);
        //SQL:update address_book set is_default = 1 where id = ?
        addressBookService.updateById(addressBook);
        return R.success(addressBook);
    }

    /**
     * 根据id查询地址
     */
    @GetMapping("/{id}")
    public R get(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        if (addressBook != null) {
            return R.success(addressBook);
        } else {
            return R.error("没有找到该对象");
        }
    }

    /**
     * 查询默认地址
     */
    @GetMapping("default")
    public R<AddressBook> getDefault() {
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        queryWrapper.eq(AddressBook::getIsDefault, 1);

        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = addressBookService.getOne(queryWrapper);

        if (null == addressBook) {
            return R.error("没有找到该对象");
        } else {
            return R.success(addressBook);
        }
    }

    /**
     * 查询指定用户的全部地址
     */
    @GetMapping("/list")
    public R<List<AddressBook>> list(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);

        //条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);

        //SQL:select * from address_book where user_id = ? order by update_time desc
        return R.success(addressBookService.list(queryWrapper));
    }
}

11、前端–菜品展示

需求分析

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

展示flavor口味信息

// 前端需要展示flavor口味信息
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish) {
    //构造查询条件
    LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
    //查询状态为1,启售状态
    lambdaQueryWrapper.eq(Dish::getStatus, 1);
    //添加排序条件
    lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

    List<Dish> list = dishService.list(lambdaQueryWrapper);

    List<DishDto> dishDtoList = list.stream().map((item) -> {
        DishDto dishDto = new DishDto();

        //对象拷贝
        BeanUtils.copyProperties(item, dishDto);

        Long categoryId = item.getCategoryId();//分类id
        Category category = categoryService.getById(categoryId);//分类对象

        if (category != null) {
            String categoryName = category.getName();
            dishDto.setCategoryName(categoryName);
        }
        //当前菜品id
        Long dishId = item.getId();

        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dishId);

        List<DishFlavor> dishFlavorList = dishFlavorService.list(queryWrapper);
        dishDto.setFlavors(dishFlavorList);

        return dishDto;
    }).collect(Collectors.toList());

    return R.success(dishDtoList);
}

套餐信息展示

/**
 * 根据条件查询套餐数据
 *
 * @param setmeal
 * @return
 */
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal) {
    //构造查询条件
    LambdaQueryWrapper<Setmeal> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId());
    //查询状态为1,启售状态
    lambdaQueryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus());
    //添加排序条件
    lambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(lambdaQueryWrapper);

    return R.success(list);
}

12、前端–购物车

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

购物车需要实现查看/增加/减少/清空商品

package com.jerry.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jerry.reggie.common.BaseContext;
import com.jerry.reggie.common.R;
import com.jerry.reggie.entity.ShoppingCart;
import com.jerry.reggie.service.ShoppingCartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

/**
 * ClassName: ShoppingCartController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-22 10:14
 * @Version 1.0
 */
@RestController
@RequestMapping("/shoppingCart")
@Slf4j
public class ShoppingCartController {
    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     * 添加购物车
     *
     * @param shoppingCart
     * @return
     */
    @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) {
        log.info("购物车数据封装,{}", shoppingCart.toString());

        // 设置用户id,指定当前是哪个用户的购物车数据
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);


        Long dishId = shoppingCart.getDishId();

        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId, shoppingCart.getUserId());

        if (dishId != null) {
            // 添加到购物车的是菜品
            lambdaQueryWrapper.eq(ShoppingCart::getDishId, dishId);
        } else {
            // 添加到购物车的是套餐
            lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
        }

        // 查询当前菜品或套餐是否在购物车中,
        ShoppingCart cart = shoppingCartService.getOne(lambdaQueryWrapper);

        if (cart != null) {
            //如果已经存在,就在原来数量基础上加一
            Integer number = cart.getNumber();
            cart.setNumber(number + 1);
            shoppingCartService.updateById(cart);
        } else {
            // 如果不存在,则x添加到购物车,数量默认就是 1
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartService.save(shoppingCart);
            cart = shoppingCart;
        }

        return R.success(cart);
    }

    /**
     * 减少购物车的菜品
     *
     * @return
     */
    @PostMapping("/sub")
    public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart) {
        log.info("购物车数据封装,{}", shoppingCart.toString());

        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());

        if (shoppingCart.getDishId() != null) {
            // 减少到购物车的是菜品
            lambdaQueryWrapper.eq(ShoppingCart::getDishId, shoppingCart.getDishId());
        } else {
            // 减少到购物车的是套餐
            lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
        }

        // 查询当前菜品或套餐是否在购物车中,
        ShoppingCart cart = shoppingCartService.getOne(lambdaQueryWrapper);

        //如果已经存在并且数量 > 1,就在原来数量基础上 - 1
        if ((cart.getNumber() > 1)) {
            Integer number = cart.getNumber();
            cart.setNumber(number - 1);
            shoppingCartService.updateById(cart);
        } else {
            //移除改菜品或套餐
            shoppingCartService.remove(lambdaQueryWrapper);
        }
        return R.success(cart);
    }

    /**
     * 清空购物车
     *
     * @return
     */
    @DeleteMapping("/clean")
    public R<String> clean() {
        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());

        shoppingCartService.remove(lambdaQueryWrapper);

        return R.success("清空购物车成功");
    }


    /**
     * 查看购物车
     *
     * @return
     */
    @GetMapping("/list")
    public R<List<ShoppingCart>> list() {
        log.info("查看购物车...");

        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
        lambdaQueryWrapper.orderByAsc(ShoppingCart::getCreateTime);

        List<ShoppingCart> list = shoppingCartService.list(lambdaQueryWrapper);

        return R.success(list);
    }
}

13、前端–用户下单

需求分析

基于SpringBoot的外卖项目(详细开发过程)

数据模型

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

代码开发

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBoot的外卖项目(详细开发过程)

OrderController

package com.jerry.reggie.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jerry.reggie.common.R;
import com.jerry.reggie.entity.Orders;
import com.jerry.reggie.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * ClassName: OrderController
 * Package: com.jerry.reggie.controller
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-22 13:53
 * @Version 1.0
 */

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 用户下单
     * @param orders
     * @return
     */
    @PostMapping("/submit")
    public R<String> submit(@RequestBody Orders orders){
        log.info("订单数据:{}",orders);
        orderService.submit(orders);
        return R.success("下单成功");
    }

    /**
     * 订单查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/userPage")
    public R<Page> userPage(int page, int pageSize){
        //分页构造器
        Page<Orders> pageInfo = new Page<>();
        //条件构造器
        LambdaQueryWrapper<Orders> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加排序条件
        lambdaQueryWrapper.orderByDesc(Orders::getOrderTime);

        orderService.page(pageInfo, lambdaQueryWrapper);

        return R.success(pageInfo);
    }
}

OrderService

package com.jerry.reggie.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.jerry.reggie.entity.Orders;

/**
 * ClassName: OrderService
 * Package: com.jerry.reggie.service
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-22 13:52
 * @Version 1.0
 */
public interface OrderService extends IService<Orders> {

    //用户下单
    void submit(Orders orders);
}

OrderServiceImpl

package com.jerry.reggie.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jerry.reggie.common.BaseContext;
import com.jerry.reggie.common.CustomException;
import com.jerry.reggie.entity.*;
import com.jerry.reggie.mapper.OrderMapper;
import com.jerry.reggie.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * ClassName: OrderServiceImpl
 * Package: com.jerry.reggie.service.impl
 * Description:
 *
 * @Author jerry_jy
 * @Create 2023-02-22 13:52
 * @Version 1.0
 */

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {

    @Autowired
    private ShoppingCartService shoppingCartService;

    @Autowired
    private UserService userService;

    @Autowired
    private AddressBookService addressBookService;

    @Autowired
    private OrderDetailService orderDetailService;

    /**
     * 用户下单
     * @param orders
     */
    @Transactional
    @Override
    public void submit(Orders orders) {

        // 获取用户id
        Long userId = BaseContext.getCurrentId();

        // 查询当前用户购物车的数据
        LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCartList = shoppingCartService.list(lambdaQueryWrapper);

        if (shoppingCartList == null || shoppingCartList.size()==0){
           throw new CustomException("购物车为空,不能下单");
        }

        // 查询用户数据
        User user = userService.getById(userId);

        // 查询地址数据
        Long addressBookId = orders.getAddressBookId();
        AddressBook addressBook = addressBookService.getById(addressBookId);

        if (addressBook == null){
            throw new CustomException("用户地址信息有误,不能下单");
        }

        long orderId = IdWorker.getId();//生成订单号

        AtomicInteger amount = new AtomicInteger(0);

        List<OrderDetail> orderDetails= shoppingCartList.stream().map((item)->{
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setOrderId(orderId);
            orderDetail.setNumber(item.getNumber());
            orderDetail.setDishFlavor(item.getDishFlavor());
            orderDetail.setDishId(item.getDishId());
            orderDetail.setSetmealId(item.getSetmealId());
            orderDetail.setName(item.getName());
            orderDetail.setImage(item.getImage());
            orderDetail.setAmount(item.getAmount());
            amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
            return orderDetail;
        }).collect(Collectors.toList());


        orders.setId(orderId);
        orders.setOrderTime(LocalDateTime.now());
        orders.setCheckoutTime(LocalDateTime.now());
        orders.setStatus(2);
        orders.setAmount(new BigDecimal(amount.get()));//总金额
        orders.setUserId(userId);
        orders.setNumber(String.valueOf(orderId));
        orders.setUserName(user.getName());
        orders.setConsignee(addressBook.getConsignee());
        orders.setPhone(addressBook.getPhone());
        orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
                + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
                + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
                + (addressBook.getDetail() == null ? "" : addressBook.getDetail()));

        // 向订单表插入,一条数据
        this.save(orders);

        // 向订单明细表插入,多条数据
        orderDetailService.saveBatch(orderDetails);

        //清空购物车数据
        shoppingCartService.remove(lambdaQueryWrapper);
    }
}

14、代码托管

Git版本管理

基于SpringBoot的外卖项目(详细开发过程)

Gitee

https://gitee.com/jinyang-jy/reggie.git

Github

https://github.com/Jerry-jy/reggie.git

项目所需资料

链接:https://pan.baidu.com/s/182tTb1rmGkTCbq9aXrbjMg?pwd=2022
提取码:2022