瑞吉外卖项目 —— 开发日志


让我看看几天能完成

瑞吉外卖项目

项目介绍

  • 本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。
  • 其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。
  • 本项目共分为3期进行开发:

    • 第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。
    • 第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。
    • 第三期主要针对系统进行优化升级,提高系统的访问性能。

技术选型

功能架构

角色

  • 后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限。
  • 后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理。
  • C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等。

MySQL

/*
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 '修改人',
        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='用户信息';

数据表

pom.xml(SpringBoot、MP、MySQL)

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.li</groupId>
  <artifactId>ruiji</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>ruiji</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.2</version>
    </dependency>

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

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <!--<version>1.18.12</version>-->
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.18</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>2.3.7.RELEASE</version>
        <configuration>
          <mainClass>com.li.RuijiApplication</mainClass>
        </configuration>
        <executions>
          <execution>
            <id>repackage</id>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

application.yml

  • 黑马的有些错误。
server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3307/ruiji?serverTimezone=UTC
    username: root
    password: 1234
    type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

启动类(RuijiApplication)

//@Slf4j注解需要在maven里配置lombok
@Slf4j
@SpringBootApplication
public class RuijiApplication {

  public static void main(String[] args) {
    SpringApplication.run(RuijiApplication.class, args);
    log.info("项目启动成功");
  }

}

静态资源

  • 静态资源需要放在 resources 下的 static 下。
  • 不通过static也可以,但要配置springmvc拦截器。
  • 复制静态资源如果不能访问,请重启idea。
  • 测试:http://localhost:8080/backend/index.html

后台登录功能开发

登录页面(login.html)

  • http://localhost:8080/backend/page/login/login.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="shortcut icon" href="../../favicon.ico">
  <!-- 引入样式 -->
  <link rel="stylesheet" href="../../plugins/element-ui/index.css" />
  <link rel="stylesheet" href="../../styles/common.css">
  <link rel="stylesheet" href="../../styles/login.css">
  <link rel="stylesheet" href="../../styles/icon/iconfont.css" />
  <style>
    .body{
      min-width: 1366px;
    }
  </style>
</head> 

<body>
  <div class="login" id="login-app">
    <div class="login-box">
      <img src="../../images/login/login-l.png" alt="">
      <div class="login-form">
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" >
          <div class="login-form-title">
            <img src="../../images/login/logo.png" style="width:139px;height:42px;" alt="" />
          </div>
          <el-form-item prop="username">
            <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号" maxlength="20"
              prefix-icon="iconfont icon-user" />
          </el-form-item>
          <el-form-item prop="password">
            <el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="iconfont icon-lock" maxlength="20"
              @keyup.enter.native="handleLogin" />
          </el-form-item>
          <el-form-item style="width:100%;">
            <el-button :loading="loading" class="login-btn" size="medium" type="primary" style="width:100%;"
              @click.native.prevent="handleLogin">
              <span v-if="!loading">登录</span>
              <span v-else>登录中...</span>
            </el-button>
          </el-form-item>
        </el-form>
      </div>
    </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/request.js"></script>
  <script src="../../js/validate.js"></script>
  <script src="../../api/login.js"></script>

  <script>
    new Vue({
      el: '#login-app',
      data() {
        return {
          loginForm:{
            username: 'admin',
            password: '123456'
          },
          loading: false
        }
      },
      computed: {
        loginRules() {
          const validateUsername = (rule, value, callback) => {
            if (value.length < 1 ) {
              callback(new Error('请输入用户名'))
            } else {
              callback()
            }
          }
          const validatePassword = (rule, value, callback) => {
            if (value.length < 6) {
              callback(new Error('密码必须在6位以上'))
            } else {
              callback()
            }
          }
          return {
            'username': [{ 'validator': validateUsername, 'trigger': 'blur' }],
            'password': [{ 'validator': validatePassword, 'trigger': 'blur' }]
          }
        }
      },
      created() {
      },
      methods: {
        async handleLogin() {
          this.$refs.loginForm.validate(async (valid) => {
            if (valid) {
              this.loading = true
              let res = await loginApi(this.loginForm)
              if (String(res.code) === '1') {
                //“1”表示登录成功
                localStorage.setItem('userInfo',JSON.stringify(res.data))
                window.location.href= '/backend/index.html'
              } else {
                //表示登录失败
                this.$message.error(res.msg)
                this.loading = false
              }
            }
          })
        }
      }
    })
  </script>
</body>

</html>

新建包(controller、entity、mapper、service、serviceimpl)

controller —— EmployeeController

package com.li.ruiji.controller;

import com.li.ruiji.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

  @Autowired
  private EmployeeService employeeService;
}

entity —— Employee

package com.li.ruiji.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;

}

mapper —— EmployeeMapper

package com.li.ruiji.mapper;

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

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {

}

service —— EmployeeService

package com.li.ruiji.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.li.ruiji.entity.Employee;

public interface EmployeeService extends IService<Employee> {
}

serviceimpl —— EmployeeServiceImpl

package com.li.ruiji.service.impl;

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

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

导入返回结果类R

  • 服务端返回结果类
  • 此类是一个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端页面。
  • 新建common包,新建R.java类
package com.li.ruiji.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<T> add(String key, Object value) {
    this.map.put(key, value);
    return this;
  }

}

在Controller中创建登录方法

  • 处理逻辑如下:

    • 将页面提交的密码password进行md5加密处理
    • 根据页面提交的用户名username查询数据库
    • 如果没有查询到则返回登录失败结果
    • 密码比对,如果不一致则返回登录失败结果
    • 查看员工状态,如果为已禁用状态,则返回员工已禁用结果
    • 登录成功,将员工id存入Session并返回登录成功结果

EmployeeController

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

  @Autowired
  private EmployeeService employeeService;

  //编写登录方法
  //员工登录
  @PostMapping("/login")
  public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
//        处理逻辑如下:
//                * 将页面提交的密码password进行md5加密处理
    String password = employee.getPassword();
    password = DigestUtils.md5DigestAsHex(password.getBytes());

//                * 根据页面提交的用户名username查询数据库
    LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Employee::getUsername,employee.getUsername());
    Employee emp = employeeService.getOne(queryWrapper);

//                * 如果没有查询到则返回登录失败结果
    if (emp == null){
      return R.error("登录失败");
    }

//                * 密码比对,如果不一致则返回登录失败结果
    if (!emp.getPassword().equals(password)){
      return R.error("登录失败");
    }

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

//                * 登录成功,将员工id存入Session并返回登录成功结果
    request.getSession().setAttribute("employee",emp.getId());

    return R.success(emp);
  }
}

注意项

  • 如果报错java.sql.SQLFeatureNotSupportedException: null
  • 升级Druid版本,可以是1.1.18
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.18</version>
</dependency>
  • 如果登录页面一直在转圈,则将datasourcepassword加上双引号
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3307/ruiji?serverTimezone=UTC
    username: root
    #如果登录页面一直在转圈,则将`datasource`的`password`加上双引号
    password: "1234"
    type: com.alibaba.druid.pool.DruidDataSource

后台退出功能开发

  • 用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST。
  • 我们只需要在Controller中创建对应的处理方法即可。
  • 具体的处理逻辑:
    1、清理Session中的用户id

2、返回结果

EmployeeController

//员工退出
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
    //清理session员工id
    request.getSession().removeAttribute("employee");
    return R.success("退出成功");
}

完善登录功能

  • 实现步骤:

    • 创建自定义过滤器LoginCheckFilter
    • 在启动类上加入注解@ServletComponentScan
    • 完善过滤器的处理逻辑。
  • 过滤器具体的处理逻辑如下:

    • 获取本次请求的URI。
    • 判断本次请求是否需要处理。
    • 如果不需要处理,则直接放行。
    • 判断登录状态,如果已登录,则直接放行。
    • 如果未登录则返回未登录结果。

新建filter包和LoginCheckFilter类

  • 同时要在启动类RuijiApplication下新加注解@ServletComponentScan
  • 需要引入fastjson包。
  import com.alibaba.fastjson.JSON;
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>
package com.li.ruiji.filter;

import com.alibaba.fastjson.JSON;
import com.li.ruiji.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;

//检查用户是否已完成登录
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {

    //路径比较(匹配器),支持通配符
    //用于步骤一(获取本次请求的URI)
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();

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

//        * 获取本次请求的URI。
        String requestURI = request.getRequestURI();

        //定义不需要的处理的请求路径
        String[] urls =new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };

    //* 判断本次请求是否需要处理。
    boolean check = check(urls,requestURI);

    //* 如果不需要处理,则直接放行。
        if (check){
            filterChain.doFilter(request,response);
            return;
        }


    //* 判断登录状态,如果已登录,则直接放行。
        if (request.getSession().getAttribute("employee") !=null){
            filterChain.doFilter(request,response);
            return;
        }


    //* 如果未登录则返回未登录结果。
        //输出流向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;

    }

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


}

新增员工

  • 整个程序的执行过程:

    • 1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端。
    • 2、服务端Controller接收页面提交的数据并调用Service将数据进行保存。
    • 3、Service调用Mapper操作数据库,保存数据。
//新增员工
@PostMapping
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 empId = (Long) request.getSession().getAttribute("employee");

    employee.setCreateUser(empId);
    employee.setUpdateUser(empId);

    employeeService.save(employee);

    return R.success("新增员工成功");
}
  • 账号重复报异常。
  • 程序进行异常捕获,通常有两种处理方式:

    • 1、在Controller方法中加入try、catch进行异常捕获。
    • 2、使用异常处理器进行全局异常捕获。
  • 在common包下新建GlobalExceptionHandler
//全局异常处理
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    //异常处理方法
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());

        if (ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg = split[2]+"已存在";
            return R.error(msg);
        }


        //账号重复报异常
        return R.error("未知错误");
    }

}

员工信息分页查询

执行过程

  • 页面发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端。
  • 服务端Controller接收页面提交的数据并调用Service查询数据。
  • Service调用Mapper操作数据库,查询分页数据。
  • Controller将查询到的分页数据响应给页面。
  • 页面接收到分页数据并通过ElementUI的Table组件展示到页面上。

配置MP的分页插件

//配置MP的分页插件
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

员工信息分页查询

    //员工信息分页查询
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){

        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
//        添加过滤条件
        queryWrapper.like(StringUtils.isNotBlank(name),Employee::getName,name);
//        添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        
        //执行查询
        employeeService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

启用/禁用员工账号

执行过程

  • 页面发送ajax请求,将参数(id、status)提交到服务端。
  • 服务端Controller接收页面提交的数据并调用Service更新数据。
  • Service调用Mapper操作数据库。

代码开发(根据id修改员工信息)

  • 启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作。
  • 在Controller中创建update方法,此方法是一个通用的修改员工信息的方法。
//根据id修改员工信息
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
    Long empId = (Long)request.getSession().getAttribute("employee");
    employee.setUpdateTime(LocalDateTime.now());
    employee.setUpdateUser(empId);
    employeeService.updateById(employee);

    return R.success("员工信息修改成功!");

}

代码修复

  • 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换。
  • 在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行]ava对象到json数据的转换。

对象映射器

package com.li.ruiji.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]
 */
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)
                .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);
    }
}

