瑞吉外卖
准备工作
首先创建所需的数据库表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375/*
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='用户信息';
创建一个SpringBoot项目,勾选上SpringWeb,Lombok,MySQL,然后在
pom.xml中导入druid和MyBatisPlus的依赖1
2
3
4
5
6
7
8
9
10<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
导入黑马提供的前端资源,放在resources/static目录下
如果直接放在resources目录下,会无法查找到,此时需要配置一下资源映射
1 |
|
然后配置一下端口号和数据库连接四要素就能访问静态页面了,这里别忘了更改数据库的名称和密码
1 | server: |
打开浏览器,访问 http://localhost:8080/backend/page/login/login.html 就可以看到登录页面了,不过此时还无法登录

后台系统登录功能
在准备工作已经将所有前端页面放在了resources/static目录下,可以直接在resource/static/backend/page/login.html看到登录页面的代码
创建实体类
登录功能比较简单,只需要输入账号密码后点击登录就应当能登陆成功

此时使用驼峰命名法的属性名,可以通过在配置文件中设置的
map-underscore-to-camel-case属性,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射(现在MybatisPlus默认开启)1 |
|
创建Mapper、Service、Controller
直接继承BaseMapper就行了,别忘了@Mapper注解
1 |
|
1 | public interface EmployeeService extends IService<Employee> { |
继承ServiceImpl,实现EmployeeService接口,别忘了@Service注解
1 |
|
1 |
|
编写Controller
给EmployeeController层添加login方法
@RequestBody用于接收前端传递给后端的json字符串(请求体中的数据)HttpServletRequest作用:如果登录成功,将员工对应的id存到session一份,这样浏览器可以获得登录员工的数据
1 | //通过表单提交,所以发送post请求 |
统一结果封装
为了方便前端接收数据,编写一个Result结果类,统一返回数据的格式
1 |
|
登出功能
在Controller层添加Logout方法
1 | /** |
登出的功能在index页面,右上角有一个按钮,点击就能登出
1 | <div class="right-menu"> |
对应的函数如下,这里的logoutApi用来发送post请求
1 | logout() { |
1 | function logoutApi(){ |
登录测试
数据库中目前只有一条用户信息,username为admin,password为123456(已经经过MD5加密了)
现在访问 http://localhost/backend/page/login/login.html
当输入正确的用户名和密码时,可以跳转至http://localhost/backend/index.html页面
当输入错误的用户名或密码,会显示错误信息


对应的HTML代码如下
1 | methods: { |
对应的JS代码如下
1 | function loginApi(data) { |
完善登录功能
使用过滤器来拦截请求
- 创建过滤器,设置所有请求全部拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LoginCheckFilter implements Filter {
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//将拦截到的URI输出到日志,这是新的输出方式,{}是占位符,将自动填充request.getRequestURI()的内容
log.info("拦截到的URI:{}", request.getRequestURI());
filterChain.doFilter(request, response);
}
}
并在启动类上加入注解@ServletComponentScan
1 |
|
启动服务器,访问index页面,查看日志,现在可以拦截到URI了
2022-09-29 18:05:53.190 …… : 拦截到的URI:/backend/index.html
2022-09-29 18:06:01.174 …… : 拦截到的URI:/employee/page
编写Filter处理逻辑
获取本次请求的URI
1
2
3
4
5
6
7
8
9//获取本次请求的URI
String uri = request.getRequestURI();
//定义不需要被拦截的请求
String[] urls = new String[]{
"/employee/login.html",
"/employee/logout.html",
"/backend/**",
"/front/**"
};判断本次请求是否需要处理
使用Spring 的PathMatcher路径匹配器1
2
3
4
5
6
7
8
9
10public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
private boolean check(String[] urls, String uri) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, uri);
if (match)
return true;
}
return false;
}
- 如果不需要处理,则直接放行
1
2
3
4if (check) {
filterChain.doFilter(request, response);
return;
}
判断登录状态,如果已登录,则直接放行
1
2
3
4
5//我们当初存的session是employee,所以这里就拿它判断
if (request.getSession().getAttribute("employee") != null) {
filterChain.doFilter(request,response);
return;
}如果未登录则返回未登录结果
1 | //未登录状态为什么要返回一个error呢?而且msg为NOTLOGIN |
从JS代码可以看出,当符合未登录状态的条件时,会自动重定向到登录页面
1 | // 响应拦截器 |
这里需要导入fastjson的坐标
1 | <dependency> |
完整代码
完整步骤就是上面的五步再使用日志来输出一些东西,方便我们来调试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class LoginCheckFilter implements Filter {
//路径匹配
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//强转
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1.获取本次请求的URI
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}",requestURI);
//定义不需要处理的请求
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//2.判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3.如果不需要处理,则直接放行
if (check) {
log.info("本次请求:{},不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
//4.判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
log.info("用户已登录,id为{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
//5.如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
log.info("用户未登录");
log.info("用户id{}",request.getSession().getAttribute("employee"));
response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN")));
}
public boolean check(String[] urls, String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
//匹配
return true;
}
}
//不匹配
return false;
}
}
测试登录
当我们直接访问 http://localhost/backend/index.html 时,可以发现已经自动跳转到登录页面并输出以下日志:

添加员工
流程分析

简单看一下前端的代码,当点击添加员工按钮时,就会调用addEmployee方法,跳转到发送post请求,此时的界面信息,数据模型绑定的是ruleForm,通过双向绑定来接受用户的信息,输入信息后保存并添加按钮绑定了submitForm函数,调用相关方法并返回操作信息
1 | <el-form |
1 | ruleForm : { |
1 | submitForm (formName, st) { |
1 | // 新增---添加员工 |
接下来来完善后端方法,先通过日志来测试能否接收到提交的员工信息
1 |
|
- 启动服务器后,按照要求填写员工信息

- 可以看到能接受到员工的相关信息,但有一些信息是null
新增的员工信息:新增的员工信息:Employee(id=null, username=111, name=111, password=null, phone=15845678912, sex=1, idNumber=371312222222222222, status=null, createTime=null, updateTime=null, createUser=null, updateUser=null)
分析员工信息:
id:可以通过雪花算法或自动递增来自动生成password:可以设置为默认值123456,但是需要进行MD5加密后存储(数据库中应当设置为加密后的密码,因为在登录时对比的就是加密后的密码)status:设定员工的状态,1表示启用,0表示禁用,设置默认值为1createTime:创建时间,这个可以指定当前系统时间updateTime:作用同上createUser:保存创建员工信息的人的名称,以免随意创建员工账号updateUser:作用同上
具体实现
综上所述,我们设置创建时间和更新时间,创建人ID和修改人ID,其他采用默认值即可,但是密码需要将默认值加密后再存储到数据库中,所以也需要手动编写代码添加
1 |
|
完善全局异常处理器并测试
解决方案:
- 在Controller层中添加try、catch进行异常捕获
- 使用异常处理器进行全局异常捕获
在com.blog.common包下创建一个全局异常处理类GlobalExceptionHandler,并添加ExceptionHandler方法用来捕获异常,并返回结果,在方法上添加@ExceptionHandler来表示该方法专门处理该种异常
1 |
|
先用日志输出一下看看能不能正常运行,这也是代码开发的一个好习惯
再次添加重复用户名的员工信息,发现这次会报错就会出现未知错误的弹窗了
控制台日志输出的错误信息为Duplicate entry 'Kyle' for key 'employee.idx_username'

- 只提示未知错误过于笼统,应当提示用户准确的错误信息才是好的程序,所以需要对错误信息进行判断,根据返回的错误信息,再对错误信息中动态的部分进行切面,利用字符串拼接来告诉用户错误的原因
1 |
|
接下来重启服务器,测试添加功能,输入已经存在的username,此时的报错信息就更加准确了

员工信息分页查询
配置MyBatisPlus分页插件
温馨提示:
- 这里如果使用的
Mybatis-Plus版本是3.5.9+,那么想使用分页功能需要额外添加一个依赖管理和一个依赖,官方说明1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-bom</artifactId>
<version>3.5.11</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
新建com.blog.config包,并在其中新建MybatisPlusConfig类
1 |
|
前端代码分析
为什么每次登录后都会先报一个错误?因为当我们访问页面时,页面会自动执行一个分页查询操作,
使用GET发送的请求,请求参数在URL中

分析前端代码:
这段代码位于resource/backend/page/member/list.html
这段生命周期函数会在页面初始化时调用来构造数据
1 | async init () { |
这里的rows对应tableData,totalCount对应counts
1 | //分页查询的JavaBean |
发送的是GET请求,请求路径为/employee/page,请求参数为前面初始化的params对象
1 | function getMemberList (params) { |
这是前端提供的拦截器,因为前面的params是json格式的,通过该拦截器将请求参数使用拼串的方式拼接到URL上
1 | // request拦截器 |
编写具体的业务逻辑
先通过日志来检查是否能正常接收到数据,因为这里的需求是服务端Controller接收页面提交的数据并调用Service查询数据,所以返回值中应当包含当前页数,总页数等数据,因此返回值的泛型不是Employee而是Page
1 |
|
此时在搜索框输入123,发现日志输出如下,符合预期
: page=1,pageSize=10,name=123
继续完善业务逻辑
1 |
|
补充说明
为什么后端传给页面的status数据为Integer类型,到页面展示效果的时候显示的是已禁用或者正常?
看一下源码就知道了
三目运算符+插值表达式
启用/禁用员工账号
需求分析
- 在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号状态为禁用的员工不能登录系统,启用后的员工可以正常登录。
- 需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。
- 管理员admin登录系统可以对所有员工账号进行启用、禁用操作。
- 如果某个员工账号状态为正常,则按钮显示为“禁用”,如果员工账号状态为已禁用,则按钮显示为“启用”


动态按钮显示分析
怎么才能做到:只有当登录的是管理员账号时,才能看到启用/禁用按钮呢?
- 当我们加载完页面的时候,获取一下当前登录账号的用户名,也就是username
1 | created() { |
随后判断一下这个用户名是不是
admin,如果是的话就显示启用/禁用,否则不显示,通过v-if来判断1
2
3
4
5
6
7
8
9<el-button
type="text"
size="small"
class="delBut non"
@click="statusHandle(scope.row)"
v-if="user === 'admin'"
>
{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>
Ajax请求发送过程

- 前端代码:
在按钮上绑定了
statusHandle(scope.row)方法1
2
3
4
5
6
7
8
9<el-button
type="text"
size="small"
class="delBut non"
@click="statusHandle(scope.row)"
v-if="user === 'admin'"
>
{{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>该方法先获得当前行的id与status,接着弹出提示框确认是否要修改状态,确认后调用
enableOrDisableEmployee将其状态取反1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//状态修改
statusHandle (row) {
this.id = row.id
this.status = row.status
this.$confirm('确认调整该账号的状态?', '提示', {
'confirmButtonText': '确定',
'cancelButtonText': '取消',
'type': 'warning'
}).then(() => {
enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {
console.log('enableOrDisableEmployee',res)
if (String(res.code) === '1') {
this.$message.success('账号状态更改成功!')
this.handleQuery()
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
})
}1
2
3
4
5
6
7
8// 修改---启用禁用接口
function enableOrDisableEmployee (params) {
return $axios({
url: '/employee',
method: 'put',
data: { ...params }
})
}
- 后端代码分析
启用、禁用员工账号,本质上是更新操作,也就是对status状态字段进行修改
养成先用日志测试的习惯1
2
3
4
5
public Result<String> update( Employee employee) {
log.info(employee.toString());
return null;
}

- 通过日志可以看到能够正常接收到employee对象数据,接着完善update方法,在更新用户信息时自动更新修改时间和修改用户
1 |
|
- 通过日志可以看到更新语句输出了,但没有输出更新语句,数据库中的status字段也没有被修改

配置消息转换器
更新失败的原因是JS对Long型数据进行处理时丢失了精度(前端接受数据类型,超过16位会精度损失),导致提交的id和数据库中的id不一致,导致更新失效,可以通过配置消息转换器将Long型数据转为String型数据,再进行数据库更新
直接复制下面的对象转换器放到common包下
1 |
|
扩展Mvc框架的消息转换器
1 |
|
再次测试
再次点击禁用按钮,此时数据库中的status字段数据发生了改变,且页面上也显示已禁用,再次点击启用,也能正常操作

编辑员工信息
流程分析
在开发代码之前先梳理一下整个操作流程与对应程序的执行顺序:
- 点击编辑按钮时,页面将跳转到
add.html,并在url中携带参数员工id - 在
add.html页面中获取url中的参数员工id - 发送
ajax请求,请求服务端,同时提交员工id参数 - 服务端接受请求,并根据
员工id查询员工信息,并将员工信息以json形式响应给页面 - 页面接收服务端响应的
json数据,并通过Vue的双向绑定进行员工信息回显 - 点击保存按钮,发送ajax请求,将页面中的员工信息以json形式提交给服务端
- 服务端接受员工信息,并进行处理,完成后给页面响应
- 页面接收到服务端响应信息后进行相应处理

具体实现
点击编辑按钮时,页面将跳转到
add.html,并在url中携带参数员工id,编辑按钮绑定的点击事件为addMemberHandle(scope.row.id)1
2
3
4
5
6
7
8
9<el-button
type="text"
size="small"
class="blueBug"
@click="addMemberHandle(scope.row.id)"
:class="{notAdmin:user !== 'admin'}"
>
编辑
</el-button>在
add.html页面中获取url中的参数员工id1
2
3
4
5
6
7
8
9
10
11
12
13
14
15addMemberHandle (st) {
if (st === 'add'){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html',
name: '添加员工'
},true)
} else {
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/add.html?id='+st,
name: '修改员工'
},true)
}
}发送
ajax请求,请求服务端,同时提交员工id参数当add.html加载完毕之后,调用钩子函数,当参数存在时,说明是编辑员工,否则是添加员工
1 | created() { |
1 | function requestUrlParam(argname){ |
- 服务端接受请求,并根据
员工id查询员工信息,并将员工信息以json形式响应给页面
1 |
|
- add.html的钩子函数中调用了init函数,接收到服务端响应的Json数据后,判断状态码,如果操作成功则将获取到的数据赋给表单,并通过双向绑定来实现数据回显
1 | async init () { |
- 点击保存按钮,发送ajax请求,将页面中的员工信息以json形式提交给服务端
1 | <el-button |
可以看出添加和修改用的是一个表单来提交数据
1 | submitForm (formName, st) { |
1 | // 修改---添加员工 |
- 服务端接受信息后,再次调用前面写的update方法,对员工信息进行修改
1 |
|
- 员工信息修改成功之后,调用
goBack函数,跳转至员工管理页面1
2
3
4
5
6
7goBack(){
window.parent.menuHandle({
id: '2',
url: '/backend/page/member/list.html',
name: '员工管理'
},false)
}
公共字段自动填充
问题引出
- 前面完成了对员工数据的添加与修改,在添加/修改员工数据的时候,需要指定创建人、创建时间、修改人、修改时间等字段,而这些字段又属于公共字段,不仅员工表有这些字段,在菜品表、分类表等其他表中,也拥有这些字段。
- 有没有办法让这些字段在一个地方统一管理,以此来简化开发呢?
- 答案是使用
MybatisPlus提供的公共字段自动填充功能
- 答案是使用
代码实现
实现步骤
在实体类的属性上方加入
@TableFiled注解,指定自动填充的策略1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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;
private Long createUser;
private Long updateUser;
}1
2
3
4
5
6
7
8
9public enum FieldFill {
DEFAULT,
INSERT,
UPDATE,
INSERT_UPDATE;
private FieldFill() {
}
}按照框架要求编写元数据对象处理器,在此类中统一对公共字段赋值,此类需要实现
MetaObjectHandler接口,实现接口之后,重写两个方法,一个是插入时填充,一个是修改时填充
关于字段填充方式,使用metaObject的setValue来实现
关于id的获取,我们之前是存到session里的,但在MyMetaObjectHandler类中不能获得HttpSession对象,所以我们需要用其他方式来获取登录用户Id1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyMetaObjectHandler implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充(insert)...");
log.info(metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
}
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充(update)...");
log.info(metaObject.toString());
metaObject.setValue("updateTime", LocalDateTime.now());
}
}
功能完善
- 现在已经能够填充时间和创建人两个公共字段了,但是还不能添加用户的id,因为之前是通过httpSession对象来获取的,但是现在不能使用session对象了,此时我们需要使用
ThreadLocal来解决 - 在学习ThreadLocal之前,需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
LocalCheekFilter中的doFilter方法EmployeeController中的update方法MyMetaObjectHandler中的updateFill方法
可以通过在这三个方法中添加日志输出测试,此处省略
什么是ThreadLocal?
- ThreadLocal并不是一个Thread,而是Thread的局部变量
- 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本
- 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
- ThreadLocal为每个线程提供单独一份存储空间,具有
线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
public void set(T value)设置当前线程的线程局部变量的值public T get()返回当前线程所对应的线程局部变量的值
如何用ThreadLocal来解决上述的问题呢?
- 我们可以在
LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
具体实现
- 在com.blog.common包下新建BaseContext类
作用:基于ThreadLocal的封装工具类,用于保护和获取当前用户id
1
2
3
4
5
6
7
8
9
10
11public 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类中添加代码,获取到id后保存到ThreadLocal中
1
2
3
4
5
6
7
8
9
10
11
12
13//4.判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null) {
log.info("用户已登录,id为{}", request.getSession().getAttribute("employee"));
//在这里获取一下线程id
long id = Thread.currentThread().getId();
log.info("doFilter的线程id为:{}", id);
//根据session来获取之前我们存的id值
Long empId = (Long) request.getSession().getAttribute("employee");
//使用BaseContext封装id
BaseContext.setCurrentId(empId);
filterChain.doFilter(request, response);
return;
}在MyMetaObjectHandler类中,调整设置id的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyMetaObjectHandler implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) {
log.info("公共字段填充(create)...");
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
//设置创建人id
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
public void updateFill(MetaObject metaObject) {
log.info("公共字段填充(insert)...");
metaObject.setValue("updateTime", LocalDateTime.now());
//设置更新人id
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}
新增菜品分类
需求分析
- 后台系统中可以管理分类信息,分类包括两种类型,分别是
菜品分类和套餐分类 - 当我们在后台系统中添加菜品时,需要选择一个菜品分类
- 当我们在后台系统中添加一个套餐时,需要选择一个套餐分类
- 在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐
可以在后台系统的分类管理页面分别添加菜品分类和套餐分类,如下


数据模型
简单了解category表中的数据

准备工作
开发业务之前,先将需要用到的类和接口的基本结构先创建好
根据数据库信息来创建实体类Category
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
//创建人
private Long createUser;
//修改人
private Long updateUser;
}
- 创建接口和实现类
1
2
3
public interface CategoryMapper extends BaseMapper<Category> {
}1
2public class CategoryMapperImpl {
}1
2public interface CategoryService extends IService<Category> {
}1
2
3
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
} - 创建控制层
1
2
3
4
5
6
7
8
public class CategoryController {
private CategoryService categoryService;
}
流程分析
我们先尝试监测一下前端给我们提供的是什么请求,以及会提交什么数据,打开开发者工具,监测NetWork,点击新增菜品分类表单的确定按钮
请求方式
请求网址: http://localhost/category
请求方法: POSTjson数据
{name: “川菜”, type: “1”, sort: “10”}
点击新增套餐分类表单的确定按钮
请求方式
请求网址: http://localhost/category
请求方法: POSTjson数据
{name: “好吃的套餐”, type: “2”, sort: “10”}
新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,所以服务端只需要提供一个方法就可以统一处理新增菜品和新增套餐
代码实现
接受数据后调用MybatisPlus提供的save方法保存数据即可,并返回成功添加的提示信息
1 |
|
但通过查看前端代码,发现显示的信息在前端写死了,只要最后的状态码是成功状态码,则均显示
分类添加成功!1
2if (res.code === 1) {
this.$message.success('分类添加成功!')如果想要添加菜品和添加套餐显示不同的响应结果,可以按照如下方式修改代码
将
1 | if (res.code === 1) { |
1 | return Result.success(category.getType() == 1 ? "添加菜品分类成功!" : "添加套餐分类成功!"); |
分类信息分页查询
与员工信息分页查询类似,只是查询的表不同
流程分析
分析流程:
- 页面发送Ajax请求,将分页查询的参数(page、pageSize)提交到服务端
- 服务端Controller接受到页面提交的数据之后,调用Service进行查询
- Service调用Mapper操作数据库,查询分页数据
- Controller将查询到的分页数据响应给页面
- 页面接收分页数据,并通过ElementUI的Table组件展示到页面上
前端代码分析
跟前面基本一致,简单回顾
页面加载完毕之后调用created钩子函数
钩子函数内又调用的是init进行初始化
1 |
|
1 | async init () { |
1 | // 查询列表接口 |
代码实现
因为前面写员工分页查询,相关配置已经完成,所以直接在CategoryController类中编写page方法即可
1 |
|
查看效果

删除分类
需求分析
- 在分类管理列表页面,可以对某个分类进行删除操作
- 需要注意的是:当某一分类还有关联的菜品或者套餐时,该分类将不允许被删除
后端代码实现
在CategoryController类上添加delete方法
1 |
|
前端代码分析
点击删除按钮后会直接调用deleteHandler()方法
1 | <el-button |
返回提示信息,确认删除后调用deleteCategory()方法去发送删除命令
1 | deleteHandle(id) { |
这里需要注意没有使用Restful风格,因为请求中为?id=xxx,还需要注意修改黑马提供的前端资料,路径为backend/api/category.js,将此处的ids改为id(两处都要修改),然后清除缓存
1 | // 删除当前列的接口 |
此时再重启服务器再次测试即可

功能完善
完成了单纯的删除操作,现在来完善功能,当菜品分类或套餐分类关联了其他菜品或套餐时,该分类将不允许被删除
代码完善:
- 首先根据数据表创建菜品和套餐对应的模型类
1 |
|
1 | /** |
- 编写对应的Mapper接口
1 |
|
1 |
|
- 编写对应的Service接口及Impl实现类
1 | public interface DishService extends IService<Dish> { |
1 |
|
1 | public interface SetmealService extends IService<Setmeal> { |
1 |
|
- 在common包下新增
CustomException类,用于封装我们的自定义异常
1 | public class CustomException extends RuntimeException{ |
- 在全局异常处理器类中,添加上
CustomerException异常的处理
1 |
|
- 在CategoryService接口中自己写一个
remove方法
1 | public interface CategoryService extends IService<Category> { |
在CategoryServiceImpl中来写具体业务逻辑
我们需要在删除数据之前,根据id值,去Dish表和Setmeal表中查询是否管理数据
如果未查询到数据则代码不关联数据,则可以删除,查询到数据则代表不能删除并抛出异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
DishService dishService;
SetmealService setmealService;
/**
* 根据id删除分类,删除之前需要进行判断
* @param id
*/
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加dish查询条件,根据分类id进行查询
dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);
//方便Debug用的
int count1 = dishService.count(dishLambdaQueryWrapper);
log.info("dish查询条件,查询到的条目数为:{}",count1);
//查看当前分类是否关联了菜品,如果已经关联,则抛出异常
if (count1 > 0){
//已关联菜品,抛出一个业务异常
throw new CustomException("当前分类下关联了菜品,不能删除");
}
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加dish查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
//方便Debug用的
log.info("setmeal查询条件,查询到的条目数为:{}",count2);
//查看当前分类是否关联了套餐,如果已经关联,则抛出异常
if (count2 > 0){
//已关联套餐,抛出一个业务异常
throw new CustomException("当前分类下关联了套餐,不能删除");
}
//正常删除
super.removeById(id);
}
}最后在controller的删除方法中调用自己写的remove方法
1
2
3
4
5
6
public Result<String> delete(Long id){
log.info("将要删除的分类id:{}",id);
categoryService.remove(id);
return Result.success("分类信息删除成功");
}
最终效果:

