目录
源码获取
reggie项目优化源码gitee
地址(优化的代码在master分支):https://gitee.com/gaoqiangmath/reggie.git
前言
本博客是根据b站瑞吉外卖视频(p1-p79)整理而来
视频链接:https://www.bilibili.com/video/BV13a411q753/?p=2&spm_id_from=pageDriver&vd_source=f4a032fee75744e378f4ac30c7e8ad39
一、项目背景介绍
技术选型
功能架构
角色
二、软件开发整体介绍
软件开发流程
角色分工
三、开发环境的搭建
3.1、数据库环境搭建
create database regiee character set utf8mb4;
db_reggie.sql
:
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50728
Source Host : localhost:3306
Source Database : reggie
Target Server Type : MYSQL
Target Server Version : 50728
File Encoding : 65001
Date: 2021-07-23 10:41:41
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for address_book
-- ----------------------------
DROP TABLE IF EXISTS `address_book`;
CREATE TABLE `address_book` (
`id` bigint(20) NOT NULL COMMENT '主键',
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`consignee` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '收货人',
`sex` tinyint(4) NOT NULL COMMENT '性别 0 女 1 男',
`phone` varchar(11) COLLATE utf8_bin NOT NULL COMMENT '手机号',
`province_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '省级区划编号',
`province_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '省级名称',
`city_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '市级区划编号',
`city_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '市级名称',
`district_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '区级区划编号',
`district_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '区级名称',
`detail` varchar(200) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '详细地址',
`label` varchar(100) CHARACTER SET utf8mb4 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(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='地址管理';
-- ----------------------------
-- Records of address_book
-- ----------------------------
INSERT INTO `address_book` 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');
INSERT INTO `address_book` VALUES ('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');
-- ----------------------------
-- Table structure for category
-- ----------------------------
DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
`id` bigint(20) NOT NULL COMMENT '主键',
`type` int(11) DEFAULT NULL COMMENT '类型 1 菜品分类 2 套餐分类',
`name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '分类名称',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_category_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
-- ----------------------------
-- Records of category
-- ----------------------------
INSERT INTO `category` VALUES ('1397844263642378242', '1', '湘菜', '1', '2021-05-27 09:16:58', '2021-07-15 20:25:23', '1', '1');
INSERT INTO `category` VALUES ('1397844303408574465', '1', '川菜', '2', '2021-05-27 09:17:07', '2021-06-02 14:27:22', '1', '1');
INSERT INTO `category` VALUES ('1397844391040167938', '1', '粤菜', '3', '2021-05-27 09:17:28', '2021-07-09 14:37:13', '1', '1');
INSERT INTO `category` VALUES ('1413341197421846529', '1', '饮品', '11', '2021-07-09 11:36:15', '2021-07-09 14:39:15', '1', '1');
INSERT INTO `category` VALUES ('1413342269393674242', '2', '商务套餐', '5', '2021-07-09 11:40:30', '2021-07-09 14:43:45', '1', '1');
INSERT INTO `category` VALUES ('1413384954989060097', '1', '主食', '12', '2021-07-09 14:30:07', '2021-07-09 14:39:19', '1', '1');
INSERT INTO `category` VALUES ('1413386191767674881', '2', '儿童套餐', '6', '2021-07-09 14:35:02', '2021-07-09 14:39:05', '1', '1');
-- ----------------------------
-- Table structure for dish
-- ----------------------------
DROP TABLE IF EXISTS `dish`;
CREATE TABLE `dish` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '菜品名称',
`category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
`price` decimal(10,2) DEFAULT NULL COMMENT '菜品价格',
`code` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '商品码',
`image` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '图片',
`description` varchar(400) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_dish_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
-- ----------------------------
-- Records of dish
-- ----------------------------
INSERT INTO `dish` VALUES ('1397849739276890114', '辣子鸡', '1397844263642378242', '7800.00', '222222222', 'f966a38e-0780-40be-bb52-5699d13cb3d9.jpg', '来自鲜嫩美味的小鸡,值得一尝', '1', '0', '2021-05-27 09:38:43', '2021-05-27 09:38:43', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397850140982161409', '毛氏红烧肉', '1397844263642378242', '6800.00', '123412341234', '0a3b3288-3446-4420-bbff-f263d0c02d8e.jpg', '毛氏红烧肉毛氏红烧肉,确定不来一份?', '1', '0', '2021-05-27 09:40:19', '2021-05-27 09:40:19', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397850392090947585', '组庵鱼翅', '1397844263642378242', '4800.00', '123412341234', '740c79ce-af29-41b8-b78d-5f49c96e38c4.jpg', '组庵鱼翅,看图足以表明好吃程度', '1', '0', '2021-05-27 09:41:19', '2021-05-27 09:41:19', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397850851245600769', '霸王别姬', '1397844263642378242', '12800.00', '123412341234', '057dd338-e487-4bbc-a74c-0384c44a9ca3.jpg', '还有什么比霸王别姬更美味的呢?', '1', '0', '2021-05-27 09:43:08', '2021-05-27 09:43:08', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397851099502260226', '全家福', '1397844263642378242', '11800.00', '23412341234', 'a53a4e6a-3b83-4044-87f9-9d49b30a8fdc.jpg', '别光吃肉啦,来份全家福吧,让你长寿又美味', '1', '0', '2021-05-27 09:44:08', '2021-05-27 09:44:08', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397851370462687234', '邵阳猪血丸子', '1397844263642378242', '13800.00', '1246812345678', '2a50628e-7758-4c51-9fbb-d37c61cdacad.jpg', '看,美味不?来嘛来嘛,这才是最爱吖', '1', '0', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397851668262465537', '口味蛇', '1397844263642378242', '16800.00', '1234567812345678', '0f4bd884-dc9c-4cf9-b59e-7d5958fec3dd.jpg', '爬行界的扛把子,东兴-口味蛇,让你欲罢不能', '1', '0', '2021-05-27 09:46:23', '2021-05-27 09:46:23', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397852391150759938', '辣子鸡丁', '1397844303408574465', '8800.00', '2346812468', 'ef2b73f2-75d1-4d3a-beea-22da0e1421bd.jpg', '辣子鸡丁,辣子鸡丁,永远的魂', '1', '0', '2021-05-27 09:49:16', '2021-05-27 09:49:16', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397853183287013378', '麻辣兔头', '1397844303408574465', '19800.00', '123456787654321', '2a2e9d66-b41d-4645-87bd-95f2cfeed218.jpg', '麻辣兔头的详细制作,麻辣鲜香,色泽红润,回味悠长', '1', '0', '2021-05-27 09:52:24', '2021-05-27 09:52:24', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397853709101740034', '蒜泥白肉', '1397844303408574465', '9800.00', '1234321234321', 'd2f61d70-ac85-4529-9b74-6d9a2255c6d7.jpg', '多么的有食欲啊', '1', '0', '2021-05-27 09:54:30', '2021-05-27 09:54:30', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397853890262118402', '鱼香肉丝', '1397844303408574465', '3800.00', '1234212321234', '8dcfda14-5712-4d28-82f7-ae905b3c2308.jpg', '鱼香肉丝简直就是我们童年回忆的一道经典菜,上学的时候点个鱼香肉丝盖饭坐在宿舍床上看着肥皂剧,绝了!现在完美复刻一下上学的时候感觉', '1', '0', '2021-05-27 09:55:13', '2021-05-27 09:55:13', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397854652581064706', '麻辣水煮鱼', '1397844303408574465', '14800.00', '2345312·345321', '1fdbfbf3-1d86-4b29-a3fc-46345852f2f8.jpg', '鱼片是买的切好的鱼片,放几个虾,增加味道', '1', '0', '2021-05-27 09:58:15', '2021-05-27 09:58:15', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397854865672679425', '鱼香炒鸡蛋', '1397844303408574465', '2000.00', '23456431·23456', '0f252364-a561-4e8d-8065-9a6797a6b1d3.jpg', '鱼香菜也是川味的特色。里面没有鱼却鱼香味', '1', '0', '2021-05-27 09:59:06', '2021-05-27 09:59:06', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397860242057375745', '脆皮烧鹅', '1397844391040167938', '12800.00', '123456786543213456', 'e476f679-5c15-436b-87fa-8c4e9644bf33.jpeg', '“广东烤鸭美而香,却胜烧鹅说古冈(今新会),燕瘦环肥各佳妙,君休偏重便宜坊”,可见烧鹅与烧鸭在粤菜之中已早负盛名。作为广州最普遍和最受欢迎的烧烤肉食,以它的“色泽金红,皮脆肉嫩,味香可口”的特色,在省城各大街小巷的烧卤店随处可见。', '1', '0', '2021-05-27 10:20:27', '2021-05-27 10:20:27', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397860578738352129', '白切鸡', '1397844391040167938', '6600.00', '12345678654', '9ec6fc2d-50d2-422e-b954-de87dcd04198.jpeg', '白切鸡是一道色香味俱全的特色传统名肴,又叫白斩鸡,是粤菜系鸡肴中的一种,始于清代的民间。白切鸡通常选用细骨农家鸡与沙姜、蒜茸等食材,慢火煮浸白切鸡皮爽肉滑,清淡鲜美。著名的泮溪酒家白切鸡,曾获商业部优质产品金鼎奖。湛江白切鸡更是驰名粤港澳。粤菜厨坛中,鸡的菜式有200余款之多,而最为人常食不厌的正是白切鸡,深受食家青睐。', '1', '0', '2021-05-27 10:21:48', '2021-05-27 10:21:48', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397860792492666881', '烤乳猪', '1397844391040167938', '38800.00', '213456432123456', '2e96a7e3-affb-438e-b7c3-e1430df425c9.jpeg', '广式烧乳猪主料是小乳猪,辅料是蒜,调料是五香粉、芝麻酱、八角粉等,本菜品主要通过将食材放入炭火中烧烤而成。烤乳猪是广州最著名的特色菜,并且是“满汉全席”中的主打菜肴之一。烤乳猪也是许多年来广东人祭祖的祭品之一,是家家都少不了的应节之物,用乳猪祭完先人后,亲戚们再聚餐食用。', '1', '0', '2021-05-27 10:22:39', '2021-05-27 10:22:39', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397860963880316929', '脆皮乳鸽', '1397844391040167938', '10800.00', '1234563212345', '3fabb83a-1c09-4fd9-892b-4ef7457daafa.jpeg', '“脆皮乳鸽”是广东菜中的一道传统名菜,属于粤菜系,具有皮脆肉嫩、色泽红亮、鲜香味美的特点,常吃可使身体强健,清肺顺气。随着菜品制作工艺的不断发展,逐渐形成了熟炸法、生炸法和烤制法三种制作方法。无论那种制作方法,都是在鸽子经过一系列的加工,挂脆皮水后再加工而成,正宗的“脆皮乳鸽皮脆肉嫩、色泽红亮、鲜香味美、香气馥郁。这三种方法的制作过程都不算复杂,但想达到理想的效果并不容易。', '1', '0', '2021-05-27 10:23:19', '2021-05-27 10:23:19', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397861683434139649', '清蒸河鲜海鲜', '1397844391040167938', '38800.00', '1234567876543213456', '1405081e-f545-42e1-86a2-f7559ae2e276.jpeg', '新鲜的海鲜,清蒸是最好的处理方式。鲜,体会为什么叫海鲜。清蒸是广州最经典的烹饪手法,过去岭南地区由于峻山大岭阻隔,交通不便,经济发展起步慢,自家打的鱼放在锅里煮了就吃,没有太多的讲究,但却发现这清淡的煮法能使鱼的鲜甜跃然舌尖。', '1', '0', '2021-05-27 10:26:11', '2021-05-27 10:26:11', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397862198033297410', '老火靓汤', '1397844391040167938', '49800.00', '123456786532455', '583df4b7-a159-4cfc-9543-4f666120b25f.jpeg', '老火靓汤又称广府汤,是广府人传承数千年的食补养生秘方,慢火煲煮的中华老火靓汤,火候足,时间长,既取药补之效,又取入口之甘甜。 广府老火汤种类繁多,可以用各种汤料和烹调方法,烹制出各种不同口味、不同功效的汤来。', '1', '0', '2021-05-27 10:28:14', '2021-05-27 10:28:14', '1', '1', '0');
INSERT INTO `dish` VALUES ('1397862477831122945', '上汤焗龙虾', '1397844391040167938', '108800.00', '1234567865432', '5b8d2da3-3744-4bb3-acdc-329056b8259d.jpeg', '上汤焗龙虾是一道色香味俱全的传统名菜,属于粤菜系。此菜以龙虾为主料,配以高汤制成的一道海鲜美食。本品肉质洁白细嫩,味道鲜美,蛋白质含量高,脂肪含量低,营养丰富。是色香味俱全的传统名菜。', '1', '0', '2021-05-27 10:29:20', '2021-05-27 10:29:20', '1', '1', '0');
INSERT INTO `dish` VALUES ('1413342036832100354', '北冰洋', '1413341197421846529', '500.00', '', 'c99e0aab-3cb7-4eaa-80fd-f47d4ffea694.png', '', '1', '0', '2021-07-09 11:39:35', '2021-07-09 15:12:18', '1', '1', '0');
INSERT INTO `dish` VALUES ('1413384757047271425', '王老吉', '1413341197421846529', '500.00', '', '00874a5e-0df2-446b-8f69-a30eb7d88ee8.png', '', '1', '0', '2021-07-09 14:29:20', '2021-07-12 09:09:16', '1', '1', '0');
INSERT INTO `dish` VALUES ('1413385247889891330', '米饭', '1413384954989060097', '200.00', '', 'ee04a05a-1230-46b6-8ad5-1a95b140fff3.png', '', '1', '0', '2021-07-09 14:31:17', '2021-07-11 16:35:26', '1', '1', '0');
-- ----------------------------
-- Table structure for dish_flavor
-- ----------------------------
DROP TABLE IF EXISTS `dish_flavor`;
CREATE TABLE `dish_flavor` (
`id` bigint(20) NOT NULL COMMENT '主键',
`dish_id` bigint(20) NOT NULL COMMENT '菜品',
`name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '口味名称',
`value` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '口味数据list',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品口味关系表';
-- ----------------------------
-- Records of dish_flavor
-- ----------------------------
INSERT INTO `dish_flavor` VALUES ('1397849417888346113', '1397849417854791681', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:37:27', '2021-05-27 09:37:27', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397849739297861633', '1397849739276890114', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:38:43', '2021-05-27 09:38:43', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397849739323027458', '1397849739276890114', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:38:43', '2021-05-27 09:38:43', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397849936421761025', '1397849936404983809', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:39:30', '2021-05-27 09:39:30', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397849936438538241', '1397849936404983809', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:39:30', '2021-05-27 09:39:30', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850141015715841', '1397850140982161409', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:40:19', '2021-05-27 09:40:19', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850141040881665', '1397850140982161409', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:40:19', '2021-05-27 09:40:19', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850392120307713', '1397850392090947585', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:41:19', '2021-05-27 09:41:19', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850392137084929', '1397850392090947585', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:41:19', '2021-05-27 09:41:19', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850630734262274', '1397850630700707841', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:42:16', '2021-05-27 09:42:16', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850630755233794', '1397850630700707841', '辣度', '[\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:42:16', '2021-05-27 09:42:16', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850851274960898', '1397850851245600769', '忌口', '[\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:43:08', '2021-05-27 09:43:08', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397850851283349505', '1397850851245600769', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:43:08', '2021-05-27 09:43:08', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851099523231745', '1397851099502260226', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:44:08', '2021-05-27 09:44:08', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851099527426050', '1397851099502260226', '辣度', '[\"不辣\",\"微辣\",\"中辣\"]', '2021-05-27 09:44:08', '2021-05-27 09:44:08', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851370483658754', '1397851370462687234', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851370483658755', '1397851370462687234', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851370483658756', '1397851370462687234', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397851668283437058', '1397851668262465537', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:46:23', '2021-05-27 09:46:23', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397852391180120065', '1397852391150759938', '忌口', '[\"不要葱\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:49:16', '2021-05-27 09:49:16', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397852391196897281', '1397852391150759938', '辣度', '[\"不辣\",\"微辣\",\"重辣\"]', '2021-05-27 09:49:16', '2021-05-27 09:49:16', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397853183307984898', '1397853183287013378', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:52:24', '2021-05-27 09:52:24', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397853423486414850', '1397853423461249026', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:53:22', '2021-05-27 09:53:22', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397853709126905857', '1397853709101740034', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:54:30', '2021-05-27 09:54:30', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397853890283089922', '1397853890262118402', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:55:13', '2021-05-27 09:55:13', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397854133632413697', '1397854133603053569', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:56:11', '2021-05-27 09:56:11', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397854652623007745', '1397854652581064706', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:58:15', '2021-05-27 09:58:15', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397854652635590658', '1397854652581064706', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:58:15', '2021-05-27 09:58:15', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397854865735593986', '1397854865672679425', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:59:06', '2021-05-27 09:59:06', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397855742303186946', '1397855742273826817', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:02:35', '2021-05-27 10:02:35', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397855906497605633', '1397855906468245506', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:03:14', '2021-05-27 10:03:14', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397856190573621250', '1397856190540066818', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:04:21', '2021-05-27 10:04:21', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397859056709316609', '1397859056684150785', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:15:45', '2021-05-27 10:15:45', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397859277837217794', '1397859277812051969', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:16:37', '2021-05-27 10:16:37', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397859487502086146', '1397859487476920321', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:17:27', '2021-05-27 10:17:27', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397859757061615618', '1397859757036449794', '甜味', '[\"无糖\",\"少糖\",\"半躺\",\"多糖\",\"全糖\"]', '2021-05-27 10:18:32', '2021-05-27 10:18:32', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397860242086735874', '1397860242057375745', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:20:27', '2021-05-27 10:20:27', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397860963918065665', '1397860963880316929', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:23:19', '2021-05-27 10:23:19', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397861135754506242', '1397861135733534722', '甜味', '[\"无糖\",\"少糖\",\"半躺\",\"多糖\",\"全糖\"]', '2021-05-27 10:24:00', '2021-05-27 10:24:00', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397861370035744769', '1397861370010578945', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:24:56', '2021-05-27 10:24:56', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397861683459305474', '1397861683434139649', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:26:11', '2021-05-27 10:26:11', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397861898467717121', '1397861898438356993', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:27:02', '2021-05-27 10:27:02', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397862198054268929', '1397862198033297410', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:28:14', '2021-05-27 10:28:14', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1397862477835317250', '1397862477831122945', '辣度', '[\"不辣\",\"微辣\",\"中辣\"]', '2021-05-27 10:29:20', '2021-05-27 10:29:20', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398089545865015297', '1398089545676271617', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-28 01:31:38', '2021-05-28 01:31:38', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398089782323097601', '1398089782285348866', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:32:34', '2021-05-28 01:32:34', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398090003262255106', '1398090003228700673', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:33:27', '2021-05-28 01:33:27', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398090264554811394', '1398090264517062657', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:34:29', '2021-05-28 01:34:29', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398090455399837698', '1398090455324340225', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:35:14', '2021-05-28 01:35:14', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398090685449023490', '1398090685419663362', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-28 01:36:09', '2021-05-28 01:36:09', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398090825358422017', '1398090825329061889', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:36:43', '2021-05-28 01:36:43', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398091007051476993', '1398091007017922561', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:37:26', '2021-05-28 01:37:26', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398091296164851713', '1398091296131297281', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:38:35', '2021-05-28 01:38:35', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398091546531246081', '1398091546480914433', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:39:35', '2021-05-28 01:39:35', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398091729809747969', '1398091729788776450', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:40:18', '2021-05-28 01:40:18', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398091889499484161', '1398091889449152513', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:40:56', '2021-05-28 01:40:56', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398092095179763713', '1398092095142014978', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:41:45', '2021-05-28 01:41:45', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398092283877306370', '1398092283847946241', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:42:30', '2021-05-28 01:42:30', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398094018939236354', '1398094018893099009', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:49:24', '2021-05-28 01:49:24', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1398094391494094850', '1398094391456346113', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:50:53', '2021-05-28 01:50:53', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1399574026165727233', '1399305325713600514', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-06-01 03:50:25', '2021-06-01 03:50:25', '1399309715396669441', '1399309715396669441', '0');
INSERT INTO `dish_flavor` VALUES ('1413389540592263169', '1413384757047271425', '温度', '[\"常温\",\"冷藏\"]', '2021-07-12 09:09:16', '2021-07-12 09:09:16', '1', '1', '0');
INSERT INTO `dish_flavor` VALUES ('1413389684020682754', '1413342036832100354', '温度', '[\"常温\",\"冷藏\"]', '2021-07-09 15:12:18', '2021-07-09 15:12:18', '1', '1', '0');
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '姓名',
`username` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户名',
`password` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '密码',
`phone` varchar(11) COLLATE utf8_bin NOT NULL COMMENT '手机号',
`sex` varchar(2) COLLATE utf8_bin NOT NULL COMMENT '性别',
`id_number` varchar(18) COLLATE utf8_bin NOT NULL COMMENT '身份证号',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';
-- ----------------------------
-- Records of employee
-- ----------------------------
INSERT INTO `employee` VALUES ('1', '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '13812312312', '1', '110101199001010047', '1', '2021-05-06 17:20:07', '2021-05-10 02:24:09', '1', '1');
-- ----------------------------
-- Table structure for orders
-- ----------------------------
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` bigint(20) NOT NULL COMMENT '主键',
`number` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '订单号',
`status` int(11) NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消',
`user_id` bigint(20) NOT NULL COMMENT '下单用户',
`address_book_id` bigint(20) NOT NULL COMMENT '地址id',
`order_time` datetime NOT NULL COMMENT '下单时间',
`checkout_time` datetime NOT NULL COMMENT '结账时间',
`pay_method` int(11) NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝',
`amount` decimal(10,2) NOT NULL COMMENT '实收金额',
`remark` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
`phone` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`address` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`user_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`consignee` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';
-- ----------------------------
-- Records of orders
-- ----------------------------
-- ----------------------------
-- Table structure for order_detail
-- ----------------------------
DROP TABLE IF EXISTS `order_detail`;
CREATE TABLE `order_detail` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '名字',
`image` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
`order_id` bigint(20) NOT NULL COMMENT '订单id',
`dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
`setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
`dish_flavor` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
`number` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
`amount` decimal(10,2) NOT NULL COMMENT '金额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单明细表';
-- ----------------------------
-- Records of order_detail
-- ----------------------------
-- ----------------------------
-- Table structure for setmeal
-- ----------------------------
DROP TABLE IF EXISTS `setmeal`;
CREATE TABLE `setmeal` (
`id` bigint(20) NOT NULL COMMENT '主键',
`category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
`name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '套餐名称',
`price` decimal(10,2) NOT NULL COMMENT '套餐价格',
`status` int(11) DEFAULT NULL COMMENT '状态 0:停用 1:启用',
`code` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '编码',
`description` varchar(512) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
`image` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_setmeal_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐';
-- ----------------------------
-- Records of setmeal
-- ----------------------------
INSERT INTO `setmeal` VALUES ('1415580119015145474', '1413386191767674881', '儿童套餐A计划', '4000.00', '1', '', '', '61d20592-b37f-4d72-a864-07ad5bb8f3bb.jpg', '2021-07-15 15:52:55', '2021-07-15 15:52:55', '1415576781934608386', '1415576781934608386', '0');
-- ----------------------------
-- Table structure for setmeal_dish
-- ----------------------------
DROP TABLE IF EXISTS `setmeal_dish`;
CREATE TABLE `setmeal_dish` (
`id` bigint(20) NOT NULL COMMENT '主键',
`setmeal_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '套餐id ',
`dish_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '菜品id',
`name` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '菜品名称 (冗余字段)',
`price` decimal(10,2) DEFAULT NULL COMMENT '菜品原价(冗余字段)',
`copies` int(11) NOT NULL COMMENT '份数',
`sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
`create_time` datetime NOT NULL COMMENT '创建时间',
`update_time` datetime NOT NULL COMMENT '更新时间',
`create_user` bigint(20) NOT NULL COMMENT '创建人',
`update_user` bigint(20) NOT NULL COMMENT '修改人',
`is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐菜品关系';
-- ----------------------------
-- Records of setmeal_dish
-- ----------------------------
INSERT INTO `setmeal_dish` VALUES ('1415580119052894209', '1415580119015145474', '1397862198033297410', '老火靓汤', '49800.00', '1', '0', '2021-07-15 15:52:55', '2021-07-15 15:52:55', '1415576781934608386', '1415576781934608386', '0');
INSERT INTO `setmeal_dish` VALUES ('1415580119061282817', '1415580119015145474', '1413342036832100354', '北冰洋', '500.00', '1', '0', '2021-07-15 15:52:55', '2021-07-15 15:52:55', '1415576781934608386', '1415576781934608386', '0');
INSERT INTO `setmeal_dish` VALUES ('1415580119069671426', '1415580119015145474', '1413385247889891330', '米饭', '200.00', '1', '0', '2021-07-15 15:52:55', '2021-07-15 15:52:55', '1415576781934608386', '1415576781934608386', '0');
-- ----------------------------
-- Table structure for shopping_cart
-- ----------------------------
DROP TABLE IF EXISTS `shopping_cart`;
CREATE TABLE `shopping_cart` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '名称',
`image` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
`user_id` bigint(20) NOT NULL COMMENT '主键',
`dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
`setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
`dish_flavor` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
`number` int(11) 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=utf8 COLLATE=utf8_bin COMMENT='购物车';
-- ----------------------------
-- Records of shopping_cart
-- ----------------------------
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键',
`name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
`phone` varchar(100) COLLATE utf8_bin NOT NULL COMMENT '手机号',
`sex` varchar(2) COLLATE utf8_bin DEFAULT NULL COMMENT '性别',
`id_number` varchar(18) COLLATE utf8_bin DEFAULT NULL COMMENT '身份证号',
`avatar` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '头像',
`status` int(11) DEFAULT '0' COMMENT '状态 0:禁用,1:正常',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息';
数据表的说明
3.2、maven项目搭建
步骤一:创建maven项目
步骤二:检查环境
步骤三:导入pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>reggie_take_out</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</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.6.6</version>
</plugin>
</plugins>
</build>
</project>
步骤四:创建resources/application.yml 文件
server:
port: 8080
spring:
application:
# 应用的名称,选择性配置
name: reggie_take_out
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
#mysql6以及以上 使用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: 123456
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
# 把SQL的查询的过程输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
#主键的生成策略,雪花算法 雪花算法和前端交互会造成精度丢失 (后面会处理)
id-type: ASSIGN_ID
步骤五:创建主程序com.gq.reggie.ReggieApplcation
package com.gq.reggie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j
@SpringBootApplication
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
log.info("reggie项目启动成功");
}
}
步骤六:开启热部署
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
步骤七:启动主程序,检查是否成功
3.3、导入前端文件
将前端文件:backend
以及front
导入到resources
目录下
注意前端文件的位置,在SpringBoot
项目中,前台默认就只能访问resource
目录下的static和template
文件夹下的文件;所以如果要使用这种方式,
所以静态资源的处理方式一:
- 直接创建一个
static
目录就行,然后把这些前端资源放在这个static
目录下 - 不想把前端文件放在这两个默认的文件夹下,那么就可以自己定义
mvc
的支持,本项目使用的就是这种方式
新建config包,在此目录下创建
WebMvcConfig
类
WebMvcConfig.java
package com.gq.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;
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
四、后台登陆功能开发
4.1、需求分析
需求分析是通过产品原型来进行的,这个是项目经理负责的;
4.2、代码开发
前端页面访问地址:http://localhost:8080/backend/page/login/login.html
查看登陆请求信息:点击登录会发送登录请求:http://localhost:8080/employee/login
根据前端页面
backend/page/login/login.html
,得知后端应该响应code
、data
、msg
的信息
后端书写相应代码结构
4.2.1、实体类entity
书写实体类:
entity/Employee.java
package com.gq.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;
}
4.2.2、Mapper
创建
mapper/EmployeeMapper
接口
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
4.2.3、service
书写
service/EmployeeService
接口
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.Employee;
public interface EmployeeServcie extends IService<Employee> {
}
书写service/impl/EmployeeServiceImpl类
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.Employee;
import com.gq.reggie.mapper.EmployeeMapper;
import com.gq.reggie.service.EmployeeServcie;
import org.springframework.stereotype.Service;
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeServcie {
}
4.2.4、封装返回的结果类
创建一个新的包,
common
,用来存放共同使用的类,新建common.R类
package com.gq.reggie.common;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@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 add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
4.2.5、controller
书写
controller/EmployeeController
类
package com.gq.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gq.reggie.common.R;
import com.gq.reggie.entity.Employee;
import com.gq.reggie.service.EmployeeServcie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
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.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeServcie employeeServcie;
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
//接收前端的json数据,这个json数据是在请求体中的
//这里为什么还有接收一个request对象的数据?
//登陆成功后,我们需要从请求中获取员工的id,并且把这个id存到session中,这样我们想要获取登陆对象的时候就可以随时获取
//1、将页面提交的密码password进行md5加密处理
String password = employee.getPassword();//从前端用户登录拿到的用户数据
password = DigestUtils.md5DigestAsHex(password.getBytes());//对用户密码进行MD5加密
//2、根据页面提交的用户名查找数据库
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper();
//判断登录端来的userName与数据库的userName是否相同
lambdaQueryWrapper.eq(Employee::getUsername, employee.getUsername());
//在设计数据库的时候我们对username使用了唯一索引,所以这里可以使用getOne方法,得到了数据库中Name和登录端Name一致的用户对象
Employee emp = employeeServcie.getOne(lambdaQueryWrapper);
//3、如果没有查询到那么返回登陆失败
if (emp == null) {
R.error("用户不存在!!");
}
//4、密码比对,如果密码错误,则返回登录失败
if (!password.equals(emp.getPassword())) {
return R.error("密码错误!!");
}
//5、查看员工状态,如果是已禁用状态,则登陆失败
if (emp.getStatus() == 0) {
return R.error("账号已禁用!!");
}
//6、登录成功,将员工ID保存到session,并且返回登陆成功消息
request.getSession().setAttribute("employee", emp.getId());
//把从数据库查询到的用户返回出去
return R.success(emp);
}
}
4.3、功能测试
登录:http://localhost:8080/backend/page/login/login.html
用户名:admin 密码:123456
登录模块优化(添加过滤器)
实现步骤:
1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@ServletComponentScan
3、完善过滤器处理逻辑
步骤一:新建包
filter
,在此目录下自定义LoginCheckFilter
package com.gq.reggie.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 检查用户是否已经登陆
*/
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*") //所有的请求都拦截
@Slf4j
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("拦截到请求{}", request.getRequestURI());
//放行
filterChain.doFilter(request, response);
}
}
步骤二:在启动类上添加注解
@ServletComponentScan
以便过滤器能够被扫描到
步骤三:完善过滤器处理逻辑
package com.gq.reggie.filter;
import com.alibaba.fastjson.JSON;
import com.gq.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;
/**
* 检查用户是否已经完成登陆
* filterName过滤器名字
* urlPatterns拦截的请求,这里是拦截所有的请求
*/
@WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*")
@Slf4j
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 {
//对请求和响应进行强转,我们需要的是带http的
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURL = request.getRequestURI();
//定义不需要处理的请求路径 比如静态资源(静态页面我们不需要拦截,因为此时的静态页面是没有数据的)
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
//直接去看页面,不拦截,因为看到的页面没有任何数据
"/backend/**",
"/front/**"
};
//做调试用的
//log.info("拦截到请求:{}",requestURL);
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURL);
//3、如果不需要处理,则直接放行
if (check) {
//log.info("本次请求{}不需要处理",requestURL);
filterChain.doFilter(request, response);
return;
}
//4、判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
//log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request, response);
return;
}
//log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据,具体响应什么数据,看前端的需求,然后前端会根据登陆状态做页面跳转
//为什么要这样写,看前端页面 backend/js/request.js 里面的相应拦截器
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
/**
* 路径匹配,检查本次请求是否需要放行
*
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls, String requestURI) {
for (String url : urls) {
//把浏览器发过来的请求和我们定义的不拦截的url做比较,匹配则放行
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
return true;
}
}
return false;
}
}
功能测试: 发起几个请求看看后台的输出,和能不能访问到资源里面的数据,和能不能跳转,注意,上面的后台日志代码已经被注释,需要在后台看到日志的话,需要把注释去掉;
五、后台系统退出
后端代码处理:
- 在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
- 清理session中的用户id
- 返回结果(前端页面会进行跳转到登录页面)
前端代码,也要把浏览器中的数据给清除;
功能测试:先登陆,然后退出即可;看浏览器中的数据是否会被清除;
六、员工管理模块
6.1、添加员工
需求分析:
数据模型:
新增员工,其实就是将我们的新增页面录入的员工数据插入到employee表
;注意:employee表
中对username
字段加入了唯一的约束,因为username
是员工的登陆账号,必须是唯一的!,除此以外,status
也默认是1
看employee
的建表sql语句
-- auto-generated definition
create table employee
(
id bigint not null comment '主键'
primary key,
name varchar(32) not null comment '姓名',
username varchar(32) not null comment '用户名',
password varchar(64) not null comment '密码',
phone varchar(11) not null comment '手机号',
sex varchar(2) not null comment '性别',
id_number varchar(18) not null comment '身份证号',
status int default 1 not null 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(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
constraint idx_username
unique (username)
)
comment '员工信息' collate = utf8_bin
employee表
中的status
字段默认设置为1,表示员工状态可以正常登陆;
代码开发:EmployeeController.java 中新增 增加员工的代码
/**
* 新增员工
*
*/
@PostMapping() //因为请求就是 /employee 在类上已经写了,所以咱俩不用再写了
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
//对新增的员工设置初始化密码123456,需要进行md5加密处理,后续员工可以直接修改密码
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户的id
Long emp = (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(emp);//创建人的id,就是当前用户的id(在进行添加操作的id)
employee.setUpdateUser(emp);//最后更新的人是谁
//新增员工
employeeServcie.save(employee);
return R.success("新增员工成功!!");
}
**功能测试:**登陆之后,点击添加,然后确认,然后去数据库看一下新增数据成功没,新增成功,那就表示代码可以执行; 注意:但是因为我们把username设置为唯一索引,所以下次再新增用户的时候,就会出现异常,这个异常是MySQL数据库抛出来的;
补充:全局异常捕获
在common
包下面,书写全局异常捕获代码
package com.gq.reggie.common;
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.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.sql.SQLIntegrityConstraintViolationException;
//RestControllerAdvice注解相当于@RestController 以及@ControllerAdvice
@RestControllerAdvice(annotations = {RestController.class, Controller.class}) //RestController 以及Controller注解下的类都会被扫描到
@Slf4j
public class GlobalExceptionHandler {
/**
* 进行异常处理
* 处理 SQLIntegrityConstraintViolationException 异常 也就是添加用户的时候 重复添加
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exCeptionHandler(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage()); //报错 打出日志
if (exception.getMessage().contains("Duplicate entry")) {
//获取已经存在的用户名,这里是从报错的异常信息中获取的
String[] s = exception.getMessage().split(" ");
String msg =s[2] + "这个用户已经存在!";
return R.error("用户名已存在");
}
return R.error("未知错误!");
}
}
**功能测试:**登陆后,添加一个一个已经存在账号名,看前端页面提示的是什么信息,以及看后台是否输出了报错日志;
6.2、员工信息分页查询
需求分析:
系统中的员工比较多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般都系统中都会以分页的方式来展示列表数据。
流程分析:
代码实现:由于分页功能是Mybatis-plus实现,需要提前添加一个基于mybatis-plus的拦截器
拦截器:在config包下添加MybatisPlusConfig配置类
package com.gq.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;
@Configuration
public class MybatisPlusConfig {
/**
* 配置mybatis-plus提供的分页插件拦截器
*
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
Controller编写:实现分页
/**
* 员工信息分页
*
* @param page 当前页数
* @param pageSize 当前页最多存放数据条数,就是这一页查几条数据
* @param name 根据name查询员工的信息
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
//这里之所以是返回page对象(mybatis-plus的page对象),是因为前端需要这些分页的数据(比如当前页,总页数)
//在编写前先测试一下前端传过来的分页数据有没有被我们接受到
//log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器 就是page对象
IPage<Employee> pageInfo = new Page<>(page,pageSize);
//构造条件构造器 就是动态的封装前端传过来的过滤条件 记得加泛型
LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>();
//根据条件查询 注意这里的条件是不为空
lqw.like(Strings.isNotEmpty(name), Employee::getName, name);
//添加一个排序条件
lqw.orderByDesc(Employee::getCreateTime);
//执行查询 这里不用封装了mybatis-plus帮我们做好了
employeeServcie.page(pageInfo, lqw);
return R.success((Page) pageInfo);
}
功能测试:分页的三个时机,①用户登录成功时,分页查询一次 ②用户使用条件查询的时候分页一次 ③跳转页面的时候分页查询一次
6.3、启动/禁用员工账号
需求分析:
在员工管理列表页面中,可以对某个员工账号进行启用或者是禁用操作。账号禁用的员工不能登陆系统,启用后的员工可以正常登陆;
需要注意的是:只有管理员(admin用户)才可以对其他普通用户进行启用操作,禁用操作,所以普通用户登录系统后启用,禁用按钮不显示;
并且如果某个员工账号的状态为正常,则按钮显示为’‘禁用’,如果员工账号状态为已禁用,则按钮显示为“启用”。
普通员工登录系统后,启用,禁用按钮不显示;
代码开发:
注意:这里修改状态码要反着来,因为正常的用户你只能把它设置为禁用;已经禁用的账号你只能把它设置为正常
流程分析:
Controller代码书写
**注意:**启用,禁用的员工账号,本质上就是一个更新操作,也就是对status状态字段进行修改操作;
在controller
中创建update
方法,此方法是一个通用的修改员工信息的方法,因为status
也是employee
中的一个属性而已;这里使用了动态SQL的功能,根据具体的数据修改对应的字段信息;
功能测试:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?
原因是:mybatis-plus
对id使用了雪花算法
,所以存入数据库中的id是19为长度
,但是前端的js只能保证数据的前16位的数据的精度
,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失;就会出现前度传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据;
当然一种解决bug的方法是:关闭mybatis-plus的雪花算法来处理ID,我们使用自增ID的策略来往数据库添加id就行;也即在application.yml中,将id-type 换成 AUTO
补充:使用自定义消息转换器
代码bug修复:
思路:既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端(Java端)给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串
;
代码实现步骤:
**步骤一:自定义消息转换类,在common目录下新建JacksonObjectMapper **
package com.gq.reggie.common;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
//这个类在配置类WebMvcConfig中直接new了 所以没有添加注解交给spring容器
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance) //将long形的转换为字符串
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
步骤二:在前面的webMvcConfig 配置类中扩展spring mvc的消息转换器,在此消息转换器中使用spring提供的对象转换器进行Java对象到json数据的转换;
/**
* 扩展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);
}
然后启动程序,使用f12查看服务器响应到浏览器的用户id是不是变成了字符串,和数据库中是否相对应;
然后再去测试 启用与禁用 员工账号这个功能,发现操作更新成功,并且数据库修改成功;
6.4、编辑员工信息
需求分析:
数据回显后端代码:其实主要逻辑在前端。。。。。
/**
* 编辑员工的时候,根据id将要修改的员工信息回显给前端
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
Employee employee = employeeServcie.getById(id);
if (employee != null) {
return R.success(employee);
}
return R.error("没有查询到该员工信息");
}
6.5、公共字段填充(这里有重点)
问题分析:
代码实现:
步骤一:在实体类中添加TableField注解
@TableField(fill = FieldFill.INSERT) //插入时填充字段
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) //插入和更新的时候填充字段
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT) //插入时填充字段
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE) //插入和更新的时候填充字段
private Long updateUser;
步骤二:在
common
包目录下,新建MyMetaObjecthandler
类 以实现MetaObjectHandler
接口
package com.gq.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义元数据对象处理器
*/
@Slf4j
@Component //注意:这个要记得交给spring容器管理,不然这个功能就没发用。。。。
//那么怎么确定你要添加的功能是不是要交给容器管理呢?就是你直接写了一个工具类或者是功能类,需要对数据库的数据或者是数据库数据的结果产生影响的时候,你明明写了这样一个类,但是功能却没有生效,那么这个时候就要首先考虑是不是容器没有托管这个类
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", new Long(1)); //这里的id是不能直接获取的,所以这里先写死,后面教你怎么动态获取员工id
metaObject.setValue("updateUser", new Long(1));
}
/**
* 更新操作,自动填充
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", new Long(1));
}
}
功能完善:
然后为了动态的获取员工的id,这里我们使用了threadLocal这个局部变量来获取和存储员工id;
创建一个工具类来设置和获取threadLocal中的员工id, 注意:要先把数据设置进threadLocal中,才能获取到
功能完善第一步:在
common
目录下,编写BaseContext
工具类
package com.gq.reggie.common;
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
//用来存储用户的id 为什么泛型是Long 因为Employee中Id类型为Long
private static ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
/**
* 设置值
*
* @param id
*/
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
/**
* 获取值
*
* @return
*/
public static Long getCurrentId() {
return threadLocal.get();
}
}
功能完善第二步:在前面我们写的
LongCheckFilter
这个过滤器中,把这个地方的代码加上添加和保存id的代码
//4、判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
//log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
//把用户id存储到本地的threadLocal
Long employeeId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(employeeId);
filterChain.doFilter(request, response);
return;
功能完善第三步:把MyMetaObjecthandler 中原来写死的部分替换掉
metaObject.setValue("createUser", BaseContext.getCurrentId()); //这里的id通过ThreadLocal获取到(BaseContext是为了得到ThreadLocal中的值创建的工具类)
metaObject.setValue("updateUser", BaseContext.getCurrentId());
本节最终功能测试:
将原本EmployeeController中对公共数据的添加操作注释掉
// employee.setCreateTime(LocalDateTime.now());
// employee.setUpdateTime(LocalDateTime.now());
// employee.setUpdateUser((Long) request.getSession().getAttribute("employee"));
// employee.setUpdateTime(LocalDateTime.now());
再执行增加员工操作:
发现公共数据(create_time、update_time、create_user、update_user),被自动补全了
七、菜品分类管理
7.1、新增分类
需求分析:
数据模型:
entity
包的Category
实体类
package com.gq.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//创建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除(逻辑删除)
@TableLogic
private Integer isDeleted;
}
数据库中的表结构:
-- auto-generated definition
create table category
(
id bigint not null comment '主键'
primary key,
type int null comment '类型 1 菜品分类 2 套餐分类',
name varchar(64) not null comment '分类名称',
sort int default 0 not 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 '修改人',
constraint idx_category_name
unique (name)
)
comment '菜品及套餐分类' collate = utf8_bin;
mapper目录下创建CategoryMapper接口
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.Category;
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
service目录下创建CategoryService接口以及CategoryServiceImpl实现类
CategoryService
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.Category;
public interface CategoryService extends IService<Category> {
}
CategoryServiceImpl
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.Category;
import com.gq.reggie.mapper.CategoryMapper;
import com.gq.reggie.service.CategoryService;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
controller目录下创建CategoryController类
代码开发:
package com.gq.reggie.controller;
import com.gq.reggie.common.R;
import com.gq.reggie.entity.Category;
import com.gq.reggie.service.CategoryService;
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;
/**
* 菜品分类管理
*/
@RestController
@Slf4j
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 新增菜品
* @param category
* @return
*/
@PostMapping
public R<String> save(@RequestBody Category category) {
log.info("{}",category);
categoryService.save(category);
return R.success("新增分类成功");
}
}
功能测试:登录后,点击添加新增菜品分类,看是否成功,数据库的数据是否变化;
7.2、菜品类的分页
代码开发:
/**
* 分页查询
*
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(@RequestParam int page, @RequestParam int pageSize) {
//创建一个分页构造器
Page<Category> categoryPage = new Page<>(page, pageSize);
//创建一个条件构造器 用来排序用的 注意这个条件构造器一定要使用泛型,否则使用条件查询这个方法的时候会报错
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
//添加排序条件 ,根据sort字段进行排序
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(categoryPage, queryWrapper);
return R.success(categoryPage);
}
功能测试:
7.3、删除分类(这里有注意点)
需求分析:
需要注意的是:当分类关联了菜品或者套餐时,不允许删除此分类
代码实现: 注意这里的删除功能是不完整的,因为可能需要删除的数据是与其他表关联的,所以删除之前要先判断该条数据是否与其他表中的数据关联;
/**
* 根据id来删除分类的数据
* @param id
* @return
*/
@DeleteMapping()
public R<String> delete(@RequestParam("ids") Long ids){ //注意这里前端传过来的数据是ids
categoryService.removeById(ids);
return R.success("分类信息删除成功");
}
功能完善:
功能完善步骤一:实体类Dish、Setmeal
Dish
package com.gq.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
菜品
*/
@Data
public class Dish implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//商品码
private String code;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
//顺序
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除(逻辑删除)
@TableLogic
private Integer isDeleted;
}
Setmeal
package com.gq.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 套餐
*/
@Data
public class Setmeal implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//分类id
private Long categoryId;
//套餐名称
private String name;
//套餐价格
private BigDecimal price;
//状态 0:停用 1:启用
private Integer status;
//编码
private String code;
//描述信息
private String description;
//图片
private String image;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除(逻辑删除)
@TableLogic
private Integer isDeleted;
}
功能完善步骤二:DishMapper、SetmealMapper
DishMapper
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.Dish;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}
SetmealMapper
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.Setmeal;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}
功能完善步骤三:DishService、DIshServiceImpl、SetmealSevice、SetmealServiceImpl
DishService
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.Dish;
import org.springframework.stereotype.Service;
public interface DishService extends IService<Dish> {
}
DIshServiceImpl
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.Dish;
import com.gq.reggie.mapper.DishMapper;
import com.gq.reggie.service.DishService;
import org.springframework.stereotype.Service;
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}
SetmealSevice
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
}
SetmealServiceImpl
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.Setmeal;
import com.gq.reggie.mapper.SetmealMapper;
import com.gq.reggie.service.SetmealService;
import org.springframework.stereotype.Service;
@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
}
功能完善步骤四:在CategoryService添加自定义方法
remove(Long id)
添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现)
//在CategoryService中定义自己需要的方法,直接写就行
void remove(Long id);
功能完善步骤五:
common
包下面自定义一个异常类CustomException
,因为在CategoryServiceImpl
中实现自定义方法要抛出异常
package com.gq.reggie.common;
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
功能完善步骤六:在全局异常捕获
GlobalExceptionHandler
中添加自定义的异常类CustomException
//GlobalExceptionHandler全局异常捕获器中添加自定义的CustomException异常,这样就可以把相关的异常信息显示给前端操作的人员看见
/**
* 处理自定义的异常,为了让前端展示我们的异常信息,这里需要把异常进行全局捕获,然后返回给前端
*
* @param exception
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandle(CustomException exception) {
log.error(exception.getMessage()); //报错记得打日志
//这里拿到的message是业务类抛出的异常信息,我们把它显示到前端
return R.error(exception.getMessage());
}
功能完善步骤七:
CategoryServiceImpl
中实现自定义方法remove(Long id)
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.common.CustomException;
import com.gq.reggie.entity.Category;
import com.gq.reggie.entity.Dish;
import com.gq.reggie.entity.Setmeal;
import com.gq.reggie.mapper.CategoryMapper;
import com.gq.reggie.mapper.DishMapper;
import com.gq.reggie.mapper.SetmealMapper;
import com.gq.reggie.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishMapper dishMapper;
@Autowired
private SetmealMapper setmealMapper;
/**
* 根据id删除 分类,删除之前需要进行判断是否有关联数据
* @param id
*/
@Override
public void remove(Long id) {
//添加条件查询
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//首先判断此id 是否关联了菜品
dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);
int dishCount = dishMapper.selectCount(dishLambdaQueryWrapper);
//查询当前分类是否关联了菜品,如果已经管理,直接抛出一个业务异常
if (dishCount > 0) {
//已经关联了菜品,抛出一个异常
throw new CustomException("当前分类项关联了菜品,不能删除");
}
//添加条件查询
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//然后判断此id 是否关联了套餐
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);
int setmealCount = setmealMapper.selectCount(setmealLambdaQueryWrapper);
//查询当前分类是否关联了套餐,如果已经管理,直接抛出一个业务异常
if (setmealCount > 0) {
//已经关联了套餐,抛出一个异常
throw new CustomException("当前分类项关联了套餐,不能删除");
}
//正常删除
super.removeById(id);
}
}
功能完善步骤八:在
CategoryController
中调用刚刚实现的自定义remove(Long id)
方法把之前的removeById
方法替换掉
/**
* 根据id来删除分类的数据
*
* @param ids
* @return
*/
@DeleteMapping()
public R<String> delete(@RequestParam("ids") Long ids) { //注意这里前端传过来的数据是ids
categoryService.remove(ids); //自定义的删除方法
return R.success("分类信息删除成功");
}
功能测试:自己添加测试数据测试就行;记得一定要测试一下删除有相关联的数据,看会不会删除和在前端提示异常信息;
7.4、修改分类
这里的编辑的数据回显,前端已经帮我们做好了,所以我们就不需要去数据库查询了,这样可以减少对数据库的操作;
/**
* 根据id修改分类
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
categoryService.updateById(category);
return R.success("修改分类信息成功");
}
记得在对应的实体类加上公共字段的值设置:前面我们配置了这个,所以这里只需要加注解就行;
//创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//创建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
八、菜品管理的业务功能
8.1、文件的上传和下载
(此节为了提前学习文件上传和下载的使用,后续菜品管理部分关于图片上传的部分直接使用此节的代码,首先导入一个静态资源:目录:backen/page/demo/upload.html
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
</head>
<body>
<div class="addBrand-container" id="food-add-app">
<div class="container">
<el-upload class="avatar-uploader"
action="/common/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeUpload"
ref="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/index.js"></script>
<script>
new Vue({
el: '#food-add-app',
data() {
return {
imageUrl: ''
}
},
methods: {
handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
},
beforeUpload (file) {
if(file){
const suffix = file.name.split('.')[1]
const size = file.size / 1024 / 1024 < 2
if(['png','jpeg','jpg'].indexOf(suffix) < 0){
this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
this.$refs.upload.clearFiles()
return false
}
if(!size){
this.$message.error('上传文件大小不能超过 2MB!')
return false
}
return file
}
}
}
})
</script>
</body>
</html>
整体介绍:
文件下载:
后端具体代码的实现:前端页面为前面介绍的backen/page/demo/upload.html
yml配置文件:配置上传图片要保存的位置;
#文件储存位置
reggie:
path: D:\Study\Items\reggie\pictures\ #最后面一定别忘加\
步骤一:在controller包目录下新建CommonController类(表示公共的controller)
package com.gq.reggie.controller;
import com.gq.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
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 javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
/**
* 此公共controller用以文件上传和下载(包括图片)
*/
@Slf4j
@RestController
@RequestMapping("/common")
public class CommonController {
@Value("${reggie.path}")
private String basePath;
/**
* 文件的上传(将前端传递来的图片保存到本地)
*
* @param file
* @return
*/
@PostMapping("/upload")
public R<String> upload(MultipartFile file) {
//这里这个file不能乱取,要和前端页面中相同,也即上传图片的框框所指代的名称相同, 见此代码块后面的 注意
//这个file是一个临时文件,需要转存到指定位置,否则本次请求完成后,临时文件会消失
//拿到文件的原始名
String originalFilename = file.getOriginalFilename();
//拿到文件的后缀名 例如 .png .jpg
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//使用uuid生成的作为文件名的一部分,这样可以防止文件名相同造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix;
//创建一个目录对象,看传文件的时候,接收文件的目录名是否存在
File dir = new File(basePath);
if (!dir.exists()) {
//文件目录不存在,直接创建一个目录
dir.mkdir();
}
try {
//把前端传过来的文件进行转存
file.transferTo(new File(basePath + fileName));
//如果想要保存的时候就要原始的图片名:file.transferTo(new File(basePath + originalFilename));
} catch (IOException e) {
e.printStackTrace();
}
//这个服务端需要根据这个fileName,将下载到本地的图片,再次回显到前端页面(就是下面的下载方法)
return R.success(fileName);
}
/**
* 文件下载(将前端传递来的图片数据(已经保存到本地)再次回显给前端)
*
* @param name
* @param response
*/
//backend/page/demo/upload.html中这样写的 this.imageUrl = `/common/download?name=${response.data}
@GetMapping("/download")
public void download(String name, HttpServletResponse response) {
//参数name 就是上面的 return R.success(fileName);
try {
//输入流,通过输入流读取文件内容 这里的name是前台用户需要下载的文件的文件名
//new File(basePath + name) 是为了从存储图片的地方获取用户需要的图片对象
FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//输出流,通过输出流将文件写回浏览器
ServletOutputStream outputStream = response.getOutputStream();
//设置写回去的文件类型
response.setContentType("image/jpeg");
//定义缓存区,准备读写文件
int len = 0;
byte[] buff = new byte[1024];
while ((len = fileInputStream.read(buff)) != -1) {
outputStream.write(buff, 0, len);
outputStream.flush();
}
//关流
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:public R<String> upload(MultipartFile file)
中的 file
要和下面这个地方的一样,接收文件的参数的名不能随便定义,要和下面的name
的值一致;
功能测试:
点击上传图片,后端代码将图片保存到指定地方(文件上传),并且图片回显到前端(文件下载)
(注意:有可能失败,因为之前加入了拦截器,重新登陆一下就可以了)
8.2、新增菜品(业务的实现是重点)
8.2.1、需求分析:
8.2.2、数据模型:
8.2.3、代码开发:
DishFlavor
package com.gq.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
菜品口味
*/
@Data
public class DishFlavor implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//菜品id
private Long dishId;
//口味名称
private String name;
//口味数据list
private String value;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除(逻辑删除)
@TableLogic
private Integer isDeleted;
}
DishFlavorMapper
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {
}
DishFlavorService
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.DishFlavor;
public interface DishFlavorService extends IService<DishFlavor> {
}
DishFlavorServiceImpl
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.DishFlavor;
import com.gq.reggie.mapper.DishFlavorMapper;
import com.gq.reggie.service.DishFlavorService;
import org.springframework.stereotype.Service;
@Service
public class DishFlavorServiceImpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService {
}
DishController
8.2.3.1、交互过程1
完成下图菜品分类
前端代码:
// 获取菜品分类列表
const getCategoryList = (params) => { //这个params参数是category表中的type type = 1表示菜品分类 type = 2表示套餐分类
return $axios({
url: '/category/list',
method: 'get',
params
})
}
if (res.code === 1) {
this.dishList = res.data //这里就相当于把所有的category对象的数据赋值给dishList
}
//这是菜品分类和数据双向绑定的前端代码: 我们返回的是一个集合,
</el-form-item>
<el-form-item
label="菜品分类:"
prop="categoryId"
>
<el-select
v-model="ruleForm.categoryId"
placeholder="请选择菜品分类"
>
<el-option v-for="(item,index) in dishList" :key="index" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
在CategoryController
书写查询代码,不过这里的返回值和参数接收值可能和自己想的有点不一样
。。。这个的返回值和参数值 值得多思考一下; 这里之所以返回list
集合,是因为这个要展示的数据是引用类型的数据集,集合可以存放任意类型的数据;
/**
* 根据条件(菜品管理模块中新增菜品页面传来的type type=1 表示菜品分类 type=2 表示套餐分类)查询菜品分类
*/
@GetMapping("/list")
//这个接口接收到参数其实就是一个前端传过来的type,这里之所以使用Category这个类来接受前端的数据,是为了以后方便
//因为这个Category类里面包含了type这个数据,返回的数据多了,你自己用啥取啥就行
public R<List<Category>> list(Category category) {
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件
queryWrapper.eq(category.getType() != null, Category::getType, category.getType());
//添加排序条件 使用两个排序条件,如果sort相同的情况下就使用更新时间进行排序
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> categories = categoryService.list(queryWrapper);
return R.success(categories);
}
测试:
8.2.3.2、交互过程2、3
上传图片(保存到指定位置),并且服务端回显照片
见前面文件上传与下载
8.2.3.3、交互过程4
点击保存按钮,将菜品相关数据保存到服务端(DTO)
点击保存按钮的时候,把前端的json
数据提交到后台,后台接收数据,对数据进行处理;要与两张表打交道,一个是dish
一个是dish_flavor
表;
看前端传递来的json
数据可以知道,前端json
数据和实体类无法对应,因此导入新的模块——DTO
新建
dto
包,在此包下新建DishDto
类
package com.gq.reggie.dto;
import com.gq.reggie.entity.Dish;
import com.gq.reggie.entity.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName; //后面要用的
private Integer copies; //后面要用的
}
前端关键代码:
<el-button
type="primary"
@click="submitForm('ruleForm')"
>
保存
</el-button>
let params = {...this.ruleForm}
// params.flavors = this.dishFlavors
params.status = this.ruleForm ? 1 : 0
params.price *= 100 //存到数据库的时候是以分为单位,所以这里x100
params.categoryId = this.ruleForm.categoryId
params.flavors = this.dishFlavors.map(obj => ({ ...obj, value: JSON.stringify(obj.value) }))
if (this.actionType == 'add') {
delete params.id
addDish(params).then(res => {
if (res.code === 1) {
this.$message.success('菜品添加成功!')
if (!st) {
this.goBack()
} else { ....
// 新增接口
const addDish = (params) => {
return $axios({
url: '/dish',
method: 'post',
data: { ...params }
})
}
后端代码:
在DishService
中新增一个方法:
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.dto.DishDto;
import com.gq.reggie.entity.Dish;
public interface DishService extends IService<Dish> {
//新增菜品,同时插入菜品对应的口味数据,需要两张表:dish、dish_flavor
public void saveWhthFlavor(DishDto dishDto);
}
DishServiceImpl
中实现新增方法:
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.dto.DishDto;
import com.gq.reggie.entity.Dish;
import com.gq.reggie.entity.DishFlavor;
import com.gq.reggie.mapper.DishMapper;
import com.gq.reggie.service.DishFlavorService;
import com.gq.reggie.service.DishService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
/**
* 新增菜品,同时保存口味数据
*
* @param dishDto
*/
@Autowired
private DishFlavorService dishFlavorService;
@Override
@Transactional //涉及到对多张表的数据进行操作,需要加事务,需要事务生效,需要在启动类加上事务注解@EnableTransactionManagement生效
public void saveWhthFlavor(DishDto dishDto) {
//保存菜品的基本信息到dish表
this.save(dishDto);
//将sidhDto中的id取出来,因为也要把这个id保存到dish_flavor表中部
Long dishId = dishDto.getId();
//保存菜品的口味信息到dish_flavor表
List<DishFlavor> flavors = dishDto.getFlavors();
//注意dish_flavor只是封装了name value 并没有封装dishId(从前端传过来的数据发现的,然而数据库又需要这个数据)
//先把flavors集合取出来,让后遍历,把每一个flavor注入dishId 然后再重新注入到一个集合中
ArrayList<DishFlavor> dishFlavors = new ArrayList<>();
for (DishFlavor dishFlavor : flavors) {
dishFlavor.setDishId(dishId);
dishFlavors.add(dishFlavor);
}
dishFlavorService.saveBatch(dishFlavors);//这个方法是批量保存
}
}
在启动类开启事务: 加上这个注解就行 @EnableTransactionManagement
DishController
package com.gq.reggie.controller;
import com.gq.reggie.common.R;
import com.gq.reggie.dto.DishDto;
import com.gq.reggie.service.DishFlavorService;
import com.gq.reggie.service.DishService;
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;
/**
* 菜品管理模块
*/
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
public R<String> save(@RequestBody DishDto dishDto) {//前端提交的是json数据的话,我们在后端就要使用这个注解来接收参数,否则接收到的数据全是null
dishService.saveWhthFlavor(dishDto);
return R.success("新增菜品成功");
}
}
功能测试:
8.3、菜品信息分页查询
功能完善里面的代码要熟悉,有集合泛型的转换,对象copy
需求分析:
图片下载的请求前面已经写好了,前端也写好了相关的请求,所以第二步的图片下载和展示就不需要我们管了;
代码编写:
controller层的代码:不过这里是有bug的,后面会改善;
DishController
中添加:
/**
* 菜品信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//构造一个分页构造器对象
Page<Dish> dishPage = new Page<>(page,pageSize);
//构造一个条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件 注意判断是否为空 使用对name的模糊查询
queryWrapper.like(name != null,Dish::getName,name);
//添加排序条件 根据更新时间降序排
queryWrapper.orderByDesc(Dish::getUpdateTime);
//去数据库处理分页 和 查询
dishService.page(dishPage,queryWrapper);
//因为上面处理的数据没有分类的id,这样直接返回R.success(dishPage)虽然不会报错,但是前端展示的时候这个菜品分类这一数据就为空
return R.success(dishPage);
}
菜品分类没有对应的值:因为dish表中只有分类id:category_id 没有具体的分类:category_name
功能完善:引入了DishDto
功能完善版的DishController
package com.gq.reggie.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gq.reggie.common.R;
import com.gq.reggie.dto.DishDto;
import com.gq.reggie.entity.Category;
import com.gq.reggie.entity.Dish;
import com.gq.reggie.service.CategoryService;
import com.gq.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* 菜品管理模块
*/
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private CategoryService categoryService;
/**
* 新增菜品
*
* @param dishDto
* @return
*/
@PostMapping
public R<String> save(@RequestBody DishDto dishDto) {//前端提交的是json数据的话,我们在后端就要使用这个注解来接收参数,否则接收到的数据全是null
dishService.saveWhthFlavor(dishDto);
return R.success("新增菜品成功");
}
/**
* 菜品信息分页查询
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name) {
//构造一个分页构造器对象
Page<Dish> dishPage = new Page<>(page, pageSize);
Page<DishDto> dishDtoPage = new Page<>(page, pageSize);
//上面对dish泛型的数据已经赋值了,这里对DishDto我们可以把之前的数据拷贝过来进行赋值
//构造一个条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加过滤条件 注意判断是否为空 使用对name的模糊查询
queryWrapper.like(name != null, Dish::getName, name);
//添加排序条件 根据更新时间降序排
queryWrapper.orderByDesc(Dish::getUpdateTime);
//去数据库处理分页 和 查询
dishService.page(dishPage, queryWrapper);
//拷贝dishPage的内容(包括currentPage,pageSize等等)到dishDtoPage,但是不要拷贝dishPage中的records,因为records表示dishPage中的具体数据,这个需要自己再加东西进去
//对象拷贝 使用框架自带的工具类,第三个参数是不拷贝到属性
BeanUtils.copyProperties(dishPage, dishDtoPage, "records");
//获取到dish的所有数据 records属性是分页插件中表示分页中所有的数据的一个集合
List<Dish> records = dishPage.getRecords();
List<DishDto> dishDtosList = records.stream().map((item) -> {
//对实体类DishDto进行categoryName的设值
DishDto dishDto = new DishDto();
//这里的item相当于Dish 对dishDto进行除categoryName属性的拷贝
BeanUtils.copyProperties(item, dishDto);
//获取分类的id
Long categoryId = item.getCategoryId();
//通过分类id获取分类对象
Category category = categoryService.getById(categoryId);
if (category != null) {
dishDto.setCategoryName(category.getName()); //将categoryName插入进去
}
return dishDto;
}).collect(Collectors.toList());
dishDtoPage.setRecords(dishDtosList);
//因为上面处理的数据没有分类的id,这样直接返回R.success(dishPage)虽然不会报错,但是前端展示的时候这个菜品分类这一数据就为空
//所以进行了上面的一系列操作
return R.success(dishDtoPage);
}
}
功能测试:
8.4、修改菜品
(回显和保存修改都是两张表)
需求分析:
代码开发:
第一次交互的后端代码已经完成了;菜品分类的信息前面做新增菜品的时候就已经完成了,这里前端发一个相关接口的请求就行;
第三次交互,图片的下载前面也已经写了,所以前端直接发生请求就行;
8.6、菜品信息的回显
DishService
中添加自定义方法getByIdWithFlavor
//根据id来查询菜品信息和对应的口味信息
public DishDto getByIdWithFlavor(Long id);
DishServiceImpl
中实现自定义方法getByIdWithFlavor
@Autowired
private DishFlavorService dishFlavorService;
/**
* 根据id查询对应的菜品信息和口味信息
*
* @param id
* @return
*/
@Override
public DishDto getByIdWithFlavor(Long id) {
//查询菜品的基本信息 从dish表查询
Dish dish = this.getById(id);
//查询当前菜品对应的口味信息,从dish_flavor查询 条件查询
LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>();
dishFlavorLambdaQueryWrapper.eq(id != null, DishFlavor::getDishId, id);
List<DishFlavor> dishFlavorList = dishFlavorService.list(dishFlavorLambdaQueryWrapper);
//然后把查询出来的flavors数据set进行 DishDto对象
DishDto dishDto = new DishDto();
//把dish表中的基本信息copy到dishDto对象,因为才创建的dishDto里面的属性全是空
BeanUtils.copyProperties(dish, dishDto);
dishDto.setFlavors(dishFlavorList);
return dishDto;
}
DishController
中调用方法getByIdWithFlavor
8.7、保存修改(重点)
DishService
中添加自定义方法updateWithFlavor
:
//修改菜品信息以及对应的口味
public void updateWithFlavor(DishDto dishDto);
DishServiceImpl
中实现自定义方法updateWithFlavor
:
@Autowired
private DishFlavorService dishFlavorService;
/**
* 修改菜品信息以及对应的口味
*
* @param dishDto
*/
@Override
@Transactional
public void updateWithFlavor(DishDto dishDto) {
//更新dish表的基本信息 因为这里的dishDto是dish的子类
this.updateById(dishDto);
//更新口味信息---》先清理再重新插入口味信息
//清理当前菜品对应口味数据---dish_flavor表的delete操作
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(queryWrapper);
//添加当前提交过来的口味数据---dish_flavor表的insert操作
List<DishFlavor> flavors = dishDto.getFlavors();
//下面这段流的代码我注释,然后测试,发现一次是报dishId没有默认值(先测),两次可以得到结果(后测,重新编译过,清除缓存过),相隔半个小时
//因为这里拿到的flavorsz只有name和value(这是在设计数据封装的问题),不过debug测试的时候发现有时候可以拿到全部数据,有时候又不可以... 所以还是加上吧。。。。。
flavors = flavors.stream().map((item) -> {
item.setDishId(dishDto.getId());
return item;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
DishController
中调用自定义方法updateWithFlavor
:
@Autowired
private DishService dishService;
/**
* 修改菜品以及口味信息
* @param dishDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody DishDto dishDto) {
dishService.updateWithFlavor(dishDto);
return R.success("菜品修改成功");
}
功能测试:修改一个菜品
九、套餐管理
9.1、新增套餐
9.1.1、需求分析:
9.1.2、数据模型:
9.1.3、代码开发:
准备工作:
实体类SetmeanDish
package com.gq.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 套餐菜品关系
*/
@Data
public class SetmealDish implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//套餐id
private Long setmealId;
//菜品id
private Long dishId;
//菜品名称 (冗余字段)
private String name;
//菜品原价
private BigDecimal price;
//份数
private Integer copies;
//排序
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除(逻辑删除)
@TableLogic
private Integer isDeleted;
}
SetmealDishMapper
package com.gq.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gq.reggie.entity.SetmealDish;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}
SetmealDishService
以及 SetmealDishServiceImpl
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.entity.SetmealDish;
public interface SetmealDishService extends IService<SetmealDish> {
}
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.entity.SetmealDish;
import com.gq.reggie.mapper.SetmealDishMapper;
import com.gq.reggie.service.SetmealDishService;
import org.springframework.stereotype.Service;
@Service
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService {
}
SetmealDto
package com.gq.reggie.dto;
import com.gq.reggie.entity.Setmeal;
import com.gq.reggie.entity.SetmealDish;
import lombok.Data;
import java.util.List;
@Data
public class SetmealDto extends Setmeal {
private List<SetmealDish> setmealDishes;
private String categoryName;
}
DishController
9.1.3.1、交互流程1
第一个交互前面(8.2.3.1)写了;分类管理通过type的值来控制在前端展示的是 菜品分类(type=1) 或者是 套餐分类(type=2)
9.1.3.2、交互流程2和3
DishController
中加入方法
@Autowired
private DishService dishService;
/**
* 再套餐管理模块中:根据条件查询对应的菜品数据
*
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<Dish>> getDishByCategoryId(Dish dish) {//会自动映射的
//这里可以传categoryId,但是为了代码通用性更强,这里直接使用dish类来接受(因为dish里面是有categoryId的),以后传dish的其他属性这里也可以使用
//构造查询条件
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish != null, Dish::getCategoryId, dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus, 1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> dishList = dishService.list(queryWrapper);
return R.success(dishList);
}
9.1.3.3、交互流程4和5
这一个步骤已经在上面 8.1、文件的上传和下载 中完成了
9.1.3.4、交互流程6
保存添加的套餐:需要将套餐数据分别保存到setmeal
以及setmeal_dish
表中
SetmealService
package com.gq.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gq.reggie.common.R;
import com.gq.reggie.dto.SetmealDto;
import com.gq.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
/**
* 套餐管理模块: 保存新增的套餐
*/
void saveSetmeal(SetmealDto setmealDto);
}
SetmealServiceImpl
package com.gq.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gq.reggie.common.R;
import com.gq.reggie.dto.SetmealDto;
import com.gq.reggie.entity.Setmeal;
import com.gq.reggie.entity.SetmealDish;
import com.gq.reggie.mapper.SetmealMapper;
import com.gq.reggie.service.SetmealDishService;
import com.gq.reggie.service.SetmealService;
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;
@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
@Autowired
private SetmealDishService setmealDish;
/**
* 套餐管理模块: 保存新增的套餐
*/
@Override
@Transactional //开启事物 需要在启动类中加上@EnableTransactionManagement 才会生效
public void saveSetmeal(SetmealDto setmealDto) {
//首先将套餐基本信息保存至setmeal表中
this.save(setmealDto);
//取出setmeal表中的id,因为前端传递过来的setmealDishes里面没有setmealId
Long setmealId = setmealDto.getId();
//其次保存套餐里面的菜品(也即前端传来的setmealDishes)到setmeal_dish表中
// 由于前端传递过来的setmealDishes里面没有setmealId,所以先要加进去setmealId
List<SetmealDish> mealDishes = setmealDto.getSetmealDishes();
//加入setmealId
mealDishes = mealDishes.stream().map((item) -> {
item.setSetmealId(setmealId);
return item;
}).collect(Collectors.toList());
setmealDish.saveBatch(mealDishes);
}
}
SetmealController
package com.gq.reggie.controller;
import com.gq.reggie.common.R;
import com.gq.reggie.dto.SetmealDto;
import com.gq.reggie.service.SetmealService;
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;
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 套餐管理模块中:保存新增的套餐
* @param setmealDto
* @return
*/
@PostMapping
public R<String> saveSetmeal(@RequestBody SetmealDto setmealDto) {
setmealService.saveSetmeal(setmealDto);
return R.success("套餐添加成功!!");
}
}
功能测试:新增一个套餐
9.2、套餐信息分页查询
由前端页面可知,套餐分类
这一列,需要categoryName
字段 ,因此需要添加DTO
,SetmealDto
类中就添加了categoryName
字段
SetmealService
/**
* 套餐管理模块:套裁分页展示
*
* @param page
* @param pageSize
* @param name
* @return
*/
Page<SetmealDto> pageSetmeal(int page, int pageSize, String name);
SetmealServiceImpl
@Autowired
private CategoryService categoryService;
/**
* 新增套餐模块:套餐分页展示
*
* @param page
* @param pageSize
* @param name
* @return
*/
@Override
public Page<SetmealDto> pageSetmeal(int page, int pageSize, String name) {
//新建page
Page<Setmeal> setmealPage = new Page<>(page, pageSize);
Page<SetmealDto> setmealDtoPage = new Page<>(page, pageSize);
//新建条件查询 根据name
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.like(name != null, Setmeal::getName, name);
//得到分页并且一句name做条件查询的setmealPage
this.page(setmealPage, setmealLambdaQueryWrapper);
//克隆setmealPage到setmealDtoPage 但是忽略掉 records,因为后面要重写,加入categoryName这一个字段
BeanUtils.copyProperties(setmealPage, setmealDtoPage, "records");
//得到setmealPage的records,进行重写
List<Setmeal> records = setmealPage.getRecords();
//得到新的records,命名为setmealDtoList 里面包含了categoryName字段
List<SetmealDto> setmealDtoList = records.stream().map((item) -> {
//item就是List<Setmeal> 中的每一个Setmeal对象
SetmealDto setmealDto = new SetmealDto();
//将item中的内容克隆到setmealDto中
BeanUtils.copyProperties(item, setmealDto);
//得到categryName
Category category = categoryService.getById(item.getCategoryId());
if (category != null) {
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
//将新的命名为setmealDtoList的records,注入到Page<SetmealDto> setmealDtoPage 中
setmealDtoPage.setRecords(setmealDtoList);
return setmealDtoPage;
}
SetmealController
/**
* 新增套餐模块:套餐分页展示
*
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page<SetmealDto>> page(int page, int pageSize, String name) {
Page<SetmealDto> setmealDtoPage = setmealService.pageSetmeal(page, pageSize, name);
return R.success(setmealDtoPage);
}
9.3、删除套餐
单个套餐删除:
批量套餐删除:
SetmealService
:
/**
* 删除套餐,同时需要删除套餐和菜品的关联数据
*
* @param ids
*/
void removeWithDish(List<Long> ids);
SetmealServiceImpl
:
@Autowired
private SetmealDishService setmealDishService;
/**
* 删除套餐,同时需要删除套餐和菜品的关联数据
* @param ids
*/
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
//sql语句应该是这样的:select count(*) setmeal where id in () and status = 1;
//查询套餐的状态,看是否可以删除
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
//如果不能删除,抛出一个业务异常
if (count > 0){
throw new CustomException("套餐正在售卖中,不能删除");
}
//如果可以删除,先删除套餐表中的数据--setmeal
this.removeByIds(ids);
//删除关系表中的数据--setmeal_dish
//delete from setmeal_dish where setmeal_id in (1,2,3)
LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper();
lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
setmealDishService.remove(lambdaQueryWrapper);
}
SetmealController
/**
* 套餐删除
* @param ids
* @return
*/
@DeleteMapping
public R<String> deleteByIds(@RequestParam(name = "ids") List<Long> ids) {
setmealService.removeWithDish(ids);
return R.success("套餐删除成功!!");
}
功能测试:测试删除套餐、批量删除套餐