扩展mvc框架的消息转换器(WebMvcConfig)

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射...");
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
    }

    /**
     * 扩展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);
    }
}

编辑员工信息

代码开发

  • 点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]。
  • 在add.html页面获取url中的参数[员工id]。
  • 发送ajax请求,请求服务端,同时提交员工id参数。
  • 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面。
  • 页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显。
  • 点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端。
  • 服务端接收员工信息,并进行处理,完成后给页面响应。
  • 页面接收到服务端响应信息后进行相应处理。
//根据id查询员工信息
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
    Employee employee = employeeService.getById(id);
    if (employee !=null){
        return R.success(employee);
    }
    return R.error("没有查询到对应的员工信息");
}

分类管理业务开发

公共字段自动填充

  • Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值。
  • 使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。

实现步骤

  • 在实体类的属性上加入@TableField注解,指定自动填充的策略。
  • 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口。

Employee实体类

    @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;

自定义元数据对象处理器(MyMetaObjectHandler)

//自定义元数据对象处理器
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    //插入操作,自动填充
    //公共字段自动填充
    public void insertFill(MetaObject metaObject) {
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser", new Long(1));
        metaObject.setValue("updateUser", new Long(1));

    }

    @Override
    //更新操作,自动填充
    //公共字段自动填充
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser", new Long(1));
    }
}
  • 原先的EmployeeController类下的新增员工模块的一段代码可以删除(注释掉)了。
    //新增员工
    @PostMapping
    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 empId = (Long) request.getSession().getAttribute("employee");
//
//        employee.setCreateUser(empId);
//        employee.setUpdateUser(empId);

        employeeService.save(employee);

        return R.success("新增员工成功");
    }
  • 原先的EmployeeController类下的根据id修改员工信息的一段代码可以删除(注释掉)了。
    //根据id修改员工信息
    @PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
//        Long empId = (Long)request.getSession().getAttribute("employee");
//        employee.setUpdateTime(LocalDateTime.now());
//        employee.setUpdateUser(empId);
        employeeService.updateById(employee);

        return R.success("员工信息修改成功!");

    }

公共字段自动填充 - 功能完善

什么是ThreadLocal?

  • ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  • ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

ThreadLocal常用方法:

  • public void set(T value)

    • 设置当前线程的线程局部变量的值
  • public T get()

    • 返回当前线程所对应的线程局部变量的值
  • 我们可以在LoginCheckFilterdoFilter方法中获取当前登录用户id,并调用ThreadLocalset方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandlerupdateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。

实现步骤:

  • 编写BaseContext工具类,基于ThreadLocal封装的工具类。
  • 在LogincheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id。
  • 在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id。

基于threadlocal封装工具类(BaseContext)

package com.li.ruiji.common;


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

//    设置值
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }

//    获取值
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

LoginCheckFilter调用BaseContext

//* 判断登录状态,如果已登录,则直接放行。
    if (request.getSession().getAttribute("employee") !=null){

        //LoginCheckFilter调用BaseContext
        Long empId = (Long) request.getSession().getAttribute("employee");
        BaseContext.setCurrentId(empId);
        //-----------------------------------------------------

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

自定义元数据对象处理器 - 修改(MyMetaObjectHandler)

//自定义元数据对象处理器
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    //插入操作,自动填充
    //公共字段自动填充
    public void insertFill(MetaObject metaObject) {
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        
        //自定义元数据对象处理器 - 修改
        metaObject.setValue("createUser", BaseContext.getCurrentId());
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
        //----------------------------------------------------------------

    }

    @Override
    //更新操作,自动填充
    //公共字段自动填充
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime", LocalDateTime.now());
        
        //自定义元数据对象处理器 - 修改
        metaObject.setValue("updateUser", BaseContext.getCurrentId());
        //----------------------------------------------------------------
    }
}

新增分类

类和接口基本结构

  • 实体类Category
  • Mapper接口CategoryMapper
  • 业务层接口CategoryService
  • 业务层实现类CategoryServicelmpl
  • 控制层CategoryController

实体类Category

package com.li.ruiji.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;


    //是否删除
    private Integer isDeleted;

}

Mapper接口CategoryMapper

package com.li.ruiji.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.li.ruiji.entity.Category;
import com.li.ruiji.entity.Employee;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CategoryMapper extends BaseMapper<Category> {

}

业务层接口CategoryService

package com.li.ruiji.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.li.ruiji.entity.Category;
import com.li.ruiji.entity.Employee;

public interface CategoryService extends IService<Category> {
}

业务层实现类CategoryServicelmpl

package com.li.ruiji.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.li.ruiji.entity.Category;
import com.li.ruiji.entity.Employee;
import com.li.ruiji.mapper.CategoryMapper;
import com.li.ruiji.mapper.EmployeeMapper;
import com.li.ruiji.service.CategoryService;
import com.li.ruiji.service.EmployeeService;
import org.springframework.stereotype.Service;

@Service
public class CategoryServicelmpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}

控制层CategoryController

//分类管理

@RestController
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;
}

“新增分类”功能(控制层CategoryController)

//分类管理

@RestController
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

//    新增分类
    @PostMapping
    public R<String> save(@RequestBody Category category){
        categoryService.save(category);
        return R.success("新增分类成功");
    }
}

分类信息分页查询

程序执行过程

  • 页面发送ajax请求,将分页查询参数(page、pageSize)提交到服务端
  • 服务端Controller接收页面提交的数据并调用Service查询数据
  • Service调用Mapper操作数据库,查询分页数据
  • Controller将查询到的分页数据响应给页面
  • 页面接收到分页数据并通过ElementUl的Table组件展示到页面上

分页查询(CategoryController)

//    分页查询
    @GetMapping("/page")
    public R<Page> page(int page, int pageSize){
        //分页构造器
        Page<Category> pageInfo = new Page<>(page,pageSize);

        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();

        //添加排序条件,根据sort进行排序
        queryWrapper.orderByAsc(Category::getSort);

        //进行分页查询
        categoryService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }
  • 注:如果报Unknown column 'is_deleted' in 'field list' 或者500的需要把Category实体类的逻辑删除那个字段注释。
/**
 * 分类
 */