修改分类
需求分析
与修改员工信息类似,点击修改按钮后,在修改窗口回显信息,最后点击确认完成修改操作

代码开发
数据的回显效果由前端来实现,与员工信息修改大体相同,简略说一些即可
- 修改按钮绑定了
editHandle函数,一旦点击就会调用该函数,并以该行的数据作为参数 - editHandle函数负责初始化表格中数据的值,也就是数据回显
- 表单通过v-model实现双向绑定
此处修改功能也和添加功能共用一个方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53//数据提交
submitForm(st) {
const classData = this.classData
const valid = (classData.name === 0 ||classData.name) && (classData.sort === 0 || classData.sort)
if (this.action === 'add') {
if (valid) {
const reg = /^\d+$/
if (reg.test(classData.sort)) {
addCategory({'name': classData.name,'type':this.type, sort: classData.sort}).then(res => {
console.log(res)
if (res.code === 1) {
this.$message.success('分类添加成功!')
if (!st) {
this.classData.dialogVisible = false
} else {
this.classData.name = ''
this.classData.sort = ''
}
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else {
this.$message.error('排序只能输入数字类型')
}
} else {
this.$message.error('请输入分类名称或排序')
}
} else if (valid) {
const reg = /^\d+$/
if (reg.test(this.classData.sort)) {
editCategory({'id':this.classData.id,'name': this.classData.name, sort: this.classData.sort}).then(res => {
if (res.code === 1) {
this.$message.success('分类修改成功!')
this.classData.dialogVisible = false
this.handleQuery()
} else {
this.$message.error(res.msg || '操作失败')
}
}).catch(err => {
this.$message.error('请求出错了:' + err)
})
} else {
this.$message.error('排序只能输入数字类型')
}
} else {
this.$message.error('请输入分类名称或排序')
}
}添加操作是post请求,修改是发送PUT请求
1
2
3
4
5
6
7
8// 修改接口
const editCategory = (params) => {
return $axios({
url: '/category',
method: 'put',
data: { ...params }
})
}
- 后端代码开发
1
2
3
4
5
6
public Result<String> update( Category category) {
log.info("修改分类信息为:{}", category);
categoryService.updateById(category);
return Result.success("修改分类信息成功");
}
文件上传与下载
文件上传简介
文件上传时,对页面的form表单有如下要求:
method="post",采用post方式提交数据enctype="multipart/form-data",采用multipart格式上传文件type="file",使用input的file控件上传
举例
1
2头像:
<input type="file"><br>头像:
目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传,这里直接使用提供好的组件就行了
把这段代码放在backend/demo目录下,命名为upload.html1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="../../plugins/element-ui/index.css" />
<link rel="stylesheet" href="../../styles/common.css" />
<link rel="stylesheet" href="../../styles/page.css" />
</head>
<body>
<div class="addBrand-container" id="food-add-app">
<div class="container">
<el-upload class="avatar-uploader"
action="/common/upload"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeUpload"
ref="upload">
<img v-if="imageUrl" :src="imageUrl" class="avatar"></img>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../plugins/element-ui/index.js"></script>
<!-- 引入axios -->
<script src="../../plugins/axios/axios.min.js"></script>
<script src="../../js/index.js"></script>
<script>
new Vue({
el: '#food-add-app',
data() {
return {
imageUrl: ''
}
},
methods: {
handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
},
beforeUpload (file) {
if(file){
const suffix = file.name.split('.')[1]
const size = file.size / 1024 / 1024 < 2
if(['png','jpeg','jpg'].indexOf(suffix) < 0){
this.$message.error('上传图片只支持 png、jpeg、jpg 格式!')
this.$refs.upload.clearFiles()
return false
}
if(!size){
this.$message.error('上传文件大小不能超过 2MB!')
return false
}
return file
}
}
}
})
</script>
</body>
</html>服务端接收客户端页面上传的文件,通常都会使用Apache的两个组件:
commons-fileuploadcommons-io
Spring框架在
spring-web包中对文件上传进行了封装,大大简化了服务端代码,只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如1
2
3
4
5
6
7
8
9
10
public class CommonController {
public Result<String> upload(MultipartFile file) {
log.info("获取文件:{}", file.toString());
return null;
}
}启动服务器,登陆之后访问http://localhost/backend/page/demo/upload.html,传入一张图片,便能在日志上看到相关信息(需要先有过登录行为)

文件下载简介
- 通过浏览器进行文件下载,通常有两种表现形式
- 以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
- 直接在浏览器中打开
- 通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
文件上传代码实现
在编写代码之前,先来设置一下拦截路径,回到拦截器类中
1
2
3
4
5
6
7
8//定义不需要处理的请求
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**"
};随后使用
transferTo方法将上传的临时文件转存到指定位置
1 |
|
此时可以通过在http://localhost:8080/backend/page/demo/upload.html该网址内继续上传图片来测试功能是否能正常执行

文件转存的位置可以通过修改配置文件来动态改变:在application.yml文件中加入以下内容
1
2reggie:
path: D:\\Test\\使用
@Value("${reggie.path}")读取到配置文件中的动态转存位置使用
uuid方式重新生成文件名,避免文件名重复造成文件覆盖通过获取原文件名来截取文件后缀,并拼接成新的文件名
最后的返回值是将我们生成的新文件名返回给前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CommonController {
private String basePath;
//file是个临时文件,我们在断点调试的时候可以看到,但是执行完整个方法之后就消失了
public Result<String> upload(MultipartFile file) {
log.info("获取文件:{}", file.toString());
//判断一下当前目录是否存在,不存在则创建
File dir = new File(basePath);
if (!dir.exists()) {
dir.mkdirs();
}
//获取一下传入的原文件名
String originalFilename = file.getOriginalFilename();
//我们只需要获取一下格式后缀,取子串,起始点为最后一个.
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
//为了防止出现重复的文件名,我们需要使用UUID
String fileName = UUID.randomUUID() + suffix;
log.info("文件保存路径:{}", basePath + fileName);
try {
//我们将其转存到我们的指定目录下
file.transferTo(new File(basePath + fileName));
} catch (IOException e) {
throw new RuntimeException(e);
}
//将文件名返回给前端,便于后期的开发
return Result.success(fileName);
}
}重启服务器,随便上传一张图片,然后去对应的目录下看看是否有上传的图片
如果一切顺利的话,目录不存在则会自动创建,而且上传的图片也在文件夹下
文件下载代码实现
前端处理
前端页面的ElementUI的upload组件会在上传完图片后,触发img组件发送请求,服务端以流的方式(输出流)将文件写回给浏览器,在浏览器中展示图片
1
2
3
4
5
6
7
8
9<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>定义前端发送回显图片请求的地址
通过这个url我们可以看出,请求路径为/common/download,且发送的是GET请求1
2
3handleAvatarSuccess (response, file, fileList) {
this.imageUrl = `/common/download?name=${response.data}`
}
后端处理
- 在
CommonController类中添加download方法- 通过输入流读取文件内容
- 通过输出流将文件写回浏览器,在浏览器展示图片
- 关闭输入输出流,释放资源
1 |
|
1 |
|
- 然后启动服务器,上传一张图片,就会发现图片直接展示在页面上了

新增菜品
需求分析
- 后台系统中可以管理菜品信息,通过新增功能来添加一个新的菜品
- 在添加菜品时需要选择当前菜品所属的菜品分类,并且上传菜品图片
- 在移动端会按照菜品分类来展示对应的菜品信息(前端的活儿,跟后端没啥太大关系)

数据模型
dish表,最后一条字段is_deleted是逻辑删除


代码开发
准备工作
- 前面跟Dish有关的已经创建好了,接下来创建DishFlavor对应的实体类,Mapper接口,Service接口及其对应的实现类
1 | /** |
1 |
|
1 | public interface DishFlavorService extends IService<DishFlavor> { |
1 |
|
编写Controller层代码
1 |
|
查询分类数据
接下来逐步完成这四个请求即可

- 调用getDishList方法来初始化表格,并通过传入的id来判断是添加菜品还是编辑菜品
1
2
3
4
5
6
7
8
9
10created() {
this.getDishList()
// 口味临时数据
this.getFlavorListHand()
this.id = requestUrlParam('id')
this.actionType = this.id ? 'edit' : 'add'
if (this.id) {
this.init()
}
}
根据响应的Code值来判断操作是否成功
1 | getDishList () { |
发送get请求来获取菜品分类列表
1 | // 获取菜品分类列表 |
使用v-for遍历菜品信息
1 | <el-select |
在
CategoryController类中,添加list方法,查询菜品信息即可1
2
3
4
5
6
7
8
9
10
11
12
13
public Result<List<Category>> list(Category category) {
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件,这里只需要判断是否为菜品(type为1是菜品,type为2是套餐)
queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
//添加排序条件
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
//查询数据
List<Category> list = categoryService.list(queryWrapper);
//返回数据
return Result.success(list);
}
接收与回显图片
该功能前面已经在CommonController中添加了对应的upload和download方法,因此可以直接使用
提交数据到服务端
- 先随便提交点数据测试一下

- 以下是浏览器发送的Json数据

- 此处可以发现价格变成了输入价格的100倍,这是前端在
submitForm方法中对数据进行的处理,一般金额都以最小单位进行存储,数据库存储的单位为分
1 | submitForm(formName, st) { |
因为Dish实体类不满足接收flavor参数,即需要导入DishDto,用于封装页面提交的数据
DTO,全称为
1
Data Transfer Object
,即数据传输对象,一般用于展示层与服务层之间的数据传输。
1
2
3
4
5
6
7
8
9
10
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
//后面这两条属性暂时没用,这里只需要用第一条属性
private String categoryName;
private Integer copies;
}在
1
DishController
类中添加
1
save
方法,重启服务器,断点调试一下看看是否封装好了数据
1
2
3
4
5@PostMapping
public Result<String> save(@RequestBody DishDto dishDto) {
log.info("接收到的数据为:{}",dishDto);
return null;
}
从图中我们可以看出,DishFlavor中的dishId为null
但是我们需要对DishFlavor中的dishId进行赋值
所以我们要取出dishDto的dishId,然后对每一组flavor的dishId赋值
这里进行一下小结,我们需要做的有以下几点
将菜品数据保存到
dish表将菜品口味数据保存到
dish_flavor表- 但是
dish_flavor表中需要一个dishId字段值,这个字段值需要我们从dishDto中获取 - 获取方式为:取出
dishDto的dishId,对每一组flavor的dishId赋值
- 但是
梳理完毕之后,那么我们就在
DishFlavorService中编写一个saveWithFlavor方法1
2
3public interface DishService extends IService<Dish> {
void saveWithFlavor(DishDto dishDto);
}同时在
DishFlavorServiceImpl中重写方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
private DishFlavorService dishFlavorService;
public void saveWithFlavor(DishDto dishDto) {
//将菜品数据保存到dish表
this.save(dishDto);
//获取dishId
Long dishId = dishDto.getId();
//将获取到的dishId赋值给dishFlavor的dishId属性
List<DishFlavor> flavors = dishDto.getFlavors();
for (DishFlavor dishFlavor : flavors) {
dishFlavor.setDishId(dishId);
}
//同时将菜品口味数据保存到dish_flavor表
dishFlavorService.saveBatch(flavors);
}
}最后别忘了改Controller层的方法
1
2
3
4
5
6
public Result<String> save( DishDto dishDto) {
log.info("接收到的数据为:{}",dishDto);
dishService.saveWithFlavor(dishDto);
return Result.success("菜品添加成功");
}
菜品信息分页查询
需求分析
- 与之前一样,如果将所有数据直接在土匪页面展示出来会显得非常乱,不易于查看,所以需要以分页的方式展示列表数据,其中图片列和菜品分类列比较特殊
- 图片列:会用到文件的下载功能
- 菜品分类列:由于菜品表只保存了category_id,所以需要查询category_id对应的菜品分类名称,从而回显数据

代码开发
开发分页功能只需要编写代码实现前端发送的两次请求即可
- 在
DishController下添加page方法,来进行分页查询
1 |
|
- 重启服务器可以发现,此时数据能够分页展示,但是图片和菜品分类的数据都没有展示
- 图片只需要将黑马提供的图片资源放到存放图片的目录下即可

- 图片只需要将黑马提供的图片资源放到存放图片的目录下即可
为什么没有菜品分类数据?
- 我们传递的是一个Dish对象,dish对象只有菜品分类id,没有菜品分类名称属性,前端是根据分类名称来填写,所以暂时没有分类数据
- 可以根据这个菜品分类id,去菜品分类表中查询对应的菜品分类名称
此时可以使用DishDto类中的另外一个属性,返回一个DishDto对象就有菜品分类名称数据了
1
2
3
4
5
6
7
8
9
public class DishDto extends Dish {
//菜品口味
private List<DishFlavor> flavors = new ArrayList<>();
//菜品分类名称
private String categoryName;
private Integer copies;
}DishDto直接继承了Dish,所以可以看作是Dish类额外添加了一个categoryName属性。
- 修改Controller层中的page方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public Result<Page> page(int page, int pageSize, String name) {
//构造分页构造器对象
Page<Dish> pageInfo = new Page<>(page, pageSize);
//这个就是我们到时候返回的结果
Page<DishDto> dishDtoPage = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加条件
queryWrapper.like(name != null, Dish::getName, name);
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行分页查询
dishService.page(pageInfo, queryWrapper);
//对象拷贝,这里只需要拷贝一下查询到的条目数
BeanUtils.copyProperties(pageInfo, dishDtoPage, "records");
//获取原records数据
List<Dish> records = pageInfo.getRecords();
//遍历每一条records数据
List<DishDto> list = records.stream().map((item) -> {
DishDto dishDto = new DishDto();
//将数据赋给dishDto对象
BeanUtils.copyProperties(item, dishDto);
//然后获取一下dish对象的category_id属性
Long categoryId = item.getCategoryId(); //分类id
//根据这个属性,获取到Category对象(这里需要用@Autowired注入一个CategoryService对象)
Category category = categoryService.getById(categoryId);
//随后获取Category对象的name属性,也就是菜品分类名称
String categoryName = category.getName();
//最后将菜品分类名称赋给dishDto对象就好了
dishDto.setCategoryName(categoryName);
//结果返回一个dishDto对象
return dishDto;
//并将dishDto对象封装成一个集合,作为我们的最终结果
}).collect(Collectors.toList());
dishDtoPage.setRecords(list);
return Result.success(dishDtoPage);
}
修改菜品
梳理交互过程
梳理流程:
- 页面发送ajax请求,请求服务器获取分类数据,用于菜品分类下拉框的数据回显(之前我们已经实现过了)
- 页面发送ajax请求,请求服务端,根据id查询当前菜品信息,用于菜品信息回显
- 页面发送请求,请求服务端进行图片下载,用于页面图片回显(之前我们已经实现过了)
- 点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端

开发修改菜品功能,其实就是在服务端写代码去处理以上四次请求
查询菜品信息
先获取修改行的数据,根据id来查询到对应菜品信息进行回显
含有菜品口味属性,所以还是要用到DishDto
先在service层编写一个
getByIdWithFlavor方法,根据dish_id去dish_flavor表中查询,将查询到的菜品口味数据赋给我们的DishDto对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public DishDto getByIdWithFlavor(Long id) {
//先根据id查询到对应的dish对象
Dish dish = this.getById(id);
//创建一个dishDao对象
DishDto dishDto = new DishDto();
//拷贝对象
BeanUtils.copyProperties(dish, dishDto);
//条件构造器,对DishFlavor表查询
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
//根据dish_id来查询对应的菜品口味数据
queryWrapper.eq(DishFlavor::getDishId, id);
//获取查询的结果
List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
//并将其赋给dishDto
dishDto.setFlavors(flavors);
//作为结果返回给前端
return dishDto;
}在
DishController中添加get方法,实现添加在DishServicelmpl中的逻辑代码,返回查询到的数据信息
1
2
3
4
5
6
public Result<DishDto> getByIdWithFlavor( Long id) {
DishDto dishDto = dishService.getByIdWithFlavor(id);
log.info("查询到的数据为:{}", dishDto);
return Result.success(dishDto);
}此时重启服务器,就看到数据成功回显在表格中了

修改菜品信息
由于Dish表中没有Flavor这个属性,所以修改的时候依旧要通过DishDto
修改菜品和添加菜品共用一个界面,根据参数来区分两种操作
1 | <el-button |
1 | // 添加 |
1 | // 修改接口 |
- 编写后端逻辑
和之前类似,重点是编写updateWithFlavor方法
1 |
|
通过id来修改基本信息,通过dish_id来删除口味信息,然后重新传入新的口味信息
1 |
|
新增套餐
需求分析
- 套餐就是多个菜品的集合
- 后台系统中可以管理套餐信息,通过新增套餐来添加一个新的套餐
- 在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片
数据模型
- 新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表中,而且还要向setmeal_dish表中插入套餐和菜品关联数据
所以在新增套餐时,需要对两张表进行操作
- setmeal表 —> 套餐表
- setmeal_dish表 —> 套餐菜品关系表


准备工作
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类SetmealDish
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52/**
* 套餐菜品关系
*/
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;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Long createUser;
private Long updateUser;
//是否删除
private Integer isDeleted;
}DTO SetmealDto
普通的SetmealDish类肯定是不够我们用的,这里还需要加上套餐内的具体菜品和套餐分类名称
1
2
3
4
5
6
7
public class SetmealDto extends Setmeal {
private List<SetmealDish> setmealDishes;
private String categoryName;
}Mapper接口SetmealDishMapper
1
2
3
public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
}业务层接口SetmealDishService
1
2public interface SetmealDishService extends IService<SetmealDish> {
}业务层实现类SetmealDishservicelmpl
1
2
3
public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper, SetmealDish> implements SetmealDishService {
}控制层SetmealController
1
2
3
4
5
6
7
8
9
public class SetmealController {
private SetmealService setmealService;
private SetmealDishService setmealDishService;
}代码开发
新增套餐页面中套餐分类的下拉框能够直接显示套餐分类数据,该功能在之前已经实现

点击添加菜品后可以看到发送了一个
dish/list?categoryId=xxx的get请求,请求参数中包含categoryId,根据这个参数查询对应的菜品数据
先去DishController中编写对应的get方法来正确显示菜品数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Result<List<Dish>> get(Dish dish) {
//条件查询器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//根据传进来的categoryId查询
queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
//只查询状态为1的菜品(启售菜品)
queryWrapper.eq(Dish::getStatus, 1);
//简单排下序,其实也没啥太大作用
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
//获取查询到的结果作为返回值
List<Dish> list = dishService.list(queryWrapper);
return Result.success(list);
}前端代码分析
1 | <el-button type="primary" @click="submitForm('ruleForm', false)"> 保存 </el-button> |
表单和前面一样,是新增和修改共用一个表单。
1 | submitForm(formName, st) { |
1 | // 新增数据接口 |
先来测试一下,查看提交的数据
1
2
3
4
5
public Result<String> save( SetmealDto setmealDto) {
log.info("套餐信息:{}", setmealDto);
return Result.success("套餐添加成功");
}
套餐信息:SetmealDto(setmealDishes=[SetmealDish(id=null, setmealId=null, dishId=1397851370462687234, name=邵阳猪血丸子, price=13800, copies=1, sort=null, createTime=null, updateTime=null, createUser=null, updateUser=null, isDeleted=null)], categoryName=null)
此处setmealId为null,再具体的代码中,要从setmealDao中获取并赋值
- 具体业务逻辑如下
1 |
|
1 | public interface SetmealService extends IService<Setmeal> { |
1 |
|
- 至此,新增套餐的功能就实现了,重启服务器测试一下,查看数据库,套餐信息和套餐菜品信息都能成功插入
套餐信息分页查询
梳理交互过程
- 页面发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务端,获取分页数据
- 页面发送请求,请求服务端进行图片下载,用于页面图片展示(已完成)
前端分析
点击套餐管理,在搜索框输入1,获取请求url与请求方式

- 请求网址: http://localhost/setmeal/page?page=1&pageSize=10&name=1
- 请求方法: GET
代码开发
- 在SetmealController类中,添加list方法,这里和前面的查询方法几乎一致,对比学习
1 |
|
1 |
|
删除套餐
需求分析
- 套餐管理列表页面点击删除按钮,可以删除对应的套餐信息
- 可以通过复选框选择多个套餐,选择批量删除一次性删除多个套餐
梳理交互过程
- 删除单个套餐时,页面发送ajax请求,根据套餐id删除对应套餐
- 删除多个套餐时,页面发送ajax请求,根据提交的多个套餐id删除对应套餐开发删除套餐功能

- 请求网址: http://localhost/setmeal?ids=1579044544635232258,1415580119015145474
- 请求方法: DELETE
代码开发
在
SetmealController中添加delete方法1
2
3
4
5
6
public Result<String> deleteByIds( List<Long> ids) {
log.info("要删除的套餐id为:{}",ids);
setmealService.removeWithDish(ids);
return Result.success("删除成功");
}在
SetmealService中创建removeWithDish方法1
void removeWithDish(List<Long> ids);
在
SetmealServiceImpl中重写方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void removeWithDish(List<Long> ids) {
//先判断一下能不能删,如果status为1,则套餐在售,不能删
//select * from setmeal where id in (ids) and status = 1
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.in(Setmeal::getId, ids);
setmealLambdaQueryWrapper.eq(Setmeal::getStatus, 1);
int count = this.count(setmealLambdaQueryWrapper);
//下面两行是我debug输出的日志,没啥用
List<Setmeal> list = this.list(setmealLambdaQueryWrapper);
log.info("查询到的数据为:{}",list);
if (count > 0) {
throw new CustomException("套餐正在售卖中,请先停售再进行删除");
}
//如果没有在售套餐,则直接删除
this.removeByIds(ids);
//继续删除
LambdaQueryWrapper<SetmealDish> setmealDishLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealDishLambdaQueryWrapper.in(SetmealDish::getSetmealId, ids);
setmealDishService.remove(setmealDishLambdaQueryWrapper);
}重启服务器,并测试
邮件发送(替换手机验证)
该处黑马使用的是阿里云短信验证码,但是使用该功能需要一定的费用,且需要申请签名等流程,非常繁琐,所以这里使用别的大佬给的QQ邮箱验证码的方式来完成

验证码登录的优点: - 方便快捷,无需注册,直接登录 - 使用短信验证码作为登录凭证,无需记忆密码 - 安全 - 登录流程: - 输入手机号(邮箱) > 获取验证码 > 输入验证码 > 点击登录 > 登录成功 ### 数据模型 此处直接将邮箱号码存到phone字段中(如果不嫌麻烦也可以另外创建一个字段来存邮箱) {% image https://raw.githubusercontent.com/silvan2077/markdown_pic/main/Picgo/20251105210804.png)
准备工作
先将要用到的类和接口的基本结构都创建好
- 实体类User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/**
* 用户信息
*/
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;
}
1 |
|
1 | public interface UserService extends IService<User> { |
1 |
|
控制层UserController
1
2
3
4
5
6
7
8
public class UserController {
private UserService userService;
}工具类(我们自己造自己的邮箱工具类)
- 首先导入坐标
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency> - 添加配置文件
1
2
3
4
5
6
7
8
9
10
11
12spring:
mail:
password: tzunlrdblmhvijce
username: 1330132229@qq.com
host: smtp.qq.com
port: 465 # 必须是 465 或 587
properties:
mail:
smtp:
auth: true
ssl:
enable: true
- 首先导入坐标
然后编写一个工具类,用于发送邮件验证码和随机生成验证码
1 |
|
修改拦截器
对用户登录操作放行
1
2
3
4
5
6
7
8
9
10
11//定义不需要处理的请求
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
//对用户登陆操作放行
"/user/login",
"/user/sendMsg"
};判断用户是否登录(移动端)
1
2
3
4
5
6
7
8//判断用户是否登录
if(request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
Long userId = (Long)request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request,response);
return;
}
发送验证码
- 重新导入完资源之后,清除浏览器缓存(或使用无痕浏览),并重启服务器,访问登录页面,获取验证码,这下应该是能收到请求的