@Data
public class Category implements Serializable {
    .....................................
    
//    //是否删除
//    private Integer isDeleted;
    
}

删除分类

程序执行过程

  • 页面发送ajax请求,将参数(id)提交到服务端。
  • 服务端Controller接收页面提交的数据并调用Service删除数据。
  • Service调用Mapper操作数据库。

根据id删除分类(CategoryController)

//    根据id删除分类
    @DeleteMapping
    public R<String> delete(Long ids){
        categoryService.removeById(ids);
        return R.success("分类信息删除成功");
    }

修改或原封不动category.js

  • 注意:这里的是id要写为ids。因为list.html中调用的category.js的api里的删除当前列的接口代码是写的ids
  • 也可以修改category.js的ids为id,那么在CategoryController中需要传的值就是(Long id)
// 删除当前列的接口
const deleCategory = (ids) => {
  return $axios({
    url: '/category',
    method: 'delete',
    params: { ids }
  })
}

创建-类和接口基本结构

  • 实体类Dish和Setmeal
  • Mapper接口DishMapper和SetmealMapper
  • Service接口DishService和SetmealService
  • Service实现类DishServicelmpl和SetmealServicelmpl

实体类Dish

package com.li.ruiji.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;


    //是否删除
    private Integer isDeleted;

}

实体类Setmeal

package com.li.ruiji.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;


    //是否删除
    private Integer isDeleted;
}

Mapper接口DishMapper

@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}

Mapper接口SetmealMapper

@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal>{
}

Service接口DishService

public interface DishService extends IService<Dish> {
}

Service接口SetmealService

public interface SetmealService extends IService<Setmeal> {
}

Service实现类DishServicelmpl

@Service
public class DishServicelmpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}

Service实现类SetmealServicelmpl

@Service
public class SetmealServicelmpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
}

修改分类(CategoryService)

public interface CategoryService extends IService<Category> {
    public void remove(Long id);
}

CategoryServicelmpl

@Service
public class CategoryServicelmpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;

    @Autowired
    private SetmealService setmealService;


    //根据id删除分类,删除之前需要进行判断
    @Override
    public void remove(Long id){

        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类id进行查询
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        int count1 = dishService.count(dishLambdaQueryWrapper);

        //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
        if (count1>0){
            //已经关联菜品,抛出一个业务异常
            throw new CustomException("当前分类下关联了菜品,不能删除!");
        }

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

        //正常删除分类
        super.removeById(id);

    }

}

在common包下新建自定义业务异常类(CustomException)

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

新增异常处理方法(GlobalExceptionHandler)

//全局异常处理
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    ------------------------------------------

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

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

修改CategoryController根据id删除分类的removeById方法

  • categoryService.removeById(id); →→→→→ categoryService.remove(id);
//    根据id删除分类
    @DeleteMapping
    public R<String> delete(Long id){
//        categoryService.removeById(id);
        categoryService.remove(id);
        return R.success("分类信息删除成功");
    }

修改分类(CategoryController)