从上图中可以看到,发送验证码的请求方式是POST,路径为
/user/sendMsg- 请求方式:
POST - 请求路径:
/user/sendMsg
- 请求方式:
那么我们在UserController控制层中,添加sendMsg方法,用来接收指令发送邮件
1 |
|
- 此时邮箱中就已经能接收到验证码了

输入验证码,点击登录
- 请求路径为:
/user/login,数据以json格式返回给服务端
- 请求路径为:
在UserController控制层中,添加
login方法,并使用日志输出一下,看看能否接受到数据1
2
3
4
5
public Result<String> login( Map map,HttpSession session){
log.info(map.toString());
return null;
}com.blog.controller.UserController : {phone=1586385296@qq.com, code=bxQCK}
看样子是可以获取到数据的,那么我们继续完善login方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Result<User> login( Map map, HttpSession session) {
log.info(map.toString());
//获取邮箱
String phone = map.get("phone").toString();
//获取验证码
String code = map.get("code").toString();
//从session中获取验证码
String codeInSession = session.getAttribute(phone).toString();
//比较这用户输入的验证码和session中存的验证码是否一致
if (code != null && code.equals(codeInSession)) {
//如果输入正确,判断一下当前用户是否存在
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//判断依据是从数据库中查询是否有其邮箱
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);
//如果不存在,则创建一个,存入数据库
if (user == null) {
user = new User();
user.setPhone(phone);
userService.save(user);
user.setName("用户" + codeInSession);
}
//存个session,表示登录状态
session.setAttribute("user",user.getId());
//并将其作为结果返回
return Result.success(user);
}
return Result.error("登录失败");
}此时输入验证码就能完成登录了。