//    根据id删除分类信息
@PutMapping
public R<String> update(@RequestBody Category category){
    categoryService.updateById(category);
    return R.success("修改分类信息成功!");
}

菜品管理业务开发

  • 注:批量删除、批量启售、批量停售,这三个功能需自己实现。

文件上传下载

文件上传

  • 服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件

    • commons-fileupload
    • commons-io
  • Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件。

文件下载

  • 通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

文件上传代码实现

  • backend\page下新建demo文件夹,并新建upload.html

upload.html

  • 默认给的会被拦截器拦截,访问不到页面。
  • 加上这个:
<link rel="shortcut icon" href="../../favicon.ico">
<!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" />
    
<!--    默认给的会被拦截器拦截,访问不到页面。加上这个-->
    <link rel="shortcut icon" href="../../favicon.ico">
    <!--   ----------------------------------------------------->
    
</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>

CommonController

  • 在Controller包中新建CommonController类。
修改LoginCheckFilter部分内容
  • 这样测试的话就不用去登录了。
//定义不需要的处理的请求路径
String[] urls =new String[]{
        "/employee/login",
        "/employee/logout",
        "/backend/**",
        "/front/**",
    //修改LoginCheckFilter部分内容
        "/common/**"
    //-----------------------------
};
application.yml
  • 在application.yml中添加图片临时保存路径(D:testImage)
ruiji:
  path: D:\testImage\
文件上传代码实现(CommonController)(完整)
//文件上传和下载
@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${ruiji.path}")
    private String basePath;

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

        //原始文件名
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

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

        //创建一个目录对象
        File dir = new File(basePath);
        //判断当前目录是否存在
        if (!dir.exists()){
//            目录不存在,需要创建
            dir.mkdirs();
        }

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

            return R.success(fileName);
    }
}

文件下载代码实现(CommonController)(完整)

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

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

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

        response.setContentType("image/jpeg");

        int len =0;
        byte[] bytes = new byte[1024];
        while( (len = fileInputStream.read(bytes)) !=-1 ){
            outputStream.write(bytes,0, len) ;
            outputStream.flush();

        }

        //关闭资源
        outputStream.close();
        fileInputStream.close();

    } catch (Exception e) {
        e.printStackTrace();
    }
}

新增菜品

  • 新增菜品,其实就是将新增页面录入的菜品信息插入到dish表。
  • 如果添加了口味做法,还需要向dish_flavor表插入数据。
  • 所以在新增菜品时,涉及到两个表:

    • dish —— 菜品表
    • dish_flavor —— 菜品口味表

类和接口基本结构

  • 实体类DishFlavor
  • Mapper接口DishFlavorMapper
  • 业务层接口DishFlavorService
  • 业务层实现类DishFlavorServicelmpl
  • 控制层DishController

实体类DishFlavor

package com.li.ruiji.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;


    //是否删除
    private Integer isDeleted;

}

Mapper接口DishFlavorMapper

@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor>{
}

业务层接口DishFlavorService

public interface DishFlavorService extends IService<DishFlavor>{
}

业务层实现类DishFlavorServicelmpl

@Service
public class DishFlavorServicelmpl extends ServiceImpl<DishFlavorMapper, DishFlavor> implements DishFlavorService {
}

控制层DishController

//菜品管理
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;
}

新增菜品时前端页面和服务端的交互过程

(开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可)

  • 页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中。
  • 页面发送请求进行图片上传,请求服务端将图片保存到服务器。
  • 页面发送请求进行图片下载,将上传的图片进行回显。
  • 点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端。

菜品分类 —— 根据条件查询分类数据(CategoryController)

//菜品分类 —— 根据条件查询分类数据
@GetMapping("/list")
public R<List<Category>> list(Category category){
    //条件构造器
    LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();

    //添加条件
    queryWrapper.eq(category.getType() !=null,Category::getType,category.getType());

    //添加排序条件
    queryWrapper.orderByAsc(Category::getSort).orderByAsc(Category::getUpdateTime);

    List<Category> list = categoryService.list(queryWrapper);
    return R.success(list);
}

导入DTO

  • 新建DTO包和DishDto类
@Data
public class DishDto extends Dish {

    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

新增菜品(DishController)

//新增菜品
@PostMapping
public R<String> save(@RequestBody DishDto dishDto){
    dishService.saveWithFlavor(dishDto);
    return R.success("新增菜品成功!");
}

DishService(接口)

public interface DishService extends IService<Dish> {

    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表: dish、dish_flavor
    public void saveWithFlavor(DishDto dishDto);
    
}

实现DishService接口方法(DishServicelmpl)

@Service
public class DishServicelmpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    @Autowired
    private DishFlavorService dishFlavorService;

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

        Long dishId = dishDto.getId();//菜品ID

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

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

为支持@Transactional需在启动类中添加@EnableTransactionManagement注解

  • 开启事物注解
@Slf4j
@SpringBootApplication
@ServletComponentScan
//新增//新增//新增//新增//新增//新增
@EnableTransactionManagement
//-------------------------------------
public class RuijiApplication {

    public static void main(String[] args) {
        SpringApplication.run(RuijiApplication.class, args);
        log.info("项目启动成功");
    }

}

菜品信息分页查询

代码开发 —— 交互过程

  • 开发菜品信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可:

    • 页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据。
    • 页面发送请求,请求服务端进行图片下载,用于页面图片展示。

菜品信息分页查询(DishController)

  • 能查询到数据,但页面效果不佳,需改进。
//菜品信息分页查询
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){

    //分页构造器
    Page<Dish> pageInfo = new Page<>(page,pageSize);

    //条件构造器
    LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();

    //添加过滤条件
    queryWrapper.like(name !=null,Dish::getName,name);

    //添加排序条件
    queryWrapper.orderByAsc(Dish::getUpdateTime);

    //执行分页查询
    dishService.page(pageInfo,queryWrapper);

    return R.success(pageInfo);
}

代码改造

//菜品管理
@RestController
@RequestMapping("/dish")
public class DishController {

    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;

    //-----------------新增----------------------
    @Autowired
    private CategoryService categoryService;
    //---------------------------------------

    //新增菜品
    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功!");
    }

    //菜品信息分页查询
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){

        //分页构造器
        Page<Dish> pageInfo = new Page<>(page,pageSize);

        
        //-----------------新增----------------------
        Page<DishDto> dishDtoPage = new Page<>();
         //---------------------------------------


        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();

        //添加过滤条件
        queryWrapper.like(name !=null,Dish::getName,name);

        //添加排序条件
        queryWrapper.orderByAsc(Dish::getUpdateTime);

        //执行分页查询
        dishService.page(pageInfo,queryWrapper);

        //-----------------新增----------------------
//      对象拷贝
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");

        List<Dish> records = pageInfo.getRecords();
        List<DishDto> list = records.stream().map((item) ->{
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();    //分类id

            //根据ID查询分类对象
            Category category = categoryService.getById(categoryId);
            
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName) ;
            }
            return dishDto;
            
        }).collect(Collectors.toList());

        dishDtoPage.setRecords(list);

        return R.success(dishDtoPage);
         //---------------------------------------
    }
}