地址簿
需求分析
- 地址簿,指的是移动端消费者用户的地址信息(外卖快递的收货地址)
- 用户登录成功后可以维护自己的地址信息(自己修改删除新增等)
- 同一个用户可以有多个地址信息,但是只能有一个默认地址。
数据模型

准备工作
创建对应的实体类
AddressBook1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68/**
* 地址簿
*/
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;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
//创建人
private Long createUser;
//修改人
private Long updateUser;
//是否删除
private Integer isDeleted;
}Mapper接口AddressBookMapper1
2
3
public interface AddressBookMapper extends BaseMapper<AddressBook> {
}业务层接口
AddressBookService1
2public interface AddressBookService extends IService<AddressBook> {
}业务层实现类
AddressBookServicelmpl1
2
3
public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {
}控制层
AddressBookController1
2
3
4
5
6
7
8
public class AddressBookController {
private AddressBookService addressBookService;
}
完善地址管理页面
- 点击头像下的地址管理,查看请求方式与地址


在
AddressBookController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
public Result<List<AddressBook>> list(AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook={}", addressBook);
//条件构造器
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(addressBook.getUserId() != null, AddressBook::getUserId, addressBook.getUserId());
queryWrapper.orderByDesc(AddressBook::getUpdateTime);
List<AddressBook> addressBooks = addressBookService.list(queryWrapper);
return Result.success(addressBooks);
}不过写完了暂时还是不能看到效果的,数据库中并没有添加对应账号的数据,所以我们继续来做新增收货地址功能
新增收货地址
修改前端代码
这段代码是新增地址的前端代码,将其中的手机号全部替换成邮箱,判断手机号的正则也换成判断邮箱的正则,也可以直接复制这段代码更换掉front/page/address-edit.html文件中的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<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, maximum-scale=1.0,user-scalable=no,minimal-ui">
<title>菩提阁</title>
<link rel="icon" href="./../images/favico.ico">
<!--不同屏幕尺寸根字体设置-->
<script src="./../js/base.js"></script>
<!--element-ui的样式-->
<link rel="stylesheet" href="../../backend/plugins/element-ui/index.css" />
<!--引入vant样式-->
<link rel="stylesheet" href="../styles/vant.min.css"/>
<!-- 引入样式 -->
<link rel="stylesheet" href="../styles/index.css" />
<!--本页面内容的样式-->
<link rel="stylesheet" href="./../styles/address-edit.css" />
</head>
<body>
<div id="address_edit" class="app">
<div class="divHead">
<div class="divTitle">
<i class="el-icon-arrow-left" @click="goBack"></i>{{title}}
</div>
</div>
<div class="divContent">
<div class="divItem">
<span>联系人:</span>
<el-input placeholder=" 请填写收货人的姓名" v-model="form.consignee" maxlength='10' class="inputUser"/></el-input>
<span class="spanChecked" @click="form.sex = '1'">
<i :class="{iActive:form.sex === '1'}"></i>
先生
</span>
<span class="spanChecked" @click="form.sex = '0'">
<i :class="{iActive:form.sex === '0'}"></i>
女士
</span>
</div>
<div class="divItem">
<span>邮箱:</span>
<el-input placeholder=" 请填写收货人邮箱" v-model="form.phone" maxlength='20' style="width: calc(100% - 80rem);"/></el-input>
</div>
<div class="divItem">
<span>收货地址:</span>
<el-input placeholder=" 请输入收货地址" v-model="form.detail" maxlength='140'/></el-input>
</div>
<div class="divItem ">
<span>标签:</span>
<span v-for="(item,index) in labelList" :key="index" @click="form.label = item;activeIndex = index" :class="{spanItem:true,spanActiveSchool:activeIndex === index}">{{item}}</span>
</div>
<div class="divSave" @click="saveAddress">保存地址</div>
<div class="divDelete" @click="deleteAddress" v-if="id">删除地址</div>
</div>
</div>
<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="../../backend/plugins/vue/vue.js"></script>
<!-- 引入组件库 -->
<script src="../../backend/plugins/element-ui/index.js"></script>
<!-- 引入vant样式 -->
<script src="./../js/vant.min.js"></script>
<script src="./../js/common.js"></script>
<script src="./../api/address.js"></script>
<!-- 引入axios -->
<script src="../../backend/plugins/axios/axios.min.js"></script>
<script src="./../js/request.js"></script>
<script>
new Vue({
el:"#address_edit",
data(){
return {
title:'新增收货地址',
form:{
consignee:'',//联系人
phone:undefined,//手机号
sex:'1',//0表示女 1 表示男
detail:'',//收货地址
label:'公司',//标签
},
labelList:[
'无','公司','家','学校'
],
id:undefined,
activeIndex :0
}
},
computed:{},
created(){
this.initData()
},
mounted(){
},
methods:{
goBack(){
history.go(-1)
},
async initData(){
const params = parseUrl(window.location.search)
this.id = params.id
if(params.id){
this.title = '编辑收货地址'
const res = await addressFindOneApi(params.id)
if(res.code === 1){
this.form = res.data
}else{
this.$notify({ type:'warning', message:res.msg});
}
}
},
async saveAddress(){
const form = this.form
if(!form.consignee){
this.$notify({ type:'warning', message:'请输入联系人'});
return
}
if(!form.phone){
this.$notify({ type:'warning', message:'请输入邮箱'});
return
}
if(!form.detail){
this.$notify({ type:'warning', message:'请输入收货地址'});
return
}
const reg = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/
if(!reg.test(form.phone)){
this.$notify({ type:'warning', message:'邮箱不合法'});
return
}
let res= {}
if(this.id){
res = await updateAddressApi(this.form)
}else{
res = await addAddressApi(this.form)
}
if(res.code === 1){
window.requestAnimationFrame(()=>{
window.location.replace('/front/page/address.html')
})
}else{
this.$notify({ type:'warning', message:res.msg});
}
},
deleteAddress(){
this.$dialog.confirm({
title: '确认删除',
message: '确认要删除当前地址吗?',
})
.then( async () => {
const res = await deleteAddressApi({ids:this.id })
if(res.code === 1){
window.requestAnimationFrame(()=>{
window.location.replace('/front/page/address.html')
})
}else{
this.$notify({ type:'warning', message:res.msg});
}
})
.catch(() => {
});
},
}
})
</script>
</body>
</html>填写表单,点击保存,发送请求
请求网址: http://localhost/addressBook
请求方法: POST
请求路径是
/addressBook,请求方式为POST,那么我们在AddressBookController中编写对应的方法1
2
3
4
5
6
7
public Result<AddressBook> addAddress( AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return Result.success(addressBook);
}添加完地址后的效果