修改菜品

代码交互过程

  • 开发修改菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可:

    • 页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示。
    • 页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显。
    • 页面发送请求,请求服务端进行图片下载,用于页图片回显。
    • 点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端。

根据id查询菜品信息和对应的口味信息(DishController)

//根据id查询菜品信息和对应的口味信息
    @GetMapping("/{id}")
    public R<DishDto> get(@PathVariable Long id){

        DishDto dishDto = dishService.getByIdWithFlavor(id);

        return R.success(dishDto);
    }

根据id查询菜品信息和对应的口味信息(DishService)

//根据id查询菜品信息和对应的口味信息
public DishDto getByIdWithFlavor(Long id);

根据id查询菜品信息和对应的口味信息(DishServicelmpl)

//根据id查询菜品信息和对应的口味信息
public DishDto getByIdWithFlavor(Long id){

    //查询菜品基本信息,从dish表查讪
    Dish dish = this.getById(id);

    DishDto dishDto = new DishDto();
    BeanUtils.copyProperties(dish,dishDto);

    //查询当前菜品对应的口味信息,从dish_flavor表查询
    LambdaQueryWrapper<DishFlavor> queryWrapper =new LambdaQueryWrapper<>();
    queryWrapper.eq(DishFlavor::getDishId,dish.getId());
    List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
    dishDto.setFlavors(flavors);


    return dishDto;
}

修改菜品(DishController)

//修改菜品
@PutMapping
public R<String> update(@RequestBody DishDto dishDto){
    dishService.updateWithFlavor(dishDto);
    return R.success("修改菜品成功!");
}

修改菜品(DishService)

//更新菜品信息,同时更新对应的口味信息
public void updateWithFlavor(DishDto dishDto);

修改菜品(DishServicelmpl)

//更新菜品信息,同时更新对应的口味信息
@Override
public void updateWithFlavor(DishDto 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();

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

    dishFlavorService.saveBatch(flavors);

}

套餐管理

新增套餐

数据模型

  • 新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表
  • 还需要向setmeal_dish表插入套餐和菜品关联数据所以在新增套餐时,涉及到两个表:

    • setmeal —— 套餐表
    • setmeal_dish —— 套餐菜品关系表

类和接口基本结构

  • 实体类SetmealDish
  • DTO SetmealDto
  • Mapper接口SetmealDishMapper
  • 业务层接口SetmealDishService
  • 业务层实现类SetmealDishServicelmpl
  • 控制层SetmealController

实体类SetmealDish

package com.li.ruiji.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;


    //是否删除
    private Integer isDeleted;
}

DTO (SetmealDto

package com.li.ruiji.dto;

import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.entity.SetmealDish;
import lombok.Data;
import java.util.List;

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish> setmealDishes;

    private String categoryName;
}

Mapper接口SetmealDishMapper

@Mapper
public interface SetmealDishMapper  extends BaseMapper<SetmealDish>{
}

业务层接口SetmealDishService

public interface SetmealDishService extends IService<SetmealDish>{
}

业务层实现类SetmealDishServicelmpl

@Service
public class SetmealDishServicelmpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService{

}

控制层SetmealController

//套餐管理
@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishService setmealDishService;
}

代码交互过程

  • 开开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可:

    • 页面(backend/page/combo/add.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中。
    • 页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中。
    • 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中。
    • 页面发送请求进行图片上传,请求服务端将图片保存到服务器。
    • 页面发送请求进行图片下载,将上传的图片进行回显。
    • 点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端。

根据条件查询对应的菜品数据(DishController)

  • 注解不能忘
//根据条件查询对应的菜品数据
    @GetMapping("/list")
    public R<List<Dish>> list(Dish dish){
        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

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

        return R.success(list);
    }

新增套餐业务功能开发(SetmealController)

  • 注: @Autowired部分的字段是之前已存在的。

    • 新增套餐注释下的才为新增的代码部分。
//套餐管理
@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishService setmealDishService;

    //    新增套餐
    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐成功");
    }

}

新增套餐,同时需要保存套餐和菜品的关联关系(SetmealService)(SetmealServicelmpl)

public interface SetmealService extends IService<Setmeal> {

    //新增套餐,同时需要保存套餐和菜品的关联关系
    public void saveWithDish(SetmealDto setmealDto);
}
@Service
public class SetmealServicelmpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {

    @Autowired
    private SetmealDishService setmealDishService;

    //新增套餐,同时需要保存套餐和菜品的关联关系
    @Transactional
    public void saveWithDish(SetmealDto setmealDto){
        //保存套餐的基本信息,操作setmeal,执行insert操作
        this.save(setmealDto);
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item) ->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());

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

    }
}

套餐信息分页查询

代码交互过程

  • 页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据。
  • 页面发送请求,请求服务端进行图片下载,用于页面图片展示。

SetmealController

  • 注:需添加一个新的@Autowired
  • private CategoryService categoryService;
//套餐管理
@RestController
@RequestMapping("/setmeal")
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private SetmealDishService setmealDishService;
    
    
    //------------------------------新增
    @Autowired
    private CategoryService categoryService;
    //------------------------------新增


    //    新增套餐
    @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐成功");
    }

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

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();

        //添加查询对象,根据name进行like模糊查询
        queryWrapper.like(name !=null,Setmeal::getName,name);

        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Setmeal::getUpdateTime) ;

        setmealService.page(pageInfo, queryWrapper);

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

        List<SetmealDto> list = records.stream().map((item) ->{
            SetmealDto setmealDto = new SetmealDto();
            //对象拷贝
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            Long categoryId = item.getCategoryId();
            //根据分类id查询分类对象
            Category category = categoryService.getById(categoryId);
            if (category != null){
                //分类名称
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());

        dtoPage.setRecords(list);

        return R.success(dtoPage);
    }
     //------------------------------新增-----------------------------------------------------------------------------------------
}

删除套餐

代码交互过程

删除套餐,同时需要删除套餐和菜品的关联数据(SetmealService)(SetmealServicelmpl)

public interface SetmealService extends IService<Setmeal> {

    //新增套餐,同时需要保存套餐和菜品的关联关系
    public void saveWithDish(SetmealDto setmealDto);

    //------------------------------新增-----------------------------------------------------------------------------------------
    //删除套餐,同时需要删除套餐和菜品的关联数据
    public void removeWithDish(List<Long> ids);
    //------------------------------新增-----------------------------------------------------------------------------------------

}
//删除套餐,同时需要删除套餐和菜品的关联数据
@Transactional
public void removeWithDish(List<Long> ids){
    //查询套餐状态,确定是否可用删除
    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);

    LambdaQueryWrapper<SetmealDish> LambdaQueryWrapper = new LambdaQueryWrapper<>();
    LambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);

    //删除关系表中的数据 _ setmeal_dish
    setmealDishService.remove(LambdaQueryWrapper);

}

删除套餐(SetmealController)

    //删除套餐
    @DeleteMapping
    public R<String> delete(@RequestParam List<Long> ids){
        setmealService.removeWithDish(ids);
        return R.success("删除套餐成功");
    }

管理后台总结

  • 员工管理 —— 完美。
  • 分类管理 —— 完美。
  • 菜品管理 —— 批量删除 批量启售 批量停售未做,停售和删除未做。
  • 套餐管理 —— 批量删除 批量启售 批量停售未做,停售和删除未做。
  • 订单明细 —— 未做。

手机验证码登录

  • 了解即可,可以用邮箱或者腾讯云替代。
  • 要钱,而且个人用户申请不了,后期会在手机验证码登录功能开发中会直接传一个模拟验证码进行验证登录。

短信发送

阿里云短信服务

代码开发

具体开发步骤

  • 导入maven坐标
  • 调用API

阿里云短信服务SDK

导入maven坐标

<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>alibabacloud-dysmsapi20170525</artifactId>
  <version>1.0.1</version>
</dependency>

调用API

// This file is auto-generated, don't edit it. Thanks.
package demo;

import com.aliyun.auth.credentials.Credential;
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
import com.aliyun.core.http.HttpClient;
import com.aliyun.core.http.HttpMethod;
import com.aliyun.core.http.ProxyOptions;
import com.aliyun.httpcomponent.httpclient.ApacheAsyncHttpClientBuilder;
import com.aliyun.sdk.service.dysmsapi20170525.models.*;
import com.aliyun.sdk.service.dysmsapi20170525.*;
import com.google.gson.Gson;
import darabonba.core.RequestConfiguration;
import darabonba.core.client.ClientOverrideConfiguration;
import darabonba.core.utils.CommonUtil;
import darabonba.core.TeaPair;

//import javax.net.ssl.KeyManager;
//import javax.net.ssl.X509TrustManager;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.CompletableFuture;

public class SendSms {
    public static void main(String[] args) throws Exception {

        // HttpClient Configuration
        /*HttpClient httpClient = new ApacheAsyncHttpClientBuilder()
                .connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds
                .responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds
                .maxConnections(128) // Set the connection pool size
                .maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds
                // Configure the proxy
                .proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001))
                        .setCredentials("<your-proxy-username>", "<your-proxy-password>"))
                // If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true))
                .x509TrustManagers(new X509TrustManager[]{})
                .keyManagers(new KeyManager[]{})
                .ignoreSSL(false)
                .build();*/

        // Configure Credentials authentication information, including ak, secret, token
        StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
                .accessKeyId("<your-accessKeyId>")
                .accessKeySecret("<your-accessKeySecret>")
                //.securityToken("<your-token>") // use STS token
                .build());

        // Configure the Client
        AsyncClient client = AsyncClient.builder()
                .region("cn-hangzhou") // Region ID
                //.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient)
                .credentialsProvider(provider)
                //.serviceConfiguration(Configuration.create()) // Service-level configuration
                // Client-level configuration rewrite, can set Endpoint, Http request parameters, etc.
                .overrideConfiguration(
                        ClientOverrideConfiguration.create()
                                .setEndpointOverride("dysmsapi.aliyuncs.com")
                        //.setReadTimeout(Duration.ofSeconds(30))
                )
                .build();

        // Parameter settings for API request
        SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
                .signName("阿里云短信测试")
                .templateCode("SMS_154950909")
                .phoneNumbers("13502544380")
                .templateParam("{\"code\":\"1234\"}")
                // Request-level configuration rewrite, can set Http request parameters, etc.
                // .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
                .build();

        // Asynchronously get the return value of the API request
        CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
        // Synchronously get the return value of the API request
        SendSmsResponse resp = response.get();
        System.out.println(new Gson().toJson(resp));
        // Asynchronous processing of return values
        /*response.thenAccept(resp -> {
            System.out.println(new Gson().toJson(resp));
        }).exceptionally(throwable -> { // Handling exceptions
            System.out.println(throwable.getMessage());
            return null;
        });*/

        // Finally, close the client
        client.close();
    }

}

手机验证码登录

代码交互过程

  • 在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信。
  • 在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求。

类和接口基本结构

  • 实体类User
  • Mapper接口UserMapper
  • 业务层接口UserService
  • 业务层实现类UserServicelmpl
  • 控制层Usercontroller
  • 工具类SMSUtilsValidateCodeutils

实体类User

package com.li.ruiji.entity;

import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
/**
 * 用户信息
 */
@Data
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //姓名
    private String name;


    //手机号
    private String phone;


    //性别 0 女 1 男
    private String sex;


    //身份证号
    private String idNumber;


    //头像
    private String avatar;


    //状态 0:禁用,1:正常
    private Integer status;
}

Mapper接口UserMapper

@Mapper
public interface UserMapper  extends BaseMapper<User>{
}

业务层接口UserService

public interface UserService extends IService<User>{
}

业务层实现类UserServicelmpl

@Service
public class UserServicelmpl extends ServiceImpl<UserMapper, User> implements UserService {
}

控制层Usercontroller

@RestController
@RequestMapping("/user")
public class Usercontroller {
    
    @Autowired
    private UserService userService;
}

新建utils包

  • 因为黑马给的短信发送工具类是原版api,所以导入的依赖也是旧版的。
  • 这样他才不会报错。

maven

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.5.16</version>
</dependency>

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
    <version>1.1.0</version>
</dependency>

短信发送工具类SMSUtils

package com.li.ruiji.utils;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;

/**
 * 短信发送工具类
 */
public class SMSUtils {

   /**
    * 发送短信
    * @param signName 签名
    * @param templateCode 模板
    * @param phoneNumbers 手机号
    * @param param 参数
    */
   public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
      DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
      IAcsClient client = new DefaultAcsClient(profile);

      SendSmsRequest request = new SendSmsRequest();
      request.setSysRegionId("cn-hangzhou");
      request.setPhoneNumbers(phoneNumbers);
      request.setSignName(signName);
      request.setTemplateCode(templateCode);
      request.setTemplateParam("{\"code\":\""+param+"\"}");
      try {
         SendSmsResponse response = client.getAcsResponse(request);
         System.out.println("短信发送成功");
      }catch (ClientException e) {
         e.printStackTrace();
      }
   }

}

随机生成验证码工具类ValidateCodeutils

package com.li.ruiji.utils;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

代码开发