设置默认地址
- 先来想想怎么设置默认地址
- 默认地址,按理说数据库中,有且仅有一条数据为默认地址,也就是
is_default字段为1
- 默认地址,按理说数据库中,有且仅有一条数据为默认地址,也就是
如何保证整个表中的
is_default字段只有一条为1- 每次设置默认地址的时候,将当前用户所有地址的
is_default字段设为0,随后将当前地址的is_default字段设为1
- 每次设置默认地址的时候,将当前用户所有地址的
当我们点击上图的设为默认按钮的时候,会发送请求
请求网址: http://localhost/addressBook/default
请求方法: PUT请求路径为
/addressBook/default,请求方式为PUT,现在在AddressBookController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Result<AddressBook> setDefaultAddress( AddressBook addressBook) {
//获取当前用户id
addressBook.setUserId(BaseContext.getCurrentId());
//条件构造器
LambdaUpdateWrapper<AddressBook> queryWrapper = new LambdaUpdateWrapper<>();
//条件:当前用户的地址
queryWrapper.eq(addressBook.getUserId() != null, AddressBook::getUserId, addressBook.getUserId());
//将当前用户地址的is_default字段全部设为0
queryWrapper.set(AddressBook::getIsDefault, 0);
//执行更新操作
addressBookService.update(queryWrapper);
//随后再将当前地址的is_default字段设为1
addressBook.setIsDefault(1);
//再次执行更新操作
addressBookService.updateById(addressBook);
return Result.success(addressBook);
}
菜品展示
需求分析
- 用户登陆成功之后,跳转到菜品页面,根据菜品分类来展示菜品和套餐
- 如果菜品设置了口味信息,则需要展示选择规格按钮,否则只展示+按钮(这部分前端来实现)
梳理交互过程
- 页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)
- 页面发送ajax请求,根据具体的菜品/套餐分类,展示对应分类中的具体菜品
前端分析
启动服务器,登录账号,看到登录到首页会发送两个请求
- 分类
请求网址:
http://localhost/category/list
请求方法: GET - 购物车
请求网址:
http://localhost/shoppingCart/list
请求方法: GET
- 分类
其中分类请求之前就写过了,且返回的状态码是200,但是页面并没有显示出来,看看前端代码来寻找原因:
Promise.all在处理多个异步请求时,需要等待绑定的每个ajax请求返回数据以后才能正常显示
虽然categoryListApi可以正常返回数据,但是cartListApi还不能,因为现在才正要开始处理,因此导致菜品数据没有正常显示
1 | //初始化数据 |
该请求路径之前写过,能够正常返回
1 | //获取所有的菜品分类 |
1 |
|
这里是因为购物车数据还没有写,因此应当先将url设置为一个空的json文件,先让页面正常显示
1 | //获取购物车内商品的集合 |
1 | {"code":1,"msg":null,"data":[],"map":{}} |
- 此时再次重启服务器,打开无痕模式,看看首页是否能显示分类数据

现在还存在一个问题,我们的菜品是有口味数据的,那么这里的按钮不该是一个+,而应该是选择规格
1
2<div class="divTypes" v-if="detailsDialog.item.flavors && detailsDialog.item.flavors.length > 0 && !detailsDialog.item.number "
@click ="chooseFlavorClick(detailsDialog.item)">选择规格</div>通过前端代码可以分析得出,根据服务器返回的结果是否有flavors字段来决定,但此时在后端返回的是
List<Dish>,其中并没有flavors属性,所以应该修改为返回DishDto。选择规格
修改原本的list方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public Result<List<DishDto>> get(Dish dish) {
//条件查询器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//根据传进来的categoryId查询
queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
//只查询状态为1的菜品(在售菜品)
queryWrapper.eq(Dish::getStatus, 1);
//简单排下序,其实也没啥太大作用
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
//获取查询到的结果作为返回值
List<Dish> list = dishService.list(queryWrapper);
log.info("查询到的菜品信息list:{}",list);
//item就是list中的每一条数据,相当于遍历了
List<DishDto> dishDtoList = list.stream().map((item) -> {
//创建一个dishDto对象
DishDto dishDto = new DishDto();
//将item的属性全都copy到dishDto里
BeanUtils.copyProperties(item, dishDto);
//由于dish表中没有categoryName属性,只存了categoryId
Long categoryId = item.getCategoryId();
//所以我们要根据categoryId查询对应的category
Category category = categoryService.getById(categoryId);
if (category != null) {
//然后取出categoryName,赋值给dishDto
dishDto.setCategoryName(category.getName());
}
//然后获取一下菜品id,根据菜品id去dishFlavor表中查询对应的口味,并赋值给dishDto
Long itemId = item.getId();
//条件构造器
LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
//条件就是菜品id
lambdaQueryWrapper.eq(itemId != null, DishFlavor::getDishId, itemId);
//根据菜品id,查询到菜品口味
List<DishFlavor> flavors = dishFlavorService.list(lambdaQueryWrapper);
//赋给dishDto的对应属性
dishDto.setFlavors(flavors);
//并将dishDto作为结果返回
return dishDto;
//将所有返回结果收集起来,封装成List
}).collect(Collectors.toList());
return Result.success(dishDtoList);
}至此,菜品展示功能就做好了
套餐展示
菜品展示和套餐展示类似,但用的不是同一个controller,因此还需要再设置一遍
请求网址: http://localhost/setmeal/list?categoryId=1413342269393674242&status=1
请求方法: GET那么我们现在就在
SetmealController中编写对应的方法
套餐没有口味数据,因此更加简单1
2
3
4
5
6
7
8
9
10
11
12
public Result<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, 1);
//排序
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
List<Setmeal> setmealList = setmealService.list(queryWrapper);
return Result.success(setmealList);
}
购物车
需求分析
- 移动端用户可以将菜品/套餐添加到购物车
- 对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车(前端实现)
- 对于套餐来说,可以直接点击当前套餐加入购物车
- 在购物车中可以修改菜品/套餐的数量,也可以清空购物车
数据模型

梳理交互过程
- 点击加入购物车按钮,页面发送ajax请求,请求服务端,将菜品/套餐添加到购物车
- 点击购物车图标,页面发送ajax请求,请求服务端,查询购物车中的菜品和套餐
- 点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作
准备工作
在开发业务功能之前,先将需要用到的类和接口的基本结构都创建好
实体类
ShoppingCart1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36/**
* 购物车
*/
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接口ShoppingCartMapper1
2
3
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {
}业务层接口
ShoppingCartService1
2public interface ShoppingCartService extends IService<ShoppingCart> {
}业务层实现类
ShoppingCartServiceImpl1
2
3
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
}控制层
ShoppingCartController1
2
3
4
5
6
7
public class ShoppingCartController {
private ShoppingCartService shoppingCartService;
}
代码开发
加入购物车
点击
加入购物车后,页面会发送请求请求网址: http://localhost/shoppingCart/add
请求方法: POST以json的形式将数据发给服务端