修改LogincheckFilter

  • LoginCheckFilter过滤器用于检查用户的登录状态。
  • 我们在进行手机验证码登录时,发送的请求需要在此过滤器处理时直接放行。
//定义不需要的处理的请求路径
String[] urls =new String[]{
        "/employee/login",
        "/employee/logout",
        "/backend/**",
        "/front/**",
        "/common/**",
    //------------------------------新增-----------------------------------------------------------------------------------------
        "/user/sendMsg",//移动端发送短信
        "/user/login" //移动端登录
   //------------------------------新增-----------------------------------------------------------------------------------------
};
  • 在LoginCheckFilter过滤器中扩展逻辑,判断移动端用户登录状态:
//前台 —— 判断登录状态,如果已登录,则直接放行。
if (request.getSession().getAttribute("user") !=null){

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

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

之前的前端代码(front)需要修改

  • 之前的前端代码(front)有点问题,建议直接复制粘贴day5的front文件夹代码来进行替换。

发送手机短信验证码(Usercontroller)

  • 需要将生成的验证码保存到Session:

    • HttpSession session不能忘咯。
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 发送手机短信验证码
     * @param user
     * @return
     */
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session){
        //获取手机号
        String phone = user.getPhone();
        
        //StringUtils导的包是org.apache.commons.lang3.StringUtils;
        if(StringUtils.isNotEmpty(phone)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}",code);

            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("瑞吉外卖","",phone,code);

            //需要将生成的验证码保存到Session
            session.setAttribute(phone,code);

            return R.success("手机验证码短信发送成功");
        }

        return R.error("手机验证码短信发送失败");
    }

}
  • 因为这里的StringUtils使用到了org.apache.commons.lang3.StringUtils;
  • 所以需要导入commons-lang3的依赖。
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.7</version>
</dependency>

移动端用户登录

/**
 * 移动端用户登录
 * @param map
 * @param session
 * @return
 */
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
    log.info(map.toString());

    //获取手机号
    String phone = map.get("phone").toString();

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

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

    //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
    if(codeInSession != null && codeInSession.equals(code)){
        //如果能够比对成功,说明登录成功

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getPhone,phone);

        User user = userService.getOne(queryWrapper);
        if(user == null){
            //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
            user = new User();
            user.setPhone(phone);
            user.setStatus(1);
            userService.save(user);
        }
        session.setAttribute("user",user.getId());
        return R.success(user);
    }
    return R.error("登录失败");
}

菜品展示

用户地址簿相关功能代码

数据模型

  • 用户的地址信息会存储在address_book表,即地址簿表中。

类和接口基本结构

  • 实体类AddressBook
  • Mapper接口AddressBookMapper
  • 业务层接口AddressBookService
  • 业务层实现类AddressBookServicelmpl
  • 控制层AddressBookController

实体类AddressBook

package com.li.ruiji.controller;

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 AddressBook implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //用户id
    private Long userId;


    //收货人
    private String consignee;


    //手机号
    private String phone;


    //性别 0 女 1 男
    private String sex;


    //省级区划编号
    private String provinceCode;


    //省级名称
    private String provinceName;


    //市级区划编号
    private String cityCode;


    //市级名称
    private String cityName;


    //区级区划编号
    private String districtCode;


    //区级名称
    private String districtName;


    //详细地址
    private String detail;


    //标签
    private String label;

    //是否默认 0 否 1是
    private Integer isDefault;

    //创建时间
    @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;


    //是否删除
    private Integer isDeleted;
}

Mapper接口AddressBookMapper

@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook>{
}

业务层接口AddressBookService

public interface AddressBookService extends IService<AddressBook>{
}

业务层实现类AddressBookServicelmpl

@Service
public class AddressBookServicelmpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {
}

控制层AddressBookController(地址簿管理)(这个代码细节要重视)

  • 这里控制层AddressBookController(地址簿管理)导致了后期下单模块出错。
  • 注释的是原代码,应该是==的,结果写成!=,逻辑错误,这样他就查询不到默认地址了。
//        if (null != addressBook) {
////            return R.error("没有找到该对象");
////        } else {
////            return R.success(addressBook);
////        }

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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.li.ruiji.common.BaseContext;
import com.li.ruiji.common.R;
import com.li.ruiji.entity.AddressBook;
import com.li.ruiji.service.AddressBookService;
import com.li.ruiji.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

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

    @Autowired
    private AddressBookService addressBookService;

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

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

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

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

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

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

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

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

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

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

菜品展示

代码交互过程

  • 页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)。
  • 页面发送ajax请求,获取第一个分类下的菜品或者套餐。

代码开发

暂时修改一下从静态json文件获取数据(front/api/main.js)

  • 注意:首页加载完成后,还发送了一次ajax请求用于加载购物车数据,此处可以将这次请求的地址暂时修改一下从静态json文件获取数据,等后续开发购物车功能时再修改回来。
//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
        // 'url': '/shoppingCart/list',
        
        //假数据
        'url': '/front/cartData.json',

        'method': 'get',
        params:{...data}
    })
}
  • 请求cartData.json(假数据),cartData.json的代码示例:
{
  "code": 1,
  "msg": null,
  "data": [],
  "map": {}
}

DishDto添加注释

  • //菜品对应的口味数据
@Data
public class DishDto extends Dish {

    //菜品对应的口味数据
    private List<DishFlavor> flavors = new ArrayList<>();

    private String categoryName;

    private Integer copies;
}

修改DishController中的list方法(根据条件查询对应的菜品数据)

  • 注释原有方法,改R<List<Dish>>R<List<DishDto>>
  • 新增一行代码:List<DishDto> dishDtoList = null;
  • 返回list的改为dishDtoListreturn R.success(dishDtoList);
  • 复制前段对象拷贝部分代码,进行改造:
//复制该段对象拷贝部分代码,并复制到“根据条件查询对应的菜品数据方法”下进行改造
List<Dish> records = pageInfo.getRecords();
List<DishDto> list = records.stream().map((item) ->{
    DishDto dishDto = new DishDto();

    BeanUtils.copyProperties(item,dishDto);

    Long categoryId = item.getCategoryId();    //分类id

    //根据ID查询分类对象
    Category category = categoryService.getById(categoryId);

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

}).collect(Collectors.toList());
  • return dishDto;前添加 ”当前菜品id “ —— Long dishId = item.getId();
//当前菜品id
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);

//SQL:select * from dish_flavor where dish_id = ?
List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);

dishDto.setFlavors(dishFlavorList);

return dishDto;

修改DishController中的list方法(根据条件查询对应的菜品数据)(2)

  • 注释掉的为原有list方法。
  • 上述步骤做完的代码如下所示:
//    //根据条件查询对应的菜品数据
//    @GetMapping("/list")
//    public R<List<Dish>> list(Dish dish){
//        //构造查询条件
//        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//        queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());
//
//        //添加条件,查询状态为1(起售状态)的菜品
//        queryWrapper.eq(Dish::getStatus,1);
//
//        //添加排序条件
//        queryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);
//
//        List<Dish> list = dishService.list(queryWrapper);
//
//        return R.success(list);
//    }

    //根据条件查询对应的菜品数据
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());

        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);

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

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

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();    //分类id

            //根据ID查询分类对象
            Category category = categoryService.getById(categoryId);

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

            //当前菜品id
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);

            //SQL:select * from dish_flavor where dish_id = ?
            List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);

            dishDto.setFlavors(dishFlavorList);

            return dishDto;

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


        return R.success(dishDtoList);
    }

创建SetmealController的list方法

//根据条件查询套餐数据
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(queryWrapper);
    return R.success(list);
}

购物车

数据模型

  • 用户的地址信息会存储在shopping_card表,即地址簿表中。

代码交互过程

类和接口基本结构

  • 实体类Shoppingcart
  • Mapper接口ShoppingCartMapper
  • 业务层接口ShoppingCartService
  • 业务层实现类ShoppingCartServicelmpl
  • 控制层ShoppingCartController

实体类Shoppingcart

package com.li.ruiji.entity;

import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 购物车
 */
@Data
public class ShoppingCart implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //名称
    private String name;

    //用户id
    private Long userId;

    //菜品id
    private Long dishId;

    //套餐id
    private Long setmealId;

    //口味
    private String dishFlavor;

    //数量
    private Integer number;

    //金额
    private BigDecimal amount;

    //图片
    private String image;

    private LocalDateTime createTime;
}

Mapper接口ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart>{
    
}

业务层接口ShoppingCartService

public interface ShoppingCartService extends IService<ShoppingCart>{
}

业务层实现类ShoppingCartServicelmpl

@Service
public class ShoppingCartServicelmpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
}

控制层ShoppingCartController

//购物车
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;
}

代码开发

添加购物车(ShoppingCartController)

//添加购物车
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){

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

        Long dishId = shoppingCart.getDishId();

    LambdaQueryWrapper<ShoppingCart> queryWrapper =new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,currentId);

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


    //查询当前菜品或者套餐是否在购物车中
    //SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
    ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);

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

    return R.success(cartServiceOne);
}

修改请求地址(front/api/main.js)

  • 在”菜品展示“中为了临时调试,修改了请求的地址,从静态json文件获取数据,现在开发购物车功能修改回来。
  • 现在的请求地址:'url': '/shoppingCart/list'
//获取购物车内商品的集合
function cartListApi(data) {
    return $axios({
        'url': '/shoppingCart/list',
        // 'url': '/front/cartData.json',

        'method': 'get',
        params:{...data}
    })
}

查看购物车(ShoppingCartController)

//查看购物车
@GetMapping("/list")
public R<List<ShoppingCart>> list(){
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    queryWrapper.orderByAsc(ShoppingCart::getCreateTime);

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

    return R.success(list);
}

清空购物车(ShoppingCartController)

//清空购物车
@DeleteMapping("/clean")
public R<String> clean(){
    //SQL : delete from shopping_cart where user_id = ?
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    shoppingCartService.remove(queryWrapper);

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

用户下单

数据模型

  • 用户下单业务对应的数据表为orders表order_detail表

    • orders —— 订单表
    • order_detail —— 订单明细表

代码交互过程

类和接口基本结构

  • 实体类OrdersOrderDetail
  • Mapper接口OrderMapperOrderDetailMapper
  • 业务层接口OrderServiceOrderDetailService
  • 业务层实现类OrderServicelmplOrderDetailServicelmpl
  • 控制层OrderControllerOrderDetailController

实体类OrdersOrderDetail

package com.li.ruiji.entity;

import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单
 */
@Data
public class Orders implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //订单号
    private String number;

    //订单状态 1待付款,2待派送,3已派送,4已完成,5已取消
    private Integer status;


    //下单用户id
    private Long userId;

    //地址id
    private Long addressBookId;


    //下单时间
    private LocalDateTime orderTime;


    //结账时间
    private LocalDateTime checkoutTime;


    //支付方式 1微信,2支付宝
    private Integer payMethod;


    //实收金额
    private BigDecimal amount;

    //备注
    private String remark;

    //用户名
    private String userName;

    //手机号
    private String phone;

    //地址
    private String address;

    //收货人
    private String consignee;
}
package com.li.ruiji.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 订单明细
 */
@Data
public class OrderDetail implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    //名称
    private String name;

    //订单id
    private Long orderId;


    //菜品id
    private Long dishId;


    //套餐id
    private Long setmealId;


    //口味
    private String dishFlavor;


    //数量
    private Integer number;

    //金额
    private BigDecimal amount;

    //图片
    private String image;
}

Mapper接口OrderMapperOrderDetailMapper

@Mapper
public interface OrderMapper extends BaseMapper<Orders>{
}
@Mapper
public interface OrderDetailMapper extends BaseMapper<OrderDetail>{
}

业务层接口OrderServiceOrderDetailService

public interface OrderService extends IService<Orders>{
}
public interface OrderDetailService extends IService<OrderDetail> {
}

业务层实现类OrderServicelmplOrderDetailServicelmpl

@Service
public class OrderServicelmpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
}
@Service
public class OrderDetailServicelmpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService{
}

控制层OrderControllerOrderDetailController

//订单(用户下单)
@RestController
@RequestMapping("/order")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
}
//订单明细(用户下单)
@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {

    @Autowired
    private OrderDetailService orderDetailService;
}

代码开发

用户下单(OrderController)

//订单(用户下单)
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    //用户下单
    @PostMapping("/submit")
    public R<String> submit(@RequestBody Orders orders){

        orderService.submit(orders);
        return R.success("下单成功");
    }
}

在OrderService中扩展方法

public interface OrderService extends IService<Orders>{

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

}

在OrderServicelmpl中实现这个“用户下单”这个方法

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

    @Autowired
    private  ShoppingCartService shoppingCartService;

    @Autowired
    private UserService userService;

    @Autowired
    private AddressBookService addressBookService;

    @Autowired
    private OrderDetailService orderDetailService;

    //用户下单
    @Transactional
    public void submit(Orders orders) {
        //获得当前用户id
        Long userId = BaseContext.getCurrentId();

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

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

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

        //查询地址数据
        Long addressBookId = orders.getAddressBookId();
        AddressBook addressBook =addressBookService.getById(addressBookId);
        if (addressBook == null){
            throw new CustomException("用户地址信息有误,不能下单");
        }

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


        AtomicInteger amount = new AtomicInteger(0);

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


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


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

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


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

    }
}
  • 在这里修改下控制层AddressBookController(地址簿管理),之前我写错了。

总结

基础篇大体上完成了,接下来便是进阶篇了。

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 瑞吉外卖项目 —— 开发日志


三二一的一的二