那么我们在
ShoppingCartController添加对应的方法
养成随时测试的习惯1
2
3
4
5
public Result<ShoppingCart> add( ShoppingCart shoppingCart){
log.info("购物车添加信息:{}",shoppingCart);
return null;
}完善业务逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public Result<ShoppingCart> add( ShoppingCart shoppingCart) {
log.info("shoppingCart={}", shoppingCart);
//获取当前用户id
Long currentId = BaseContext.getCurrentId();
//设置当前用户id
shoppingCart.setUserId(currentId);
//获取当前菜品id
Long dishId = shoppingCart.getDishId();
//条件构造器
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
//判断添加的是菜品还是套餐
if (dishId != null) {
queryWrapper.eq(ShoppingCart::getDishId, dishId);
} else {
queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId());
}
//查询当前菜品或者套餐是否在购物车中
ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
if (cartServiceOne != null) {
//如果已存在就在当前的数量上加1
Integer number = cartServiceOne.getNumber();
cartServiceOne.setNumber(number + 1);
shoppingCartService.updateById(cartServiceOne);
} else {
//如果不存在,则还需设置一下创建时间
shoppingCart.setCreateTime(LocalDateTime.now());
//如果不存在,则添加到购物车,数量默认为1
shoppingCartService.save(shoppingCart);
//这里是为了统一结果,最后都返回cartServiceOne会比较方便
cartServiceOne = shoppingCart;
}
return Result.success(cartServiceOne);
}此时可以测试能否成功添加购物车,不过现在还不会显示,因为之前设置前端页面为死数据,现在可以去数据库查看购物车的数据是否添加上
查看购物车
之前为了页面展示不报错,直接将购物车的地址换成了一个死数据,现在购物车的功能以及完成,因此应当换回真数据
1
2
3
4
5
6
7
8
9//获取购物车内商品的集合
function cartListApi(data) {
return $axios({
// 'url': '/shoppingCart/list',
'url': '/front/cartData.json',
'method': 'get',
params: {...data}
})
}
请求路径:http://localhost/shoppingCart/list
请求方式:GET
在
ShoppingCartController中添加对应的方法1
2
3
4
5
6
7
8
public Result<List<ShoppingCart>> list() {
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
Long userId = BaseContext.getCurrentId();
queryWrapper.eq(ShoppingCart::getUserId, userId);
List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);
return Result.success(shoppingCarts);
}
- 此时就可以添加菜品/套餐到购物车中了,不过现在还不能完成购物车的其他功能,比如减少购物车中的数量,删除购物车中的商品等
清空购物车
点击清空按钮,发现此时又发送了请求
请求网址: http://localhost/shoppingCart/clean
请求方法: DELETE清空购物车比较简单,只需要获取用户id,然后去
shopping__cart表中删除对应id的数据即可
在ShoppingCartController中编写对应的方法
1 |
|
用户下单
需求分析
- 移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的去结算按钮,页面跳转到订单确认页面,点击去支付按钮,完成下单操作
数据模型
户下单业务对应的数据表为orders表和order_detail表


梳理交互过程
- 在购物车中点击去结算按钮,页面跳转到订单确认页面
- 在订单确认页面中,发送ajax请求,请求服务端,获取当前登录用户的默认地址
- 在订单确认页面,发送ajax请求,请求服务端,获取当前登录用户的购物车数据
- 在订单确认页面点击去支付按钮,发送ajax请求,请求服务端,完成下单操作
准备工作
- 实体类
Orders和OrderDetail1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54/**
* 订单
*/
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;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38/**
* 订单明细
*/
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接口
OrdersMapper和OrderDetailMapper1
2
3
public interface OrderMapper extends BaseMapper<Orders> {
}1
2
3
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
} - Service接口
OrdersService和OrderDetailService1
2public interface OrderService extends IService<Orders> {
}1
2public interface OrderDetailService extends IService<OrderDetail> {
} - Service实现类
OrdersServiceImpl和OrderDetailServiceImpl1
2
3
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
}1
2
3
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {
} - Controller接口
OrdersController和OrderDetailController1
2
3
4
5
6
7
public class OrderController {
private OrderService orderService;
}1
2
3
4
5
6
7
public class OrderDetailController {
private OrderDetailService orderDetailService;
}
前端分析
点击去结算按钮,然后查看发送的请求url和方式
请求网址: http://localhost/addressBook/default
请求方法: GET页面跳转到确认订单页面,发送ajax请求,用于获取用户的默认地址,但是请求失败,服务端没有对应的映射
根据请求路径
/addressBook/default,请求方式GET,进入到AddressBookController编写响应方法1
2
3
4
5
6
7
8
9
10
11
12
13
public Result<AddressBook> defaultAddress() {
//获取当前用户id
Long userId = BaseContext.getCurrentId();
//条件构造器
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
//当前用户
queryWrapper.eq(userId != null, AddressBook::getUserId, userId);
//默认地址
queryWrapper.eq(AddressBook::getIsDefault, 1);
AddressBook addressBook = addressBookService.getOne(queryWrapper);
return Result.success(addressBook);
}重启服务器,再次点击按钮就能看到地址了

结算
继续点击去支付,查看发送的请求url与请求方式
请求网址: http://localhost/order/submit
请求方法: POST提交给服务端的数据格式为JSON

- 请求路径
/order/submit,请求方式POST,现在去OrderController中开发对应的功能
具体的submit方法放在OrderService写,OrderController调用写好的submit方法即可
1 | public interface OrderService extends IService<Orders> { |
1 |
|
养成测试接收数据的习惯
1 |
|
1 | public interface OrderService extends IService<Orders> { |
此时可以测试一下是否能接收到数据
orders:Orders(id=null, number=null, status=null, userId=null, addressBookId=1986397979540123650, orderTime=null, checkoutTime=null, payMethod=1, amount=null, remark=, userName=null, phone=null, address=null, consignee=null)
编写具体的submit方法的逻辑代码,需要先分析下单功能需要获取哪些数据
- 获取当前用户id
- 根据用户id查询其购物车数据
- 根据查询到的购物车数据,对订单表插入数据(1条)
根据查询到的购物车数据,对订单明细表插入数据(多条)
下单后清空购物车数据
具体实现:
1 |
|
代码量很多,但是大部分都是赋值操作,由于购物车数据与订单数据和订单详情的重复字段不是很多,所以这里就没采用
BeanUtils.copyProperties()来复制属性,而是自己一个一个set的重启服务器,测试结算按钮,下单后就可以在数据库的
orders表中找到相关数据了
移动端补充功能
历史订单功能
每次访问个人中心/历史订单时,都会发送请求
请求网址: http://localhost/order/userPage?page=1&pageSize=1
请求方法: GET根据请求网址,看样子是个分页请求,我们之前把订单数据存进了order表中,那么该功能,大概率就是从表中查出数据然后返回给前端
直接在
OrderController中编写对应的方法在此之前,先创建一个OrderDto,传输固定的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrdersDto extends Orders {
private String userName;
private String phone;
private String address;
private String consignee;
private List<OrderDetail> orderDetails;
}分页代码跟之前的也没啥区别,反复练习更有印象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public Result<Page> page(int page, int pageSize) {
//获取当前id
Long userId = BaseContext.getCurrentId();
Page<Orders> pageInfo = new Page<>(page, pageSize);
Page<OrdersDto> ordersDtoPage = new Page<>(page, pageSize);
//条件构造器
LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
//查询当前用户id订单数据
queryWrapper.eq(userId != null, Orders::getUserId, userId);
//按时间降序排序
queryWrapper.orderByDesc(Orders::getOrderTime);
orderService.page(pageInfo, queryWrapper);
List<OrdersDto> list = pageInfo.getRecords().stream().map((item) -> {
OrdersDto ordersDto = new OrdersDto();
//获取orderId,然后根据这个id,去orderDetail表中查数据
Long orderId = item.getId();
LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderDetail::getOrderId, orderId);
List<OrderDetail> details = orderDetailService.list(wrapper);
BeanUtils.copyProperties(item, ordersDto);
//之后set一下属性
ordersDto.setOrderDetails(details);
return ordersDto;
}).collect(Collectors.toList());
BeanUtils.copyProperties(pageInfo, ordersDtoPage, "records");
ordersDtoPage.setRecords(list);
//日志输出看一下
log.info("list:{}", list);
return Result.success(ordersDtoPage);
}
登出功能
这个应该算简单的了吧,清除该用户的session记录即可,点击退出登录,请求如下
请求网址: http://localhost/user/loginout
请求方法: POST请求路径
/user/loginout,请求方式POST
所以我们应该去UserController中编写对应的方法1
2
3
4
5
public Result<String> logout(HttpServletRequest request) {
request.getSession().removeAttribute("user");
return Result.success("退出成功");
}
修改/删除地址
数据回显- 点击地址选项卡的
铅笔图案,会发送修改请求并跳转到修改地址页面请求网址: http://localhost:8080/addressBook/1986395330853879810
请求方法: GET 可以看到请求地址非常像Restful风格,因此推测请求路径大概率为
/addressBook/{id}在
AddressBookController中编写对应的方法1
2
3
4
5
6
7
8
public Result<AddressBook> getById( Long id) {
AddressBook addressBook = addressBookService.getById(id);
if (addressBook == null){
throw new CustomException("地址信息不存在");
}
return Result.success(addressBook);
}
- 点击地址选项卡的
- 此时再点击修改按钮,就可以看到该地址的数据回显到修改页面了

修改地址
点击上图中的
保存地址按钮,查看发送的请求请求网址: http://localhost/addressBook
请求方法: PUT请求方式
PUT,直接在AddressBookController中编写对应的方法1
2
3
4
5
6
7
8
public Result<String> updateAdd( AddressBook addressBook) {
if (addressBook == null) {
throw new CustomException("地址信息不存在,请刷新重试");
}
addressBookService.updateById(addressBook);
return Result.success("地址修改成功");
}
删除地址
点击上图中的
删除地址按钮,查看发送的请求请求网址: http://localhost/addressBook?ids=1579828298672885762
请求方法: DELETE在
AddressBookController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
public Result<String> deleteAdd( Long id) {
if (id == null) {
throw new CustomException("地址信息不存在,请刷新重试");
}
AddressBook addressBook = addressBookService.getById(id);
if (addressBook == null) {
throw new CustomException("地址信息不存在,请刷新重试");
}
addressBookService.removeById(id);
return Result.success("地址删除成功");
}
再来一单
- 这个功能其实比较隐晦,因为当订单状态为
已完成时才会出现这个按钮(修改orders表中的status字段为4)
前端代码
点击
再来一单,查看发送的请求请求网址: http://localhost/order/again
请求方法: POST数据只携带了一个json格式的id数据,根据常识,这个id只能是orders表中的订单id,即
order_id1
{id: "1986672088484409346"}
现在传回的数据只有一个order_id,因此要根据它去查询对应的下单信息
分析
再来一单具体实现思路(参考一下当初我们怎么添加购物车的)- 参考点外卖的经验,再来一单应当是将该订单的数据添加到购物车,并跳转到之前的下单页面
- 之前是我们手动选择数据(菜品/套餐)添加到购物车,现在相当于手里有发票,根据发票上的数据,再买一遍
来
OrderController编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Result<String> again( Map<String,String> map){
//获取order_id
Long orderId = Long.valueOf(map.get("id"));
//条件构造器
LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
//查询订单的口味细节数据
queryWrapper.eq(OrderDetail::getOrderId,orderId);
List<OrderDetail> details = orderDetailService.list(queryWrapper);
//获取用户id,待会需要set操作
Long userId = BaseContext.getCurrentId();
List<ShoppingCart> shoppingCarts = details.stream().map((item) ->{
ShoppingCart shoppingCart = new ShoppingCart();
//Copy对应属性值
BeanUtils.copyProperties(item,shoppingCart);
//设置一下userId
shoppingCart.setUserId(userId);
//设置一下创建时间为当前时间
shoppingCart.setCreateTime(LocalDateTime.now());
return shoppingCart;
}).collect(Collectors.toList());
//加入购物车
shoppingCartService.saveBatch(shoppingCarts);
return Result.success("喜欢吃就再来一单吖~");
}
减号按钮
- 之前下单的时候,只有加号按钮能用,减号按钮还没配置,我们点击
减号,看看发送了什么请求请求网址: http://localhost/shoppingCart/sub
请求方法: POST
返回的json数据如下,只有
dishId和setmealId1
2
3
4{
dishId: null,
setmealId: "1986672088484409346"
}思路分析: 根据返回的id,来对不同的菜品/套餐的number属性修改(对应的数量-1),如果number等于0,则删除
在
ShoppingCartController中开发对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public Result<ShoppingCart> sub( ShoppingCart shoppingCart) {
Long dishId = shoppingCart.getDishId();
Long setmealId = shoppingCart.getSetmealId();
//条件构造器
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
//只查询当前用户ID的购物车
queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
//代表数量减少的是菜品数量
if (dishId != null) {
//通过dishId查出购物车菜品数据
queryWrapper.eq(ShoppingCart::getDishId, dishId);
ShoppingCart dishCart = shoppingCartService.getOne(queryWrapper);
//将查出来的数据的数量-1
dishCart.setNumber(dishCart.getNumber() - 1);
Integer currentNum = dishCart.getNumber();
//然后判断
if (currentNum > 0) {
//大于0则更新
shoppingCartService.updateById(dishCart);
} else if (currentNum == 0) {
//小于0则删除
shoppingCartService.removeById(dishCart.getId());
}
return Result.success(dishCart);
}
if (setmealId != null) {
//通过setmealId查询购物车套餐数据
queryWrapper.eq(ShoppingCart::getSetmealId, setmealId);
ShoppingCart setmealCart = shoppingCartService.getOne(queryWrapper);
//将查出来的数据的数量-1
setmealCart.setNumber(setmealCart.getNumber() - 1);
Integer currentNum = setmealCart.getNumber();
//然后判断
if (currentNum > 0) {
//大于0则更新
shoppingCartService.updateById(setmealCart);
} else if (currentNum == 0) {
//等于0则删除
shoppingCartService.removeById(setmealCart.getId());
}
return Result.success(setmealCart);
}
return Result.error("系统繁忙,请稍后再试");
}
后台系统补充功能
菜品启售/停售
点击停售按钮,查看发送的请求
请求网址: http://localhost:8080/dish/status/0?ids=1985250880349995010
请求方法: POST当前商品为启售状态,其status为1,但点击停售按钮时,发送的status为0,前端是直接对这个status取反了,我们直接用发送的这个status来更新我们的商品状态就好了,不用在后端再次进行判断
在
DishController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
public Result<String> status( Integer status, Long ids) {
log.info("status:{},ids:{}", status, ids);
Dish dish = dishService.getById(ids);
if (dish != null) {
//直接用它传进来的这个status改就行
dish.setStatus(status);
dishService.updateById(dish);
return Result.success("售卖状态修改成功");
}
return Result.error("系统繁忙,请稍后再试");
}
菜品批量启售/停售
这个其实就是传进来了一个ids的数组,我们在上面的方法上稍作修改就好了,直接用
LambdaUpdateWrapper更方便1
2
3
4
5
6
7
8
9
public Result<String> status( Integer status, List<Long> ids) {
log.info("status:{},ids:{}", status, ids);
LambdaUpdateWrapper<Dish> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(ids != null, Dish::getId, ids);
updateWrapper.set(Dish::getStatus, status);
dishService.update(updateWrapper);
return Result.success("批量操作成功");
}
菜品批量删除
删除跟批量删除应该也是同一个操作,点击删除按钮,查看请求
请求网址: http://localhost:8080/dish?ids=1985250880349995010,1413384757047271425
请求方法: DELETE按理说,这里应该是逻辑删除,表中有一个字段为
is_delete,但是要按逻辑删除的话,还得改前面的list和page代码,因为查询的时候,没涉及到逻辑删除,模型类中也没有isDelete属性所以这里为了省事选择直接删除,但如果是逻辑删除,执行的是update,将逻辑删除字段设为1表示逻辑删除,查询的时候只查询逻辑删除字段为0的数据,表示未删除的数据
需要注意的是,如果选中的删除列表中,存在启售状态商品,则不允许删除
在
DishController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
public Result<String> delete( List<Long> ids) {
log.info("删除的ids:{}", ids);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Dish::getId, ids);
queryWrapper.eq(Dish::getStatus, 1);
long count = dishService.count(queryWrapper);
if (count > 0) {
throw new CustomException("删除列表中存在启售状态商品,无法删除");
}
dishService.removeByIds(ids);
return Result.success("删除成功");
}
套餐批量启售/停售
点击批量停售按钮,查看发送的请求
请求网址: http://localhost:8080/setmeal/status/0?ids=1986422148185178113,1415580119015145474
请求方法: POST跟之前的菜品批量启售/停售没有太大区别
1
2
3
4
5
6
7
8
public Result<String> status( String status, List<Long> ids) {
LambdaUpdateWrapper<Setmeal> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.in(Setmeal::getId, ids);
updateWrapper.set(Setmeal::getStatus, status);
setmealService.update(updateWrapper);
return Result.success("批量操作成功");
}
套餐修改
数据回显点击修改按钮,查看发送的请求
请求网址: http://localhost:8080/setmeal/1986422148185178113
请求方法: GET这个请求大概率是用于处理数据回显的,请求路径
/setmeal/{setmealId},请求方式GET普通的
Setmeal实体类肯定是不够用的,因此要使用SetmealDto在
SetmealController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Result<SetmealDto> getById( Long id) {
Setmeal setmeal = setmealService.getById(id);
SetmealDto setmealDto = new SetmealDto();
//拷贝数据
BeanUtils.copyProperties(setmeal, setmealDto);
//条件构造器
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
//根据setmealId查询具体的setmealDish
queryWrapper.eq(SetmealDish::getSetmealId, id);
List<SetmealDish> setmealDishes = setmealDishService.list(queryWrapper);
//然后再设置属性
setmealDto.setSetmealDishes(setmealDishes);
//作为结果返回
return Result.success(setmealDto);
}
套餐修改点击保存按钮,查看发送的请求
请求网址: http://localhost/setmeal
请求方法: PUT携带的数据如下

请求路径
/setmeal,请求方式PUT在
SetmealController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Result<Setmeal> updateWithDish( SetmealDto setmealDto) {
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
Long setmealId = setmealDto.getId();
//先根据id把setmealDish表中对应套餐的数据删了
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmealId);
setmealDishService.remove(queryWrapper);
//然后在重新添加
setmealDishes = setmealDishes.stream().map((item) ->{
//这属性没有,需要我们手动设置一下
item.setSetmealId(setmealId);
return item;
}).collect(Collectors.toList());
//更新套餐数据
setmealService.updateById(setmealDto);
//更新套餐对应菜品数据
setmealDishService.saveBatch(setmealDishes);
return Result.success(setmealDto);
}
订单明细
点击订单明细,输入查询条件,查看发送的请求
请求网址: http://localhost:8080/order/page?page=1&pageSize=10&number=111&beginTime=2025-11-01%2000%3A00%3A00&endTime=2025-12-01%2023%3A59%3A59
请求方法: GET在前面移动端的历史订单功能,其实跟这个差不多,稍作修改即可
主要是删除了按当前userId查询,新增了按订单号和事件段查询
1 |
|
历史订单是只查询指定用户的数据,那我们后台这里,查询所有的用户数据就行,也就不用指定userId
但是需要判断输入的订单号和时间段,这个要写动态SQL,不过我们可以用MP来帮我们完成
1 |
|
最终效果如下,输入时间段/订单号也能正常查询

关于用户名字段为null,去修改前端代码
/backend/order/list.html,找到用户,将userName改成consignee就好了,如果还不显示,清除浏览器缓存再刷新重试1
2<!--<el-table-column prop="userName" label="用户"></el-table-column>-->
<el-table-column prop="consignee" label="用户"></el-table-column>
修改订单状态
点击上图中的
派送按钮,查看发送的请求请求网址: http://localhost/order
请求方法: PUT携带的json数据
1
2
3
4{
status: 3,
id: "1986674402569883649"
}携带的status为3,那该按钮的作用应该是将订单状态设置为传入的status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17switch(row.status){
case 1:
str = '待付款'
break;
case 2:
str = '正在派送'
break;
case 3:
str = '已派送'
break;
case 4:
str = '已完成'
break;
case 5:
str = '已取消'
break;
}在
OrderController中编写对应的方法1
2
3
4
5
6
7
8
9
10
11
public Result<String> changeStatus( Map<String, String> map) {
int status = Integer.parseInt(map.get("status"));
Long orderId = Long.valueOf(map.get("id"));
log.info("修改订单状态:status={status},id={id}", status, orderId);
LambdaUpdateWrapper<Orders> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Orders::getId, orderId);
updateWrapper.set(Orders::getStatus, status);
orderService.update(updateWrapper);
return Result.success("订单状态修改成功");
}
那么至此,应该是把页面里的所有功能都实现了



