• 瑞吉外卖项目开发文档1——基础功能


     导航:

    【黑马Java笔记+踩坑汇总】JavaSE+JavaWeb+SSM+SpringBoot+瑞吉外卖+SpringCloud/SpringCloudAlibaba+黑马旅游+谷粒商城

    源码/资料:

    https://wwmg.lanzouk.com/iOufB135ydwj

    目录

    1 项目介绍

    1.1 技术选型

    1.2 功能架构

    1.3 项目预览 

    2 数据库建库建表

    3 开发环境

    3.1 Maven搭建

    3.2 启动测试

    3.3 导入前端页面

    4 后台管理端-账户操作

    4.1 登陆功能

    4.1.1实体类和结果类

    4.1.2 dao,service,controller

    4.1.3 拦截页面登陆(添加过滤器,未登录状态自动跳转登录页面)

    4.2 新增员工

    4.3 全局异常拦截处理

    4.4 员工信息分页查询

    4.4.1 接口分析

    4.4.2 Mybatis-plus实现分页

    4.5 启用禁用员工账号,js主键丢失精度问题

    4.6 修改员工窗口回显信息

    4.7 公共字段自动填充,@TableField的fill

    5 分类操作

    5.1 新增分类

    5.1.1分析

    5.1.2 category实体类

    5.1.3 dao和service

    5.1.4 分类信息

    5.2 分类页面,分页查询

    5.3 删除分类

    5.3.1 基础删除,不检查关联的菜品

    5.3.2 菜品和套餐的实体类,别忘了@JsonFormat

    5.3.3 菜品和套餐的dao和service

    5.3.4 检查后删除,业务异常捕获

    5.4 修改分类

    6 菜品操作

    6.1 文件上传和下载

    6.1.1 文件上传

    6.1.2 文件下载,回显上传的图片

    6.2 新增菜品

    6.2.1 分析

    6.2.2 菜品味道实体类,dao和service

    6.2.3 根据条件查询分类数据

    6.2.4 创建DTO类封装表单数据

    6.2.5 新增菜品,DishController

    6.3 菜品信息分页展示

    6.4 修改菜品

    6.4.1 修改数据回显

    6.4.2 修改菜品

    7 套餐操作

    7.1 环境准备

    7.1.1 数据模型

    7.1.2 Setmeal,SetmealDish实体类和数据传输对象SetmealDto

    7.1.3 套餐和套菜关系的dao和Service

    7.2 新增套餐

    7.2.1 根据分类id查询DishDto,DishController

    7.2.2 保存数据,SetmealController

    7.3 套餐信息分页展示

    7.4 批量删除套餐

    7.5 回顾区别@RequestBody、@RequestParam、@PathVariable

    8 验证码功能

    8.1 腾讯云短信服务

    8.1.1 介绍

    8.1.2 步骤

    8.2 手机验证码登录

    8.2.1 数据模型

    8.2.2 环境准备(user实体类、dao、Service、工具类)

    8.2.3 修改登录拦截过滤器

    8.2.4 模拟发送验证码,UserController

    8.2.5 前端登录

    9 移动端功能

    9.1 用户地址簿增删改查

    9.1.1 分析

    9.1.2 AddressBook 实体类、dao、Service

    9.1.3 controller

    9.2 菜品展示

    9.2.1 前端购物车请求设为假数据

    9.2.2 套餐和菜品按分类展示

    9.3 购物车

    9.3.1 分析

    9.3.2 ShoppingCart实体类、dao和Service

    9.3.2 添加购物车

    9.3.3 查看购物车

    9.3.4 购物车减少菜品数量

    9.3.5 清空购物车

    9.4 下单

    9.4.1 分析

    9.4.2 Order,OrderDetail实体类、dao、Service、controller

    9.4.3 提交订单


    1 项目介绍

    1.1 技术选型

    img

    Spring Session:

    可以说是目前非常完美的 session 共享解决方案。 它提供一组 API 和实现, 用于管理用户的 session 信息.它把 servlet 容器实现的 httpSession 替换为 spring-session, 专注于解决 session 管理问题, Session 信息存储在 Redis 中, 可简单快速且无缝的集成到我们的应用中。

    Swagger

    是一套规范,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。可用于:1.接口的文档在线自动生成、2.功能测试、3.前后端分离。

    1.2 功能架构

    img

    在这里插入图片描述

    1.3 项目预览 

    后台管理端:

     

    用户移动端:

     

     

    2 数据库建库建表

    创建一个名为reggie的数据库:

    CREATE DATABASE reggie CHARACTER SET utf8mb4;
    

    编码utf8mb4:

    MySQL在5.5.3之后增加了这个utf8mb4的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode。utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。

    MySQL的“utf8”实际上不是真正的UTF-8。

    “utf8”只支持每个字符最多三个字节,而真正的UTF-8是每个字符最多四个字节,所以尽量在mysql中用utf8mb4

    导入表:

    在这里插入图片描述

    可以用Navicat导入sql文件,也可以命令行:

    img

    1. /*
    2. Navicat MySQL Data Transfer
    3. Source Server : localhost
    4. Source Server Version : 50728
    5. Source Host : localhost:3306
    6. Source Database : reggie
    7. Target Server Type : MYSQL
    8. Target Server Version : 50728
    9. File Encoding : 65001
    10. Date: 2021-07-23 10:41:41
    11. */
    12. SET FOREIGN_KEY_CHECKS=0;
    13. -- ----------------------------
    14. -- Table structure for address_book
    15. -- ----------------------------
    16. DROP TABLE IF EXISTS `address_book`;
    17. CREATE TABLE `address_book` (
    18. `id` bigint(20) NOT NULL COMMENT '主键',
    19. `user_id` bigint(20) NOT NULL COMMENT '用户id',
    20. `consignee` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '收货人',
    21. `sex` tinyint(4) NOT NULL COMMENT '性别 0 女 1 男',
    22. `phone` varchar(11) COLLATE utf8_bin NOT NULL COMMENT '手机号',
    23. `province_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '省级区划编号',
    24. `province_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '省级名称',
    25. `city_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '市级区划编号',
    26. `city_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '市级名称',
    27. `district_code` varchar(12) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '区级区划编号',
    28. `district_name` varchar(32) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '区级名称',
    29. `detail` varchar(200) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '详细地址',
    30. `label` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '标签',
    31. `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '默认 0 否 1是',
    32. `create_time` datetime NOT NULL COMMENT '创建时间',
    33. `update_time` datetime NOT NULL COMMENT '更新时间',
    34. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    35. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    36. `is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
    37. PRIMARY KEY (`id`) USING BTREE
    38. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='地址管理';
    39. -- ----------------------------
    40. -- Records of address_book
    41. -- ----------------------------
    42. 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');
    43. 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');
    44. -- ----------------------------
    45. -- Table structure for category
    46. -- ----------------------------
    47. DROP TABLE IF EXISTS `category`;
    48. CREATE TABLE `category` (
    49. `id` bigint(20) NOT NULL COMMENT '主键',
    50. `type` int(11) DEFAULT NULL COMMENT '类型 1 菜品分类 2 套餐分类',
    51. `name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '分类名称',
    52. `sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
    53. `create_time` datetime NOT NULL COMMENT '创建时间',
    54. `update_time` datetime NOT NULL COMMENT '更新时间',
    55. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    56. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    57. PRIMARY KEY (`id`) USING BTREE,
    58. UNIQUE KEY `idx_category_name` (`name`)
    59. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
    60. -- ----------------------------
    61. -- Records of category
    62. -- ----------------------------
    63. INSERT INTO `category` VALUES ('1397844263642378242', '1', '湘菜', '1', '2021-05-27 09:16:58', '2021-07-15 20:25:23', '1', '1');
    64. INSERT INTO `category` VALUES ('1397844303408574465', '1', '川菜', '2', '2021-05-27 09:17:07', '2021-06-02 14:27:22', '1', '1');
    65. INSERT INTO `category` VALUES ('1397844391040167938', '1', '粤菜', '3', '2021-05-27 09:17:28', '2021-07-09 14:37:13', '1', '1');
    66. INSERT INTO `category` VALUES ('1413341197421846529', '1', '饮品', '11', '2021-07-09 11:36:15', '2021-07-09 14:39:15', '1', '1');
    67. INSERT INTO `category` VALUES ('1413342269393674242', '2', '商务套餐', '5', '2021-07-09 11:40:30', '2021-07-09 14:43:45', '1', '1');
    68. INSERT INTO `category` VALUES ('1413384954989060097', '1', '主食', '12', '2021-07-09 14:30:07', '2021-07-09 14:39:19', '1', '1');
    69. INSERT INTO `category` VALUES ('1413386191767674881', '2', '儿童套餐', '6', '2021-07-09 14:35:02', '2021-07-09 14:39:05', '1', '1');
    70. -- ----------------------------
    71. -- Table structure for dish
    72. -- ----------------------------
    73. DROP TABLE IF EXISTS `dish`;
    74. CREATE TABLE `dish` (
    75. `id` bigint(20) NOT NULL COMMENT '主键',
    76. `name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '菜品名称',
    77. `category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
    78. `price` decimal(10,2) DEFAULT NULL COMMENT '菜品价格',
    79. `code` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '商品码',
    80. `image` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '图片',
    81. `description` varchar(400) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
    82. `status` int(11) NOT NULL DEFAULT '1' COMMENT '0 停售 1 起售',
    83. `sort` int(11) NOT NULL DEFAULT '0' COMMENT '顺序',
    84. `create_time` datetime NOT NULL COMMENT '创建时间',
    85. `update_time` datetime NOT NULL COMMENT '更新时间',
    86. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    87. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    88. `is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
    89. PRIMARY KEY (`id`) USING BTREE,
    90. UNIQUE KEY `idx_dish_name` (`name`)
    91. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
    92. -- ----------------------------
    93. -- Records of dish
    94. -- ----------------------------
    95. 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');
    96. 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');
    97. 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');
    98. 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');
    99. 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');
    100. 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');
    101. 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');
    102. 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');
    103. 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');
    104. 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');
    105. 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');
    106. 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');
    107. 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');
    108. 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');
    109. 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');
    110. 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');
    111. 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');
    112. 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');
    113. 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');
    114. 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');
    115. 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');
    116. 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');
    117. 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');
    118. -- ----------------------------
    119. -- Table structure for dish_flavor
    120. -- ----------------------------
    121. DROP TABLE IF EXISTS `dish_flavor`;
    122. CREATE TABLE `dish_flavor` (
    123. `id` bigint(20) NOT NULL COMMENT '主键',
    124. `dish_id` bigint(20) NOT NULL COMMENT '菜品',
    125. `name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '口味名称',
    126. `value` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '口味数据list',
    127. `create_time` datetime NOT NULL COMMENT '创建时间',
    128. `update_time` datetime NOT NULL COMMENT '更新时间',
    129. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    130. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    131. `is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
    132. PRIMARY KEY (`id`) USING BTREE
    133. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品口味关系表';
    134. -- ----------------------------
    135. -- Records of dish_flavor
    136. -- ----------------------------
    137. INSERT INTO `dish_flavor` VALUES ('1397849417888346113', '1397849417854791681', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:37:27', '2021-05-27 09:37:27', '1', '1', '0');
    138. INSERT INTO `dish_flavor` VALUES ('1397849739297861633', '1397849739276890114', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:38:43', '2021-05-27 09:38:43', '1', '1', '0');
    139. INSERT INTO `dish_flavor` VALUES ('1397849739323027458', '1397849739276890114', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:38:43', '2021-05-27 09:38:43', '1', '1', '0');
    140. INSERT INTO `dish_flavor` VALUES ('1397849936421761025', '1397849936404983809', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:39:30', '2021-05-27 09:39:30', '1', '1', '0');
    141. INSERT INTO `dish_flavor` VALUES ('1397849936438538241', '1397849936404983809', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:39:30', '2021-05-27 09:39:30', '1', '1', '0');
    142. INSERT INTO `dish_flavor` VALUES ('1397850141015715841', '1397850140982161409', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:40:19', '2021-05-27 09:40:19', '1', '1', '0');
    143. INSERT INTO `dish_flavor` VALUES ('1397850141040881665', '1397850140982161409', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:40:19', '2021-05-27 09:40:19', '1', '1', '0');
    144. INSERT INTO `dish_flavor` VALUES ('1397850392120307713', '1397850392090947585', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:41:19', '2021-05-27 09:41:19', '1', '1', '0');
    145. INSERT INTO `dish_flavor` VALUES ('1397850392137084929', '1397850392090947585', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:41:19', '2021-05-27 09:41:19', '1', '1', '0');
    146. INSERT INTO `dish_flavor` VALUES ('1397850630734262274', '1397850630700707841', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:42:16', '2021-05-27 09:42:16', '1', '1', '0');
    147. INSERT INTO `dish_flavor` VALUES ('1397850630755233794', '1397850630700707841', '辣度', '[\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:42:16', '2021-05-27 09:42:16', '1', '1', '0');
    148. INSERT INTO `dish_flavor` VALUES ('1397850851274960898', '1397850851245600769', '忌口', '[\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:43:08', '2021-05-27 09:43:08', '1', '1', '0');
    149. INSERT INTO `dish_flavor` VALUES ('1397850851283349505', '1397850851245600769', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:43:08', '2021-05-27 09:43:08', '1', '1', '0');
    150. INSERT INTO `dish_flavor` VALUES ('1397851099523231745', '1397851099502260226', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:44:08', '2021-05-27 09:44:08', '1', '1', '0');
    151. INSERT INTO `dish_flavor` VALUES ('1397851099527426050', '1397851099502260226', '辣度', '[\"不辣\",\"微辣\",\"中辣\"]', '2021-05-27 09:44:08', '2021-05-27 09:44:08', '1', '1', '0');
    152. INSERT INTO `dish_flavor` VALUES ('1397851370483658754', '1397851370462687234', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
    153. INSERT INTO `dish_flavor` VALUES ('1397851370483658755', '1397851370462687234', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
    154. INSERT INTO `dish_flavor` VALUES ('1397851370483658756', '1397851370462687234', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:45:12', '2021-05-27 09:45:12', '1', '1', '0');
    155. INSERT INTO `dish_flavor` VALUES ('1397851668283437058', '1397851668262465537', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:46:23', '2021-05-27 09:46:23', '1', '1', '0');
    156. INSERT INTO `dish_flavor` VALUES ('1397852391180120065', '1397852391150759938', '忌口', '[\"不要葱\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:49:16', '2021-05-27 09:49:16', '1', '1', '0');
    157. INSERT INTO `dish_flavor` VALUES ('1397852391196897281', '1397852391150759938', '辣度', '[\"不辣\",\"微辣\",\"重辣\"]', '2021-05-27 09:49:16', '2021-05-27 09:49:16', '1', '1', '0');
    158. INSERT INTO `dish_flavor` VALUES ('1397853183307984898', '1397853183287013378', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:52:24', '2021-05-27 09:52:24', '1', '1', '0');
    159. INSERT INTO `dish_flavor` VALUES ('1397853423486414850', '1397853423461249026', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:53:22', '2021-05-27 09:53:22', '1', '1', '0');
    160. INSERT INTO `dish_flavor` VALUES ('1397853709126905857', '1397853709101740034', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:54:30', '2021-05-27 09:54:30', '1', '1', '0');
    161. INSERT INTO `dish_flavor` VALUES ('1397853890283089922', '1397853890262118402', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:55:13', '2021-05-27 09:55:13', '1', '1', '0');
    162. INSERT INTO `dish_flavor` VALUES ('1397854133632413697', '1397854133603053569', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-27 09:56:11', '2021-05-27 09:56:11', '1', '1', '0');
    163. INSERT INTO `dish_flavor` VALUES ('1397854652623007745', '1397854652581064706', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 09:58:15', '2021-05-27 09:58:15', '1', '1', '0');
    164. INSERT INTO `dish_flavor` VALUES ('1397854652635590658', '1397854652581064706', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:58:15', '2021-05-27 09:58:15', '1', '1', '0');
    165. INSERT INTO `dish_flavor` VALUES ('1397854865735593986', '1397854865672679425', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 09:59:06', '2021-05-27 09:59:06', '1', '1', '0');
    166. INSERT INTO `dish_flavor` VALUES ('1397855742303186946', '1397855742273826817', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:02:35', '2021-05-27 10:02:35', '1', '1', '0');
    167. INSERT INTO `dish_flavor` VALUES ('1397855906497605633', '1397855906468245506', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:03:14', '2021-05-27 10:03:14', '1', '1', '0');
    168. INSERT INTO `dish_flavor` VALUES ('1397856190573621250', '1397856190540066818', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:04:21', '2021-05-27 10:04:21', '1', '1', '0');
    169. INSERT INTO `dish_flavor` VALUES ('1397859056709316609', '1397859056684150785', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:15:45', '2021-05-27 10:15:45', '1', '1', '0');
    170. INSERT INTO `dish_flavor` VALUES ('1397859277837217794', '1397859277812051969', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:16:37', '2021-05-27 10:16:37', '1', '1', '0');
    171. INSERT INTO `dish_flavor` VALUES ('1397859487502086146', '1397859487476920321', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:17:27', '2021-05-27 10:17:27', '1', '1', '0');
    172. INSERT INTO `dish_flavor` VALUES ('1397859757061615618', '1397859757036449794', '甜味', '[\"无糖\",\"少糖\",\"半躺\",\"多糖\",\"全糖\"]', '2021-05-27 10:18:32', '2021-05-27 10:18:32', '1', '1', '0');
    173. INSERT INTO `dish_flavor` VALUES ('1397860242086735874', '1397860242057375745', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:20:27', '2021-05-27 10:20:27', '1', '1', '0');
    174. INSERT INTO `dish_flavor` VALUES ('1397860963918065665', '1397860963880316929', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:23:19', '2021-05-27 10:23:19', '1', '1', '0');
    175. INSERT INTO `dish_flavor` VALUES ('1397861135754506242', '1397861135733534722', '甜味', '[\"无糖\",\"少糖\",\"半躺\",\"多糖\",\"全糖\"]', '2021-05-27 10:24:00', '2021-05-27 10:24:00', '1', '1', '0');
    176. INSERT INTO `dish_flavor` VALUES ('1397861370035744769', '1397861370010578945', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-27 10:24:56', '2021-05-27 10:24:56', '1', '1', '0');
    177. INSERT INTO `dish_flavor` VALUES ('1397861683459305474', '1397861683434139649', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:26:11', '2021-05-27 10:26:11', '1', '1', '0');
    178. INSERT INTO `dish_flavor` VALUES ('1397861898467717121', '1397861898438356993', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:27:02', '2021-05-27 10:27:02', '1', '1', '0');
    179. INSERT INTO `dish_flavor` VALUES ('1397862198054268929', '1397862198033297410', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-27 10:28:14', '2021-05-27 10:28:14', '1', '1', '0');
    180. INSERT INTO `dish_flavor` VALUES ('1397862477835317250', '1397862477831122945', '辣度', '[\"不辣\",\"微辣\",\"中辣\"]', '2021-05-27 10:29:20', '2021-05-27 10:29:20', '1', '1', '0');
    181. INSERT INTO `dish_flavor` VALUES ('1398089545865015297', '1398089545676271617', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-28 01:31:38', '2021-05-28 01:31:38', '1', '1', '0');
    182. INSERT INTO `dish_flavor` VALUES ('1398089782323097601', '1398089782285348866', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:32:34', '2021-05-28 01:32:34', '1', '1', '0');
    183. INSERT INTO `dish_flavor` VALUES ('1398090003262255106', '1398090003228700673', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:33:27', '2021-05-28 01:33:27', '1', '1', '0');
    184. INSERT INTO `dish_flavor` VALUES ('1398090264554811394', '1398090264517062657', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:34:29', '2021-05-28 01:34:29', '1', '1', '0');
    185. INSERT INTO `dish_flavor` VALUES ('1398090455399837698', '1398090455324340225', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:35:14', '2021-05-28 01:35:14', '1', '1', '0');
    186. INSERT INTO `dish_flavor` VALUES ('1398090685449023490', '1398090685419663362', '温度', '[\"热饮\",\"常温\",\"去冰\",\"少冰\",\"多冰\"]', '2021-05-28 01:36:09', '2021-05-28 01:36:09', '1', '1', '0');
    187. INSERT INTO `dish_flavor` VALUES ('1398090825358422017', '1398090825329061889', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:36:43', '2021-05-28 01:36:43', '1', '1', '0');
    188. INSERT INTO `dish_flavor` VALUES ('1398091007051476993', '1398091007017922561', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:37:26', '2021-05-28 01:37:26', '1', '1', '0');
    189. INSERT INTO `dish_flavor` VALUES ('1398091296164851713', '1398091296131297281', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:38:35', '2021-05-28 01:38:35', '1', '1', '0');
    190. INSERT INTO `dish_flavor` VALUES ('1398091546531246081', '1398091546480914433', '忌口', '[\"不要葱\",\"不要蒜\",\"不要香菜\",\"不要辣\"]', '2021-05-28 01:39:35', '2021-05-28 01:39:35', '1', '1', '0');
    191. INSERT INTO `dish_flavor` VALUES ('1398091729809747969', '1398091729788776450', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:40:18', '2021-05-28 01:40:18', '1', '1', '0');
    192. INSERT INTO `dish_flavor` VALUES ('1398091889499484161', '1398091889449152513', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:40:56', '2021-05-28 01:40:56', '1', '1', '0');
    193. INSERT INTO `dish_flavor` VALUES ('1398092095179763713', '1398092095142014978', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:41:45', '2021-05-28 01:41:45', '1', '1', '0');
    194. INSERT INTO `dish_flavor` VALUES ('1398092283877306370', '1398092283847946241', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:42:30', '2021-05-28 01:42:30', '1', '1', '0');
    195. INSERT INTO `dish_flavor` VALUES ('1398094018939236354', '1398094018893099009', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:49:24', '2021-05-28 01:49:24', '1', '1', '0');
    196. INSERT INTO `dish_flavor` VALUES ('1398094391494094850', '1398094391456346113', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-05-28 01:50:53', '2021-05-28 01:50:53', '1', '1', '0');
    197. INSERT INTO `dish_flavor` VALUES ('1399574026165727233', '1399305325713600514', '辣度', '[\"不辣\",\"微辣\",\"中辣\",\"重辣\"]', '2021-06-01 03:50:25', '2021-06-01 03:50:25', '1399309715396669441', '1399309715396669441', '0');
    198. INSERT INTO `dish_flavor` VALUES ('1413389540592263169', '1413384757047271425', '温度', '[\"常温\",\"冷藏\"]', '2021-07-12 09:09:16', '2021-07-12 09:09:16', '1', '1', '0');
    199. INSERT INTO `dish_flavor` VALUES ('1413389684020682754', '1413342036832100354', '温度', '[\"常温\",\"冷藏\"]', '2021-07-09 15:12:18', '2021-07-09 15:12:18', '1', '1', '0');
    200. -- ----------------------------
    201. -- Table structure for employee
    202. -- ----------------------------
    203. DROP TABLE IF EXISTS `employee`;
    204. CREATE TABLE `employee` (
    205. `id` bigint(20) NOT NULL COMMENT '主键',
    206. `name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '姓名',
    207. `username` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户名',
    208. `password` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '密码',
    209. `phone` varchar(11) COLLATE utf8_bin NOT NULL COMMENT '手机号',
    210. `sex` varchar(2) COLLATE utf8_bin NOT NULL COMMENT '性别',
    211. `id_number` varchar(18) COLLATE utf8_bin NOT NULL COMMENT '身份证号',
    212. `status` int(11) NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
    213. `create_time` datetime NOT NULL COMMENT '创建时间',
    214. `update_time` datetime NOT NULL COMMENT '更新时间',
    215. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    216. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    217. PRIMARY KEY (`id`) USING BTREE,
    218. UNIQUE KEY `idx_username` (`username`)
    219. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';
    220. -- ----------------------------
    221. -- Records of employee
    222. -- ----------------------------
    223. 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');
    224. -- ----------------------------
    225. -- Table structure for orders
    226. -- ----------------------------
    227. DROP TABLE IF EXISTS `orders`;
    228. CREATE TABLE `orders` (
    229. `id` bigint(20) NOT NULL COMMENT '主键',
    230. `number` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '订单号',
    231. `status` int(11) NOT NULL DEFAULT '1' COMMENT '订单状态 1待付款,2待派送,3已派送,4已完成,5已取消',
    232. `user_id` bigint(20) NOT NULL COMMENT '下单用户',
    233. `address_book_id` bigint(20) NOT NULL COMMENT '地址id',
    234. `order_time` datetime NOT NULL COMMENT '下单时间',
    235. `checkout_time` datetime NOT NULL COMMENT '结账时间',
    236. `pay_method` int(11) NOT NULL DEFAULT '1' COMMENT '支付方式 1微信,2支付宝',
    237. `amount` decimal(10,2) NOT NULL COMMENT '实收金额',
    238. `remark` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
    239. `phone` varchar(255) COLLATE utf8_bin DEFAULT NULL,
    240. `address` varchar(255) COLLATE utf8_bin DEFAULT NULL,
    241. `user_name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
    242. `consignee` varchar(255) COLLATE utf8_bin DEFAULT NULL,
    243. PRIMARY KEY (`id`) USING BTREE
    244. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单表';
    245. -- ----------------------------
    246. -- Records of orders
    247. -- ----------------------------
    248. -- ----------------------------
    249. -- Table structure for order_detail
    250. -- ----------------------------
    251. DROP TABLE IF EXISTS `order_detail`;
    252. CREATE TABLE `order_detail` (
    253. `id` bigint(20) NOT NULL COMMENT '主键',
    254. `name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '名字',
    255. `image` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
    256. `order_id` bigint(20) NOT NULL COMMENT '订单id',
    257. `dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
    258. `setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
    259. `dish_flavor` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
    260. `number` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
    261. `amount` decimal(10,2) NOT NULL COMMENT '金额',
    262. PRIMARY KEY (`id`) USING BTREE
    263. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='订单明细表';
    264. -- ----------------------------
    265. -- Records of order_detail
    266. -- ----------------------------
    267. -- ----------------------------
    268. -- Table structure for setmeal
    269. -- ----------------------------
    270. DROP TABLE IF EXISTS `setmeal`;
    271. CREATE TABLE `setmeal` (
    272. `id` bigint(20) NOT NULL COMMENT '主键',
    273. `category_id` bigint(20) NOT NULL COMMENT '菜品分类id',
    274. `name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '套餐名称',
    275. `price` decimal(10,2) NOT NULL COMMENT '套餐价格',
    276. `status` int(11) DEFAULT NULL COMMENT '状态 0:停用 1:启用',
    277. `code` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '编码',
    278. `description` varchar(512) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息',
    279. `image` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
    280. `create_time` datetime NOT NULL COMMENT '创建时间',
    281. `update_time` datetime NOT NULL COMMENT '更新时间',
    282. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    283. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    284. `is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
    285. PRIMARY KEY (`id`) USING BTREE,
    286. UNIQUE KEY `idx_setmeal_name` (`name`)
    287. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐';
    288. -- ----------------------------
    289. -- Records of setmeal
    290. -- ----------------------------
    291. 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');
    292. -- ----------------------------
    293. -- Table structure for setmeal_dish
    294. -- ----------------------------
    295. DROP TABLE IF EXISTS `setmeal_dish`;
    296. CREATE TABLE `setmeal_dish` (
    297. `id` bigint(20) NOT NULL COMMENT '主键',
    298. `setmeal_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '套餐id ',
    299. `dish_id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '菜品id',
    300. `name` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '菜品名称 (冗余字段)',
    301. `price` decimal(10,2) DEFAULT NULL COMMENT '菜品原价(冗余字段)',
    302. `copies` int(11) NOT NULL COMMENT '份数',
    303. `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
    304. `create_time` datetime NOT NULL COMMENT '创建时间',
    305. `update_time` datetime NOT NULL COMMENT '更新时间',
    306. `create_user` bigint(20) NOT NULL COMMENT '创建人',
    307. `update_user` bigint(20) NOT NULL COMMENT '修改人',
    308. `is_deleted` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除',
    309. PRIMARY KEY (`id`) USING BTREE
    310. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='套餐菜品关系';
    311. -- ----------------------------
    312. -- Records of setmeal_dish
    313. -- ----------------------------
    314. 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');
    315. 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');
    316. 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');
    317. -- ----------------------------
    318. -- Table structure for shopping_cart
    319. -- ----------------------------
    320. DROP TABLE IF EXISTS `shopping_cart`;
    321. CREATE TABLE `shopping_cart` (
    322. `id` bigint(20) NOT NULL COMMENT '主键',
    323. `name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '名称',
    324. `image` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '图片',
    325. `user_id` bigint(20) NOT NULL COMMENT '主键',
    326. `dish_id` bigint(20) DEFAULT NULL COMMENT '菜品id',
    327. `setmeal_id` bigint(20) DEFAULT NULL COMMENT '套餐id',
    328. `dish_flavor` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '口味',
    329. `number` int(11) NOT NULL DEFAULT '1' COMMENT '数量',
    330. `amount` decimal(10,2) NOT NULL COMMENT '金额',
    331. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    332. PRIMARY KEY (`id`) USING BTREE
    333. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='购物车';
    334. -- ----------------------------
    335. -- Records of shopping_cart
    336. -- ----------------------------
    337. -- ----------------------------
    338. -- Table structure for user
    339. -- ----------------------------
    340. DROP TABLE IF EXISTS `user`;
    341. CREATE TABLE `user` (
    342. `id` bigint(20) NOT NULL COMMENT '主键',
    343. `name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
    344. `phone` varchar(100) COLLATE utf8_bin NOT NULL COMMENT '手机号',
    345. `sex` varchar(2) COLLATE utf8_bin DEFAULT NULL COMMENT '性别',
    346. `id_number` varchar(18) COLLATE utf8_bin DEFAULT NULL COMMENT '身份证号',
    347. `avatar` varchar(500) COLLATE utf8_bin DEFAULT NULL COMMENT '头像',
    348. `status` int(11) DEFAULT '0' COMMENT '状态 0:禁用,1:正常',
    349. PRIMARY KEY (`id`) USING BTREE
    350. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用户信息';

    3 开发环境

    3.1 Maven搭建

    先创建空项目或空文件夹:

    img

    创建springboot项目

    pom导入依赖

    spring-boot-devtools是热部署相关的依赖,commons-lang是工具类。

    1. "1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. <modelVersion>4.0.0modelVersion>
    5. <parent>
    6. <groupId>org.springframework.bootgroupId>
    7. <artifactId>spring-boot-starter-parentartifactId>
    8. <version>2.7.3version>
    9. <relativePath/>
    10. parent>
    11. <groupId>com.vincegroupId>
    12. <artifactId>reggieartifactId>
    13. <version>0.0.1-SNAPSHOTversion>
    14. <name>reggiename>
    15. <description>reggiedescription>
    16. <properties>
    17. <java.version>11java.version>
    18. properties>
    19. <dependencies>
    20. <dependency>
    21. <groupId>org.springframework.bootgroupId>
    22. <artifactId>spring-boot-devtoolsartifactId>
    23. <optional>trueoptional>
    24. dependency>
    25. <dependency>
    26. <groupId>org.springframework.bootgroupId>
    27. <artifactId>spring-boot-starterartifactId>
    28. dependency>
    29. <dependency>
    30. <groupId>org.springframework.bootgroupId>
    31. <artifactId>spring-boot-starter-testartifactId>
    32. <scope>testscope>
    33. dependency>
    34. <dependency>
    35. <groupId>org.springframework.bootgroupId>
    36. <artifactId>spring-boot-starter-webartifactId>
    37. <scope>compilescope>
    38. dependency>
    39. <dependency>
    40. <groupId>com.baomidougroupId>
    41. <artifactId>mybatis-plus-boot-starterartifactId>
    42. <version>3.5.2version>
    43. dependency>
    44. <dependency>
    45. <groupId>org.projectlombokgroupId>
    46. <artifactId>lombokartifactId>
    47. dependency>
    48. <dependency>
    49. <groupId>com.alibabagroupId>
    50. <artifactId>fastjsonartifactId>
    51. <version>2.0.12version>
    52. dependency>
    53. <dependency>
    54. <groupId>commons-langgroupId>
    55. <artifactId>commons-langartifactId>
    56. <version>2.6version>
    57. dependency>
    58. <dependency>
    59. <groupId>mysqlgroupId>
    60. <artifactId>mysql-connector-javaartifactId>
    61. <scope>runtimescope>
    62. dependency>
    63. <dependency>
    64. <groupId>com.alibabagroupId>
    65. <artifactId>druid-spring-boot-starterartifactId>
    66. <version>1.2.11version>
    67. dependency>
    68. <dependency>
    69. <groupId>org.springframework.bootgroupId>
    70. <artifactId>spring-boot-starter-testartifactId>
    71. <scope>testscope>
    72. dependency>
    73. dependencies>
    74. <build>
    75. <plugins>
    76. <plugin>
    77. <groupId>org.springframework.bootgroupId>
    78. <artifactId>spring-boot-maven-pluginartifactId>
    79. plugin>
    80. plugins>
    81. build>
    82. project>

    application.yml:

    1. server:
    2. port: 80
    3. spring:
    4. datasource:
    5. druid:
    6. driver-class-name: com.mysql.cj.jdbc.Driver
    7. url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
    8. username: root
    9. password: 1234
    10. main:
    11. banner-mode: off
    12. #应用名称,可选
    13. application:
    14. name: reggie_takeout
    15. mybatis-plus:
    16. configuration:
    17. #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,开启按照驼峰命名法映射。例如address_book映射成AddressBook
    18. map-underscore-to-camel-case: true
    19. # mysql的日志
    20. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    21. global-config:
    22. db-config:
    23. #id生成策略是ASSIGN_ID
    24. id-type: ASSIGN_ID
    25. banner: false
    26. # 如果表名有前缀,为了和实体类对应上,设置前缀。或者在实体类@TableName
    27. # table-prefix: tbl_

    3.2 启动测试

    创建测试类并启动

    回顾:@Slf4j注解是lombok依赖中的注解,可以设置日志。

    在这里插入图片描述

    3.3 导入前端页面

    存到static目录:

    在这里插入图片描述

    扩展:

    也可以导入到resources中,就需要放行静态页面,设置资源映射,在springmvc里讲过:

    SpringMVC基础_vincewm的博客-CSDN博客

    在默认页面和前台页面的情况下,直接把这俩拖到resource目录下直接访问是访问不到的,因为被mvc框架拦截了 所以我们要编写一个映射类放行这些资源

    创建配置映射类

    在这里插入图片描述在这里插入图片描述

    访问成功 在这里插入图片描述

    4 后台管理端-账户操作

    4.1 登陆功能

    4.1.1实体类和结果类

    前端页面

    在这里插入图片描述

    在这里插入图片描述在这里插入图片描述

    约定 res.data.code为1时是登录成功。

    数据库的employee员工表:

    员工表存储员工的用户名、密码、身份证等信息,用来后台页面登录。

    在这里插入图片描述

    这里password是加密后的数据,真实数据是123456

    创建实体类Employee

    也可以用mybatis plus逆向工程生成实体类。 参考下面文章的5.1:【Java笔记+踩坑】MyBatisPlus基础_id-type: assign_id_vincewm的博客-CSDN博客

    1. @Data
    2. //实体类实现Serializable接口,把对象转换为字节序列。序列化操作用于存储时,一般是对于NoSql数据库。
    3. public class Employee implements Serializable {
    4. //在反序列化的过程中,如果接收方为对象加载了一个类,如果该对象的serialVersionUID与对应持久化时的类不同,那么反序列化的过程中将会导致InvalidClassException异常。
    5. private static final long serialVersionUID = 1L;
    6. @JsonFormat(shape = JsonFormat.Shape.STRING)
    7. private Long id;
    8. private String username;
    9. private String name;
    10. private String password;
    11. private String phone;
    12. private String sex;
    13. private String idNumber;
    14. private Integer status;
    15. @TableField(fill=FieldFill.INSERT)
    16. private LocalDateTime createTime;
    17. @TableField(fill =FieldFill.INSERT_UPDATE)
    18. private LocalDateTime updateTime;
    19. //阿里巴巴的开发规范中推荐每个表都带有一个createTime 和一个 updateTime, 但是每次自己手动添加太麻烦了,可以配置MP让其自动添加,使用@TableField的fill注解。
    20. @TableField(fill = FieldFill.INSERT)
    21. @JsonFormat(shape = JsonFormat.Shape.STRING)
    22. private Long createUser;
    23. @TableField(fill = FieldFill.INSERT_UPDATE)
    24. @JsonFormat(shape = JsonFormat.Shape.STRING)
    25. private Long updateUser;
    26. }

    @TableField的fill属性

    默认值是:FieldFill.DEFAULT

    描述
    DEFAULT默认不处理
    INSERT插入时填充字段
    UPDATE更新时填充字段
    INSERT_UPDATE插入和更新时填充字段

    R结果类:

    1. //用泛型格式,如果controller返回的是页面数据,则return R.success(page);如果返回的是成功消息,则return R.success("success");如果返回错误消息,return R.error("错误原因");
    2. @Data
    3. public class R {
    4. private Integer code; //编码:1成功,0和其它数字为失败
    5. private String msg; //错误信息
    6. private T data; //数据
    7. private Map map = new HashMap(); //动态数据
    8. //静态类,controller返回时直接return R.success(T);
    9. public static R success(T object) {
    10. R r = new R();
    11. r.data = object;
    12. r.code = 1;
    13. return r;
    14. }
    15. public static R error(String msg) {
    16. R r = new R();
    17. r.msg = msg;
    18. r.code = 0;
    19. return r;
    20. }
    21. //添加动态数据
    22. public R add(String key, Object value) {
    23. this.map.put(key, value);
    24. return this;
    25. }
    26. }

    4.1.2 dao,service,controller

    dao层:

    1. @Mapper
    2. public interface EmployeeDao extends BaseMapper {
    3. }

    service层:

    1. public interface EmployeeService extends IService {
    2. }
    3. @Service
    4. public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {
    5. }

    这里业务接口继承了IService,业务实现类继承了ServiceImpl,这样service就继承了Mybatis-plus的各方法、变量。

    ServiceImpl类各方法(未过期)的作用

    getBaseMapper() getEntityClass() saveBatch() saveOrUpdate() saveOrUpdateBatch() updateBatchById() getOne() getMap() getObj() ServiceImpl类各属性的作用

    log:打印日志 baseMapper:实现了许多的SQL操作 entityClass:实体类 mapperClass:映射类

    controller层:

    业务逻辑 在这里插入图片描述

    1. @Slf4j
    2. @RestController
    3. @RequestMapping("/employee")
    4. public class EmployeeController {
    5. @Autowired
    6. private EmployeeService employeeService;
    7. /**
    8. * 员工登录
    9. * @param request
    10. * @param employee
    11. * @return
    12. */
    13. @PostMapping("/login")
    14. public R login(HttpServletRequest request,@RequestBody Employee employee){
    15. //1、将页面提交的密码password进行md5加密处理。同一个数据多次md5加密的结果是一样的,所以md5不能解密,但可以通过碰撞解密。
    16. String password = employee.getPassword();
    17. password = DigestUtils.md5DigestAsHex(password.getBytes());
    18. //2、根据页面提交的用户名username查询数据库
    19. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    20. queryWrapper.eq(Employee::getUsername,employee.getUsername());
    21. Employee emp = employeeService.getOne(queryWrapper);
    22. //3、如果没有查询到则返回登录失败结果
    23. if(emp == null){
    24. return R.error("用户名或密码错误");
    25. }
    26. //4、密码比对,如果不一致则返回登录失败结果
    27. if(!emp.getPassword().equals(password)){
    28. return R.error("登录失败");
    29. }
    30. //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
    31. if(emp.getStatus() == 0){
    32. return R.error("账号已被禁用");
    33. }
    34. //6、登录成功,将员工id存入Session并返回登录成功结果
    35. //被忘了存Session,默认有效期30分钟
    36. request.getSession().setAttribute("employee",emp.getId());
    37. return R.success(emp);
    38. }
    39. /**
    40. * 员工退出
    41. * @param request
    42. * @return
    43. */
    44. @PostMapping("/logout")
    45. public R logout(HttpServletRequest request){
    46. //清理Session中保存的当前登录员工的id
    47. request.getSession().removeAttribute("employee");
    48. return R.success("退出成功");
    49. }
    50. }

    MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

    md5是一种不可逆的加密,一定记住是不可逆的。即得到密文无法还原明文。

    同一个数据多次md5加密的结果是一样的,所以md5不能解密,但可以通过碰撞解密。

    比如你得到一个md5加密串"E10ADC3949BA59ABBE56E057F20F883E",你有N个密码,通过md5加密加密N个密码,得到其中一个和"E10ADC3949BA59ABBE56E057F20F883E"一致,那么则密码一致。

    md5解密网站(实际是靠碰撞解密):md5在线解密破解,md5解密加密

    登出功能:删除Session

    1. @RequestMapping("/logout")
    2. public R logout(HttpServletRequest request){
    3. //尝试删除
    4. try {
    5. request.getSession().removeAttribute("employee");
    6. }catch (Exception e){
    7. //删除失败
    8. return R.error("登出失败");
    9. }
    10. return R.success("登出成功");
    11. }

    可以看到点击退出后就清除存储信息了。

    img

    imgimg

    4.1.3 拦截页面登陆(添加过滤器,未登录状态自动跳转登录页面)

    这里的话用户直接url+资源名可以随便访问,所以要加个拦截器或者过滤器,没有登陆时,不给访问,自动跳转到登陆页面。

    过滤器和拦截器回顾:

    过滤器:JavaWeb基础9——Filter,Listener,Ajax,Axios,JSON_vincewm的博客-CSDN博客

    拦截器:SSM整合vincewm的博客-CSDN博客ssm

    拦截器和过滤器之间的区别:

    归属不同:Filter属于Servlet技术,依赖于Servlet容器;Interceptor属于SpringMVC技术,不依赖于servlet容器。 拦截内容不同:Filter对所有访问进行增强,几乎对所有请求起作用;Interceptor仅针对SpringMVC的访问进行增强,只能对action请求起作用。 调用次数不同:在action的生命周期中,过滤器只能在容器初始化时被调用一次,而拦截器可以多次被调用。 获取bean的权限不同:过滤器不能获取IOC容器中的各个bean;拦截器就可以,因为拦截器本身是个bean。这点很重要,在拦截器里注入一个service,可以调用业务逻辑。 底层机制不同:过滤器是基于函数回调,拦截器是基于java的反射机制的。

    在这里插入图片描述

    代码实现:

    1.在引导类注解**@ServletComponentScan**

    2.在filter包下编写过滤器:

    1. //坑点,路径是"/*",别忘了*
    2. @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
    3. @Slf4j
    4. public class LoginCheckFilter implements Filter{
    5. //路径匹配器,支持通配符,别忘了
    6. public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    7. @Override
    8. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    9. //0.要先把请求响应ServletXxx转成它的子接口HttpServletXxx,从而多了一些针对于Http协议的方法
    10. HttpServletRequest request = (HttpServletRequest) servletRequest;
    11. HttpServletResponse response = (HttpServletResponse) servletResponse;
    12. //1、获取本次请求的URI
    13. String requestURI = request.getRequestURI();// /backend/index.html
    14. log.info("拦截到请求:{}",requestURI);
    15. //定义不需要处理的请求路径,前端页面可以放行,只是除登录登出的后端拦截就行。
    16. String[] urls = new String[]{
    17. "/employee/login",
    18. "/employee/logout",
    19. "/backend/**",
    20. "/front/**"
    21. };
    22. //2、判断本次请求是否需要处理
    23. boolean check = check(urls, requestURI);
    24. //3、如果不需要处理,则直接放行
    25. if(check){
    26. log.info("本次请求{}不需要处理",requestURI);
    27. filterChain.doFilter(request,response);
    28. return;
    29. }
    30. //4、判断登录状态,如果已登录,则直接放行
    31. if(request.getSession().getAttribute("employee") != null){
    32. log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
    33. filterChain.doFilter(request,response);
    34. return;
    35. }
    36. log.info("用户未登录");
    37. //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
    38. //必须错误信息NOTLOGIN,因为前端根据msg==“NOTLOGIN”和code==0判断未登录
    39. response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    40. return;
    41. }
    42. /**
    43. * 路径匹配,检查本次请求是否需要放行
    44. * @param urls
    45. * @param requestURI
    46. * @return
    47. */
    48. public boolean check(String[] urls,String requestURI){
    49. for (String url : urls) {
    50. //这里是坑点,不能用equals
    51. boolean match = PATH_MATCHER.match(url, requestURI);
    52. if(match){
    53. return true;
    54. }
    55. }
    56. return false;
    57. }
    58. }

    坑点:

    • 0.别忘了引导类注解@ServletComponentScan
    • 1.通配符/*,别忘了*号
    • 2.别忘了创建静态final对象AntPathMatcher ,用它的match()方法进行url匹配,不能用equals。

    响应到前端的是否登录信息将在requeset.js中处理:

    其实所有前端页面都引用了js/request.js里的前端拦截器,前端拦截器完成跳转到登陆页面,不在后端做处理 在这里插入图片描述

    4.2 新增员工

    新增员工功能,(前端对手机号和身份证号长度做了一个校验) 在这里插入图片描述

    分析数据库,注意点是id为主键,所有字段非null, status默认值为1表示启用

    img

    前端页面:

    img

    img

    请求 URL: http://localhost:9001/employee (POST请求)

    img

    代码执行过程:

    img

    id雪花自增算法,改不改都行,Mybatis-plus默认自增就是ASSIGN_ID 在这里插入图片描述

    代码实现:

    1. @PostMapping
    2. public R save(HttpServletRequest request,@RequestBody Employee employee){
    3. log.info("新增员工,员工信息:{}",employee.toString());
    4. //前端没有设置密码框,这里设置初始密码123456,需要进行md5加密处理。正常可以把"12345"改成employee.getPassword()
    5. employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
    6. employee.setCreateTime(LocalDateTime.now());
    7. employee.setUpdateTime(LocalDateTime.now());
    8. //获得当前登录用户的id
    9. Long empId = (Long) request.getSession().getAttribute("employee");
    10. employee.setCreateUser(empId);
    11. employee.setUpdateUser(empId);
    12. employeeService.save(employee);
    13. //登录失败情况由下一节异常拦截处理
    14. return R.success("新增员工成功");
    15. }

    4.3 全局异常拦截处理

    先看看这种代码的try catch 这种try catch来捕获异常固然好,但是,代码量一大起来,超级多的try catch就会很乱 在这里插入图片描述全局异常处理代码实现

    在Common包下:

    1. //@RestControllerAdvice包括了下面两行
    2. @ControllerAdvice(annotations = {RestController.class, Controller.class})
    3. @ResponseBody
    4. @Slf4j
    5. public class GlobalExceptionHandler {
    6. /**
    7. * 异常处理方法
    8. * @return
    9. */
    10. //捕获完整性约束违反异常(其实就是数据库唯一约束异常)SQLIntegrityConstraintViolationException
    11. @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    12. public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
    13. log.error(ex.getMessage());
    14. if(ex.getMessage().contains("Duplicate entry")){
    15. return R.error("唯一约束异常:"+exception.getMessage().split(" ")[2]+"已存在");
    16. }
    17. return R.error("未知唯一约束错误");
    18. }
    19. }

    在这里插入图片描述 当报错信息出现Duplicate entry时,就意味着新增员工异常了 所以,我们对异常类的方法进行一些小改动,让这个异常反馈变得更人性化 这个时候再来客户端试试,就会提供人性化的报错,非常的快乐~

    img

    这回再回到Controller,这时就不需要再来try catch这种形式了,不用管他,因为一旦出现错误就会被我们的AOP捕获。所以,不需要再用try catch来抓了 在这里插入图片描述 异常类位置:com.cc.common.GloableExceptionHandler

    4.4 员工信息分页查询

    4.4.1 接口分析

    需求 在这里插入图片描述分页请求接口

    在这里插入图片描述在这里插入图片描述

    查询员工及显示接口 在这里插入图片描述在这里插入图片描述

    逻辑流程 在这里插入图片描述

    4.4.2 Mybatis-plus实现分页

    步骤1.mp分页拦截器

    在config包下:

    1. @Configuration
    2. public class MybatisPlusConfig {
    3. @Bean
    4. public MybatisPlusInterceptor mybatisPlusInterceptor(){
    5. MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
    6. mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    7. return mybatisPlusInterceptor;
    8. }
    9. }

    步骤2.分页查询

    1. @GetMapping("/page")
    2. public R page(int page,int pageSize,String name){
    3. log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
    4. //构造分页构造器
    5. Page pageInfo = new Page(page,pageSize);
    6. //构造条件构造器
    7. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
    8. //添加过滤条件
    9. // StringUtils.isNotEmpty(name)判断为空的标准是name!=null&name.length>0
    10. queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
    11. //添加排序条件
    12. queryWrapper.orderByDesc(Employee::getUpdateTime);
    13. //执行查询
    14. employeeService.page(pageInfo,queryWrapper);
    15. return R.success(pageInfo);
    16. }

    步骤3.测试分页:

    img

    将前端页面,展示每页行数的数组改成[1,2,3,4],然后ctrl+f5强制刷新,不然有缓存

    img

    StringUtils.isNotEmpty(name)判断为空的标准是name!=null&name!=""

    4.5 启用禁用员工账号,js主键丢失精度问题

    只有管理员账号有禁用启用按钮:

    img

    img

    前端如何判断当前用户是admin?缓存:

    imgimgimg

    点击禁用后发送请求:

    img

    js丢失long精度问题

    Long类型id是19位,而js只能处理17位数字,Long类型主键从前端传回后端丢失精度,导致传到后端的id和数据库id不一致:

    img

    解决方案:实体类注解@JsonFormat

    1. @JsonFormat(shape = JsonFormat.Shape.STRING)
    2. private Long id;

    4.6 修改员工窗口回显信息

    img

    img

    代码实现:

    1. @GetMapping("/{id}")
    2. public R getById(@PathVariable Long id){
    3. Employee employee = employeeService.getById(id);
    4. log.info("查询到员工:{}",employee);
    5. if(employee!=null)
    6. return R.success(employee);
    7. else return R.error("找不到这个员工");
    8. }

    4.7 公共字段自动填充,@TableField的fill

    TheadLocal

    img

    客户端发送的每次http请求,在服务端都会分配新的线程。因此登录检查过滤器、controller、元数据对象处理器属于一个线程。

    TheadLocal是线程的局部变量:

    imgTheadLocal常用方法:

    img

    如何在元数据对象处理器中获取当前登录用户的id?

    因为登录检查过滤器、controller、元数据对象处理器属于一个线程,所以可以在filter中获取登录用户的id,set到ThreadLocal中,在元数据处理器中get到线程局部变量ThreadLocal的值。

    代码实现:

    第一步,实体类注解:

    1. @TableField(fill = FieldFill.INSERT) //插入时填充字段
    2. private LocalDateTime createTime;
    3. @TableField(fill = FieldFill.INSERT_UPDATE) //插入和更新时填充字段
    4. private LocalDateTime updateTime;
    5. @TableField(fill = FieldFill.INSERT) //插入时填充字段
    6. private Long createUser;
    7. @TableField(fill = FieldFill.INSERT_UPDATE) //插入和更新时填充字段
    8. private Long updateUser;

    第二步,封装基于ThreadLocal的工具类

    在common包下:

    1. /**
    2. * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
    3. */
    4. public class BaseContext {
    5. private static ThreadLocal threadLocal = new ThreadLocal<>();
    6. /**
    7. * 设置值
    8. * @param id
    9. */
    10. public static void setCurrentId(Long id){
    11. threadLocal.set(id);
    12. }
    13. /**
    14. * 获取值
    15. * @return
    16. */
    17. public static Long getCurrentId(){
    18. return threadLocal.get();
    19. }
    20. }

    第三步,登录检查过滤器把id加到ThreadLocal

    1. //4、判断登录状态,如果已登录,则直接放行
    2. if(request.getSession().getAttribute("employee") != null){
    3. log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
    4. //这里要强转,虽然request.getSession().getAttribute("employee")类型确实是Long
    5. Long empId = (Long) request.getSession().getAttribute("employee");
    6. BaseContext.setCurrentId(empId);
    7. filterChain.doFilter(request,response);
    8. return;
    9. }

    第四步,自定义元数据对象处理器,获取ThreadLocal的id

    在common包下:

    1. /**
    2. * 自定义元数据对象处理器
    3. */
    4. @Component
    5. @Slf4j
    6. public class MyMetaObjectHandler implements MetaObjectHandler {
    7. /**
    8. * 插入操作,自动填充
    9. * @param metaObject
    10. */
    11. @Override
    12. public void insertFill(MetaObject metaObject) {
    13. log.info("公共字段自动填充[insert]...");
    14. log.info(metaObject.toString());
    15. //第一个参数属性名,第二个参数自动填充的值
    16. metaObject.setValue("createTime", LocalDateTime.now());
    17. metaObject.setValue("updateTime",LocalDateTime.now());
    18. metaObject.setValue("createUser",BaseContext.getCurrentId());
    19. metaObject.setValue("updateUser",BaseContext.getCurrentId());
    20. }
    21. /**
    22. * 更新操作,自动填充
    23. * @param metaObject
    24. */
    25. @Override
    26. public void updateFill(MetaObject metaObject) {
    27. log.info("公共字段自动填充[update]...");
    28. log.info(metaObject.toString());
    29. long id = Thread.currentThread().getId();
    30. log.info("线程id为:{}",id);
    31. metaObject.setValue("updateTime",LocalDateTime.now());
    32. metaObject.setValue("updateUser",BaseContext.getCurrentId());
    33. }
    34. }

    第五步,删除之前写的创建、更新时间等相关代码,让其自动填充。

    5 分类操作

    5.1 新增分类

    5.1.1分析

    img

    img

    数据模型,category表:

    img

    5.1.2 category实体类

    注意:别忘了 @JsonFormat注解防止js获取主键时丢失精度,js只能读17位,而雪花算法Long类型是19位。

    1. /**
    2. * 分类
    3. */
    4. @Data
    5. public class Category implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //类型 1 菜品分类 2 套餐分类
    10. private Integer type;
    11. //分类名称
    12. private String name;
    13. //顺序
    14. private Integer sort;
    15. //创建时间
    16. @TableField(fill = FieldFill.INSERT)
    17. private LocalDateTime createTime;
    18. //更新时间
    19. @TableField(fill = FieldFill.INSERT_UPDATE)
    20. private LocalDateTime updateTime;
    21. //创建人
    22. @TableField(fill = FieldFill.INSERT)
    23. @JsonFormat(shape = JsonFormat.Shape.STRING)
    24. private Long createUser;
    25. //修改人
    26. @TableField(fill = FieldFill.INSERT_UPDATE)
    27. @JsonFormat(shape = JsonFormat.Shape.STRING)
    28. private Long updateUser;
    29. }

    5.1.3 dao和service

    1. @Mapper
    2. public interface CategoryMapper extends BaseMapper {
    3. }
    4. public interface CategoryService extends IService {
    5. public void remove(Long id);
    6. }
    7. @Service
    8. public class CategoryServiceImpl extends ServiceImpl implements CategoryService{
    9. @Autowired
    10. private DishService dishService;
    11. @Autowired
    12. private SetmealService setmealService;
    13. /**
    14. * 根据id删除分类,删除之前需要进行判断
    15. * @param id
    16. */
    17. @Override
    18. public void remove(Long id) {
    19. LambdaQueryWrapper dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
    20. //添加查询条件,根据分类id进行查询
    21. dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
    22. int count1 = dishService.count(dishLambdaQueryWrapper);
    23. //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
    24. if(count1 > 0){
    25. //已经关联菜品,抛出一个业务异常
    26. throw new CustomException("当前分类下关联了菜品,不能删除");
    27. }
    28. //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
    29. LambdaQueryWrapper setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
    30. //添加查询条件,根据分类id进行查询
    31. setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
    32. int count2 = setmealService.count();
    33. if(count2 > 0){
    34. //已经关联套餐,抛出一个业务异常
    35. throw new CustomException("当前分类下关联了套餐,不能删除");
    36. }
    37. //正常删除分类
    38. super.removeById(id);
    39. }
    40. }

    5.1.4 分类信息

    请求:

    img

    img

    代码:

    1. @RestController
    2. @RequestMapping("/category")
    3. @Slf4j
    4. public class CategoryController {
    5. @Autowired
    6. private CategoryService categoryService;
    7. /**
    8. * 新增分类
    9. * @param category
    10. * @return
    11. */
    12. @PostMapping
    13. public R save(@RequestBody Category category){
    14. log.info("category:{}",category);
    15. categoryService.save(category);
    16. return R.success("新增分类成功");
    17. }
    18. }

    5.2 分类页面,分页查询

    请求:

    img

    代码 :

    跟前面员工分页基本一样。

    1. @GetMapping("/page")
    2. public R page(int page,int pageSize){
    3. //分页构造器
    4. Page pageInfo = new Page<>(page,pageSize);
    5. //条件构造器
    6. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    7. //添加排序条件,根据sort进行排序
    8. queryWrapper.orderByAsc(Category::getSort);
    9. //分页查询
    10. categoryService.page(pageInfo,queryWrapper);
    11. return R.success(pageInfo);
    12. }

    测试:

    在/page/category/list.html因为数据少,修改前端每页展示量:

    img

    修改后刷新,记得idea改成切出idea刷新资源,如果展示不出则用ctrl+f5清除缓存刷新,测试成功:

    img

    5.3 删除分类

    删除分类需要注意的是当分类关联了菜品和套餐时,不允许删除。

    5.3.1 基础删除,不检查关联的菜品

    请求:

    img

    • 如果忘了加注解@JsonFormat注解,会发生js获取主键时丢失精度,js只能读17位,而雪花算法Long类型是19位。

    • @JsonFormat(shape = JsonFormat.Shape.STRING)
      

    img

    代码:

    1. @DeleteMapping
    2. public R page(Long ids){
    3. boolean success = categoryService.removeById(ids);
    4. if(success)return R.success("删除成功");
    5. else return R.error("删除失败");
    6. }

    5.3.2 菜品和套餐的实体类,别忘了@JsonFormat

    套餐:

    1. /**
    2. * 套餐
    3. */
    4. @Data
    5. public class Setmeal implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //分类id
    10. @JsonFormat(shape = JsonFormat.Shape.STRING)
    11. private Long categoryId;
    12. //套餐名称
    13. private String name;
    14. //套餐价格
    15. private BigDecimal price;
    16. //状态 0:停用 1:启用
    17. private Integer status;
    18. //编码
    19. private String code;
    20. //描述信息
    21. private String description;
    22. //图片
    23. private String image;
    24. @TableField(fill = FieldFill.INSERT)
    25. private LocalDateTime createTime;
    26. @TableField(fill = FieldFill.INSERT_UPDATE)
    27. private LocalDateTime updateTime;
    28. @TableField(fill = FieldFill.INSERT)
    29. @JsonFormat(shape = JsonFormat.Shape.STRING)
    30. private Long createUser;
    31. @TableField(fill = FieldFill.INSERT_UPDATE)
    32. @JsonFormat(shape = JsonFormat.Shape.STRING)
    33. private Long updateUser;
    34. }

    菜品:

    1. /**
    2. 菜品
    3. */
    4. @Data
    5. public class Dish implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //菜品名称
    10. private String name;
    11. //菜品分类id
    12. @JsonFormat(shape = JsonFormat.Shape.STRING)
    13. private Long categoryId;
    14. //菜品价格
    15. private BigDecimal price;
    16. //商品码
    17. private String code;
    18. //图片
    19. private String image;
    20. //描述信息
    21. private String description;
    22. //0 停售 1 起售
    23. private Integer status;
    24. //顺序
    25. private Integer sort;
    26. @TableField(fill = FieldFill.INSERT)
    27. private LocalDateTime createTime;
    28. @TableField(fill = FieldFill.INSERT_UPDATE)
    29. private LocalDateTime updateTime;
    30. @TableField(fill = FieldFill.INSERT)
    31. @JsonFormat(shape = JsonFormat.Shape.STRING)
    32. private Long createUser;
    33. @TableField(fill = FieldFill.INSERT_UPDATE)
    34. @JsonFormat(shape = JsonFormat.Shape.STRING)
    35. private Long updateUser;
    36. }

    5.3.3 菜品和套餐的dao和service

    建议自己写,很快,复制粘贴总会把实体类之类的写错。

    1. @Mapper
    2. public interface DishMapper extends BaseMapper {
    3. }
    4. @Mapper
    5. public interface SetmealMapper extends BaseMapper {
    6. }
    7. public interface DishService extends IService {
    8. }
    9. public interface SetmealService extends IService {
    10. }
    11. @Service
    12. @Slf4j
    13. public class DishServiceImpl extends ServiceImpl implements DishService {
    14. }
    15. @Service
    16. @Slf4j
    17. public class SetmealServiceImpl extends ServiceImpl implements SetmealService {
    18. }

    5.3.4 检查后删除,业务异常捕获

    目标:当分类关联了菜品和套餐时,不允许删除。

    跟进到IService的remove方法,通过比较器wrapper删除,参数改成Integer的话算重载,可以改:

    img

    代码实现:

    我前面自己写的时候,没有用异常捕获,是在业务层根据不同情况返回不同数字,然后controller根据数字R.error("错误原因") 。用异常捕获也能实现,两种效果都一样,用异常捕获更规范

    service:

    1. @Override
    2. public Integer remove(Long id){
    3. LambdaQueryWrapper wrapper1=new LambdaQueryWrapper<>();
    4. wrapper1.eq(Setmeal::getCategoryId,id);
    5. LambdaQueryWrapper wrapper2=new LambdaQueryWrapper<>();
    6. wrapper2.eq(Dish::getCategoryId,id);
    7. log.info("套餐查询:{}",setmealService.getMap(wrapper1));log.info("菜品查询:{}",dishService.getMap(wrapper2));
    8. //查到有菜品或套餐使用这个分类
    9. if(setmealService.count(wrapper1)>0) return -1;
    10. if(dishService.count(wrapper2)>0) return -2;
    11. return categoryDao.deleteById(id)>0?1:0;
    12. }

    1. @DeleteMapping
    2. public R remove(Long ids){
    3. log.info("要删除的id:{}",ids);
    4. int success = -3;success=categoryService.remove(ids);
    5. log.info("删除状态:{}",success);
    6. if(success==1)return R.success("删除成功");
    7. else if(success==0) return R.error("网络原因删除失败");
    8. else if(success==-1) return R.error("有套餐正在使用此分类");
    9. else if(success==-2)return R.error("有菜品正在使用此分类");
    10. else return R.error("未知错误");
    11. }

    异常捕获方案:

    service

    1. @Service
    2. public class CategoryServiceImpl extends ServiceImpl implements CategoryService{
    3. @Autowired
    4. private DishService dishService;
    5. @Autowired
    6. private SetmealService setmealService;
    7. /**
    8. * 根据id删除分类,删除之前需要进行判断
    9. * @param id
    10. */
    11. @Override
    12. public void remove(Long id) {
    13. LambdaQueryWrapper dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
    14. //添加查询条件,根据分类id进行查询
    15. dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
    16. int count1 = dishService.count(dishLambdaQueryWrapper);
    17. //查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
    18. if(count1 > 0){
    19. //已经关联菜品,抛出一个业务异常
    20. throw new CustomException("当前分类下关联了菜品,不能删除");
    21. }
    22. //查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
    23. LambdaQueryWrapper setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
    24. //添加查询条件,根据分类id进行查询
    25. setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
    26. int count2 = setmealService.count();
    27. if(count2 > 0){
    28. //已经关联套餐,抛出一个业务异常
    29. throw new CustomException("当前分类下关联了套餐,不能删除");
    30. }
    31. //正常删除分类
    32. super.removeById(id);
    33. }
    34. }

    controller

    1. @DeleteMapping
    2. public R delete(Long id){
    3. log.info("删除分类,id为:{}",id);
    4. //categoryService.removeById(id);
    5. if(categoryService.remove(ids)) return R.success("删除成功");
    6. //这里也可以throw业务异常,但不建议,一般都是service抛出异常,controller返回R.error();
    7. else return R.error("删除失败");
    8. }

    业务异常类:

    1. /**
    2. * 自定义业务异常类
    3. */
    4. public class CustomException extends RuntimeException {
    5. //带参构造方法,重新设置异常信息
    6. public CustomException(String message){
    7. super(message);
    8. }
    9. }

    全局异常处理:

    1. @RestControllerAdvice
    2. //@ControllerAdvice(annotations = {RestController.class, Controller.class})
    3. //@ResponseBody
    4. @Slf4j
    5. public class GlobalExceptionHandler {
    6. /**
    7. * 异常处理方法
    8. * @return
    9. */
    10. @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    11. public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
    12. log.error(ex.getMessage());
    13. if(ex.getMessage().contains("Duplicate entry")){
    14. String[] split = ex.getMessage().split(" ");
    15. String msg = split[2] + "已存在";
    16. return R.error(msg);
    17. }
    18. return R.error("未知错误");
    19. }
    20. /**
    21. * 异常处理方法
    22. * @return
    23. */
    24. @ExceptionHandler(CustomException.class)
    25. public R exceptionHandler(CustomException ex){
    26. log.error(ex.getMessage());
    27. return R.error(ex.getMessage());
    28. }
    29. }

    测试后成功:

    img

    5.4 修改分类

    请求:

    img

    可以知道是通过json传数据,要用@RequestBody传实体类。

    代码实现:

    像修改时间和修改人都会自动填充,具体看上面4.7自动填充。

    1. @PutMapping
    2. public R update(@RequestBody Category category){
    3. if(categoryService.updateById(category)) return R.success("修改成功");
    4. else return R.error("修改失败");
    5. }

    6 菜品操作

    img

    6.1 文件上传和下载

    6.1.1 文件上传

    文件上传表单要求:

    img

    前端组件库底层也是用这样表单:

    img

    服务端接收上传文件,底层通常用Apache两个组件:

    img

    spring-web对文件上传进行了封装,只需在controller声明MultipartFile 类型参数、参数名为Form data就可以接受文件

    img

    上传页面拷贝到page目录下:

    img

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 文件上传
    6. "stylesheet" href="../../plugins/element-ui/index.css" />
    7. "stylesheet" href="../../styles/common.css" />
    8. "stylesheet" href="../../styles/page.css" />
    9. "addBrand-container" id="food-add-app">
    10. "container">
    11. "avatar-uploader"
    12. action="/common/upload"
    13. :show-file-list="false"
    14. :on-success="handleAvatarSuccess"
    15. :before-upload="beforeUpload"
    16. ref="upload">
    17. if="imageUrl" :src="imageUrl" class="avatar">
    18. else class="el-icon-plus avatar-uploader-icon">
  • 上传请求:

    img

    img

    注意:待会传参MutlipartFile 对象名必须是file,也就是和form data的名字一致。

    代码实现:

    yml设置路径

    1. reggie:
    2. #写成D:\img\或者D:/img/也能成功,写成双反斜杠点保险点,防止转义
    3. path: D:\\img\\

    读取路径并接收上传的文件:

    1. @RestController
    2. @RequestMapping("/common")
    3. @Slf4j
    4. public class CommonController {
    5. @Value("${reggie.path}")
    6. private String basePath;
    7. /**
    8. * 文件上传
    9. * @param file
    10. * @return
    11. */
    12. @PostMapping("/upload")
    13. //MultipartFile 译为多部分的文件
    14. public R upload(MultipartFile file){
    15. //file是一个临时文件,默认存在C:\\User\\...目录下,需要转存到指定位置,否则本次请求完成后临时文件会删除
    16. log.info(file.toString());
    17. //原始文件名
    18. String originalFilename = file.getOriginalFilename();//abc.jpg
    19. //获取文件后缀名,这里不能split,因为split里不能根据“.”分割
    20. String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
    21. //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖。UUID 是通用唯一识别码,具体解释看代码下面引用
    22. String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
    23. //创建一个目录对象
    24. File dir = new File(basePath);
    25. //判断当前目录是否存在
    26. if(!dir.exists()){
    27. //目录不存在,需要创建
    28. dir.mkdirs();
    29. }
    30. try {
    31. //将临时文件转存到指定位置
    32. file.transferTo(new File(basePath + fileName));
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. }
    36. return R.success(fileName);
    37. }
    38. }

    UUID:

    UUID 是 通用唯一识别码,UUID 的目的,是让分布式系统中的所有元素,都能有唯一的辨识资讯,而不需要透过中央控制端来做辨识资讯的指定。

    UUID.randomUUID().toString()是javaJDK提供的一个自动生成主键的方法。UUID(Universally Unique Identifier)全局唯一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的,是由一个十六位的数字组成,表现出来的形式。由以下几部分的组合:当前日期和时间(UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同),时钟序列,全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得),UUID的唯一缺陷在于生成的结果串会比较长。

    回顾:前面Mybatis-plus学习五种id生成策略时,有个策略是ASSIGN_UUID:

    • ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢

    MyBatisPlus基础_vincewm的博客-CSDN博客

    测试上传成功:

    img

    img

    6.1.2 文件下载,回显上传的图片

    将文件从服务端下载到本地计算机,可以用标签展示下载的图片:

    img

    两种文件下载方式:

    img

    请求:

    img

    get方式传参,controller参数名为name就能直接传了,springmvc是可以自动转换格式的。 怕name冲突,可以@RequestParam起别名。

    代码实现:

    1. /**
    2. * 文件下载
    3. * @param name
    4. * @param response
    5. */
    6. @GetMapping("/download")
    7. public void download(String name, HttpServletResponse response){
    8. try {
    9. //输入流,通过输入流读取文件内容
    10. FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
    11. //输出流,通过输出流将文件写回浏览器
    12. ServletOutputStream outputStream = response.getOutputStream();
    13. response.setContentType("image/jpeg");
    14. int len = 0;
    15. //注意别写成大写
    16. byte[] bytes = new byte[1024];
    17. while ((len = fileInputStream.read(bytes)) != -1){
    18. outputStream.write(bytes,0,len);
    19. outputStream.flush();
    20. }
    21. //关闭资源
    22. outputStream.close();
    23. fileInputStream.close();
    24. } catch (Exception e) {
    25. e.printStackTrace();
    26. }
    27. }

    坑点:

    • byte[] bytes = new byte[1024];的byte别写成大写 。

    • File输入流读文件,从response获得的Servlet输出流写文件

    • 最后一定记得关闭输入输出流,输出流每次write刷新一次。如果忘了刷新和关闭将会写不成功。

    • voidwrite(byte[] b, int off, int len)len字节从位于偏移量 off的指定字节数组写入此文件输出流。

    6.2 新增菜品

    6.2.1 分析

    imgimg

    表结构:

    img菜品口味dish_favor表:

    img

    6.2.2 菜品味道实体类,dao和service

    实体类:

    1. @Data
    2. public class DishFlavor implements Serializable {
    3. private static final long serialVersionUID = 1L;
    4. @JsonFormat(shape = JsonFormat.Shape.STRING)
    5. private Long id;
    6. //菜品id
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long dishId;
    9. //口味名称
    10. private String name;
    11. //口味数据list
    12. private String value;
    13. @TableField(fill = FieldFill.INSERT)
    14. private LocalDateTime createTime;
    15. @TableField(fill = FieldFill.INSERT_UPDATE)
    16. private LocalDateTime updateTime;
    17. @TableField(fill = FieldFill.INSERT)
    18. @JsonFormat(shape = JsonFormat.Shape.STRING)
    19. private Long createUser;
    20. @TableField(fill = FieldFill.INSERT_UPDATE)
    21. @JsonFormat(shape = JsonFormat.Shape.STRING)
    22. private Long updateUser;
    23. //是否删除
    24. private Integer isDeleted;
    25. }

    1. @Mapper
    2. public interface DishFlavorMapper extends BaseMapper {
    3. }
    4. public interface DishFlavorService extends IService {
    5. }
    6. @Service
    7. public class DishFlavorServiceImpl extends ServiceImpl implements DishFlavorService {
    8. }

    6.2.3 根据条件查询分类数据

    跟前面不同的一点是分类信息要用CategoryController获取:

    img

    请求:

    点击增加菜品按钮的请求:展示分类框

    img

    CategoryController

    1. @GetMapping("/list")
    2. //其实参数传Integer的type也可以,只是要再创建分类对象赋值type,麻烦。这里封装成category。
    3. //参数不是json转实体类,所以不用加@RequestBody
    4. public R> list(Category category){
    5. //条件构造器
    6. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    7. //添加条件
    8. queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
    9. //添加排序条件
    10. queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
    11. List list = categoryService.list(queryWrapper);
    12. return R.success(list);
    13. }
    14. }

    6.2.4 创建DTO类封装表单数据

    因为表单既包括了菜品,也包括了菜品味道,所以要新创建一个DTO类封装所有表单信息,它是菜品实体类的子类。

    img

    在com.example.domain包下创建dto.DishDto类:

    1. @Data
    2. public class DishDto extends Dish {
    3. private List flavors = new ArrayList<>();
    4. //下面两个成员变量暂时用不到,先放着,后面有其他表单需要封装
    5. //菜品所属分类名称
    6. private String categoryName;
    7. private Integer copies;
    8. }

    6.2.5 新增菜品,DishController

    请求:

    img

    DishServiceImpl

    1. @Transactional //记得在引导类@EnableTransactionManagement
    2. public void saveWithFlavor(DishDto dishDto) {
    3. //保存菜品的基本信息到菜品表dish
    4. this.save(dishDto);
    5. Long dishId = dishDto.getId();//菜品id
    6. //菜品口味
    7. List flavors = dishDto.getFlavors();
    8. // stream流的filter是满足条件的留下,是对原数组的过滤;map则是对原list的加工,map里是Lambda表达式,形容对list加工。
    9. // 通过stream流的map将list加工,把这个菜品的id加到每个list元素的dishId属性;
    10. flavors = flavors.stream().map((item) -> { //给list每个元素map加工,返回值为加工后的结果
    11. item.setDishId(dishId);
    12. return item;
    13. }).collect(Collectors.toList()); //将加工后的结果遍历收集到原list
    14. //保存菜品口味数据到菜品口味表dish_flavor
    15. dishFlavorService.saveBatch(flavors);
    16. }

    回顾stream流:

    • stream流的filter是满足条件的留下,是对原数组的过滤;map则是对原list的加工,map里参数是Lambda表达式,Lambda表达式参数是集合的元素,返回值是对元素遍历加工后的元素。
    • collect(Collectors.toList())是将结果遍历收集到原List集合。

    坑点:别忘了加@Transactional,引导类加 @EnableTransactionManagement,因为这个业务里有保存菜品和保存味道,他们要么都成功,要么都失败。

    DishController

    1. @PostMapping
    2. public R save(@RequestBody DishDto dishDto){
    3. log.info(dishDto.toString());
    4. dishService.saveWithFlavor(dishDto);
    5. return R.success("新增菜品成功");
    6. }

    6.3 菜品信息分页展示

    分析:

    imgimg

    请求:

    img

    代码:

    首先把图片资料拷贝到D://img中,这是前面上传下载代码时候在yml配置的图片路径。

    DishController,需要注意的点是菜品的分类属性是category表的id,要返回包含categoryName的DishDto

    1. @GetMapping("/page")
    2. public R page(int page,int pageSize,String name){
    3. //构造分页构造器对象
    4. Page pageInfo = new Page<>(page,pageSize);
    5. Page dishDtoPage = new Page<>();
    6. //条件构造器
    7. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    8. //添加过滤条件
    9. queryWrapper.like(name != null,Dish::getName,name);
    10. //添加排序条件
    11. queryWrapper.orderByDesc(Dish::getUpdateTime);
    12. //执行分页查询,dishService查的是dish表,所以必须先Dish查询,再拷贝到DishDto
    13. dishService.page(pageInfo,queryWrapper);
    14. //注意这一步,将Dish的page拷贝到DishDto的page,第三个参数是忽略records属性
    15. BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
    16. List records = pageInfo.getRecords();
    17. //steam流的map(Lambda表达式)对list进行加工,stream流返回值是可以换泛型的。等号左边是List,右边是List
    18. List list = records.stream().map((item) -> {
    19. DishDto dishDto = new DishDto();
    20. //对象拷贝的工具类BeanUtils,将list里每一个菜品Dish对象信息拷贝到DishDto对象
    21. BeanUtils.copyProperties(item,dishDto);
    22. //将菜品所属分类的id查询为name,存到DishDto对象的categoryName,再返回替换item,完成item的加工
    23. Long categoryId = item.getCategoryId();//分类id
    24. //根据id查询分类对象
    25. Category category = categoryService.getById(categoryId);
    26. if(category != null){
    27. String categoryName = category.getName();
    28. dishDto.setCategoryName(categoryName);
    29. }
    30. return dishDto;
    31. }).collect(Collectors.toList());
    32. dishDtoPage.setRecords(list);
    33. return R.success(dishDtoPage);
    34. }

    图片展示是查询到菜品image属性存储图片的id,前端代码根据id发送请求循环渲染下载图片:

    img

    坑点:

    有些菜品查不到分类,所以在查Category时候要判断查到的值是否是null。

    我这里忘记刷新数据库,前面测试删除分类不小心删了几个分类,Navicat看数据库还是有信息存在的,对比之下卡了我很长时间。

    img

    6.4 修改菜品

    分析:修改和新增使用的是一个页面

    img

    6.4.1 修改数据回显

    分析:

    有修改页面可以得知这里返回值必须是DishDto对象,包括菜品、味道、分类信息。 所以要在DishServiceImpl写getByIdWithFlavor方法。这里分类名返回null即可,前端会发送请求从分类id查分类name。

    请求:

    img

    代码

    DishSerivceImpl

    这里分类名返回null即可,前端会发送请求从分类id查分类name。

    1. public DishDto getByIdWithFlavor(Long id) {
    2. //查询菜品基本信息,从dish表查询
    3. Dish dish = this.getById(id);
    4. DishDto dishDto = new DishDto();
    5. BeanUtils.copyProperties(dish,dishDto);
    6. //查询当前菜品对应的口味信息,从dish_flavor表查询
    7. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    8. queryWrapper.eq(DishFlavor::getDishId,dish.getId());
    9. List flavors = dishFlavorService.list(queryWrapper);
    10. dishDto.setFlavors(flavors);
    11. return dishDto;
    12. }

    DishController

    1. @GetMapping("/{id}")
    2. public R get(@PathVariable Long id){
    3. DishDto dishDto = dishService.getByIdWithFlavor(id);
    4. return R.success(dishDto);
    5. }

    坑点:

    如果分类那里显示的是数字,那就是因为你没有处理js对Long类型丢失精度问题,给实体类跟id相关的全部加

    @JsonFormat(shape = JsonFormat.Shape.STRING)
    

    6.4.2 修改菜品

    img

    主要的点是要把原来的味道信息删除,再把新的味道信息添加。

    DishSerivceImpl

    1. @Override
    2. @Transactional //要保证引导类已经@EnableTransactionManagement
    3. public void updateWithFlavor(DishDto dishDto) {
    4. //更新dish表基本信息
    5. this.updateById(dishDto);
    6. //清理当前菜品对应口味数据---dish_flavor表的delete操作
    7. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
    8. queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
    9. dishFlavorService.remove(queryWrapper);
    10. //添加当前提交过来的口味数据---dish_flavor表的insert操作
    11. List flavors = dishDto.getFlavors();
    12. flavors = flavors.stream().map((item) -> {
    13. item.setDishId(dishDto.getId());
    14. return item;
    15. }).collect(Collectors.toList());
    16. dishFlavorService.saveBatch(flavors);
    17. }

    DishController

    1. @PutMapping
    2. public R update(@RequestBody DishDto dishDto){
    3. log.info(dishDto.toString());
    4. dishService.updateWithFlavor(dishDto);
    5. return R.success("新增菜品成功");
    6. }

    测试成功:

    img

    7 套餐操作

    img

    7.1 环境准备

    7.1.1 数据模型

    img status状态0为停售1为起售,code是套餐编码

    imgname是菜品名称, copies 是套餐内的这个菜品数量,is_deleted用于逻辑删除。关系表存的菜品套餐id类型都是varchar

    7.1.2 Setmeal,SetmealDish实体类和数据传输对象SetmealDto

    Setmeal在前面分类那里写过了

    SetmealDish

    1. /**
    2. * 套餐菜品关系
    3. */
    4. @Data
    5. public class SetmealDish implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. private Long id;
    8. //套餐id
    9. private Long setmealId;
    10. //菜品id
    11. private Long dishId;
    12. //菜品名称 (冗余字段)
    13. private String name;
    14. //菜品原价
    15. private BigDecimal price;
    16. //份数
    17. private Integer copies;
    18. //排序
    19. private Integer sort;
    20. @TableField(fill = FieldFill.INSERT)
    21. private LocalDateTime createTime;
    22. @TableField(fill = FieldFill.INSERT_UPDATE)
    23. private LocalDateTime updateTime;
    24. @TableField(fill = FieldFill.INSERT)
    25. private Long createUser;
    26. @TableField(fill = FieldFill.INSERT_UPDATE)
    27. private Long updateUser;
    28. //是否删除
    29. private Integer isDeleted;
    30. }

    SetmealDto

    1. @Data
    2. public class SetmealDto extends Setmeal {
    3. //套餐和菜品关系的list
    4. private List setmealDishes;
    5. //所属分类名
    6. private String categoryName;
    7. }

    7.1.3 套餐和套菜关系的dao和Service

    Setmeal的dao和Service在前面分类那里写过了。

    SetmealDishDao

    1. @Mapper
    2. public interface SetmealDishMapper extends BaseMapper {
    3. }
    4. public interface SetmealDishService extends IService {
    5. }
    6. @Service
    7. @Slf4j
    8. public class SetmealDishServiceImpl extends ServiceImpl implements SetmealDishService {
    9. }

    7.2 新增套餐

    7.2.1 根据分类id查询DishDto,DishController

    img

    要查询每个类别菜品的全部信息

    请求:img

    根据categoryId查询这个分类里所有菜品信息(包括味道)list

    代码实现:

    DishController

    1. @GetMapping("/list")
    2. //根据条件查询多菜品。如果参数只有categoryId,那就只能查单一条件,通用性低
    3. public R> list(Dish dish) {
    4. log.info("dish:{}", dish);
    5. //条件构造器,条件name和categoryId和status(price那些条件始终没需求就不做条件),根据更新时间排序
    6. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    7. queryWrapper.like(StringUtils.isNotEmpty(dish.getName()), Dish::getName, dish.getName());
    8. queryWrapper.eq(null != dish.getCategoryId(), Dish::getCategoryId, dish.getCategoryId());
    9. //查询条件,状态为1(起售状态)的菜品
    10. queryWrapper.eq(Dish::getStatus,1);
    11. queryWrapper.orderByDesc(Dish::getUpdateTime);
    12. //条件查询到菜品列表
    13. List dishs = dishService.list(queryWrapper);
    14. //对list每个元素加工,设置分类和味道信息,返回成dto,
    15. List dishDtos = dishs.stream().map(item -> {
    16. DishDto dishDto = new DishDto();
    17. BeanUtils.copyProperties(item, dishDto);
    18. Category category = categoryService.getById(item.getCategoryId());
    19. if (category != null) {
    20. dishDto.setCategoryName(category.getName());
    21. }
    22. LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
    23. wrapper.eq(DishFlavor::getDishId, item.getId());
    24. dishDto.setFlavors(dishFlavorService.list(wrapper));
    25. return dishDto;
    26. }).collect(Collectors.toList());
    27. return R.success(dishDtos);
    28. }

    7.2.2 保存数据,SetmealController

    请求:

    img

    img

    可以看出,setmealDishes不是setmeal表的字段,要用SetmealDto封装参数。

    代码:

    1. SetmealServiceImpl
    2. @Transactional //引导类要开启业务
    3. public boolean saveWithDish(SetmealDto setmealDto) {
    4. //保存后setmealDao就生成了套餐id
    5. if(!this.save(setmealDto))return false;
    6. //获取套菜关系的list
    7. List setmealDishes = setmealDto.getSetmealDishes();
    8. //加工list,每个套餐关系设置前面生成的套餐id,其他参数都在表单传来了,或有默认值
    9. setmealDishes.stream().map(item->{
    10. item.setSetmealId(setmealDto.getId());
    11. return item;
    12. }).collect(Collectors.toList());
    13. if(!setmealDishService.saveBatch(setmealDishes)) return false;
    14. return true;
    15. }

    SetmealController

    1. @RestController
    2. @RequestMapping("/setmeal")
    3. @Slf4j
    4. public class SetmealController {
    5. @Autowired
    6. private SetmealService setmealService;
    7. @Autowired
    8. private CategoryService categoryService;
    9. @Autowired
    10. private SetmealDishService setmealDishService;
    11. /**
    12. * 新增套餐
    13. * @param setmealDto
    14. * @return
    15. */
    16. @PostMapping
    17. public R save(@RequestBody SetmealDto setmealDto){
    18. log.info("套餐信息:{}",setmealDto);
    19. setmealService.saveWithDish(setmealDto);
    20. return R.success("新增套餐成功");
    21. }
    22. }

    7.3 套餐信息分页展示

    img

    请求:

    img

    分析:

    由预览效果可知还要传参name,因为有套餐分类名,所以返回的要是Page。

    代码:

    SetmealController

    1. @GetMapping("/page")
    2. public R page(int page,int pageSize,String name){
    3. //分页构造器对象
    4. Page pageInfo = new Page<>(page,pageSize);
    5. Page dtoPage = new Page<>();
    6. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    7. //添加查询条件,根据name进行like模糊查询
    8. queryWrapper.like(name != null,Setmeal::getName,name);
    9. //添加排序条件,根据更新时间降序排列
    10. queryWrapper.orderByDesc(Setmeal::getUpdateTime);
    11. setmealService.page(pageInfo,queryWrapper);
    12. //对象拷贝
    13. BeanUtils.copyProperties(pageInfo,dtoPage,"records");
    14. List records = pageInfo.getRecords();
    15. List list = records.stream().map((item) -> {
    16. SetmealDto setmealDto = new SetmealDto();
    17. //对象拷贝
    18. BeanUtils.copyProperties(item,setmealDto);
    19. //分类id
    20. Long categoryId = item.getCategoryId();
    21. //根据分类id查询分类对象
    22. Category category = categoryService.getById(categoryId);
    23. if(category != null){
    24. //分类名称
    25. String categoryName = category.getName();
    26. setmealDto.setCategoryName(categoryName);
    27. }
    28. return setmealDto;
    29. }).collect(Collectors.toList());
    30. dtoPage.setRecords(list);
    31. return R.success(dtoPage);
    32. }

    发送请求测试,成功:

    http://localhost/setmeal/page?page=1&pageSize=1

    坑点:

    创建Page对象,忘了传参数pageSize和page

    7.4 批量删除套餐

    请求:

    img

    代码:

    1. @DeleteMapping
    2. //接收集合类型要@RequestParam
    3. public R delete(@RequestParam List ids){
    4. log.info("ids:{}",ids);
    5. setmealService.removeWithDish(ids);
    6. return R.success("套餐数据删除成功");
    7. }

    7.5 回顾区别@RequestBody@RequestParam@PathVariable

    关于接收参数,我们学过三个注解@RequestBody、@RequestParam、@PathVariable,这三个注解之间的区别和应用分别是什么?

    联系:都是参数注解,都用于前端到controller的参数传递。 区别 @RequestParam用于接收非JSON参数,集合类型必须注解。可以起别名。

    1. @RequestMapping("/commonParamDifferentName")
    2. @ResponseBody
    3. public String commonParamDifferentName(@RequestParam("name") String userName , int age){
    4. System.out.println("普通参数传递 userName ==> "+userName);
    5. System.out.println("普通参数传递 age ==> "+age);
    6. return "{'module':'common param different name'}";
    7. }
    8. //集合参数:同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
    9. @RequestMapping("/listParam")
    10. @ResponseBody
    11. public String listParam(@RequestParam List likes){
    12. System.out.println("集合参数传递 likes ==> "+ likes);
    13. return "{'module':'list param'}";
    14. }

    @RequestBody用于接收json数据(请求体参数),必须加上才能获取到前端传来的JSON数据。

    1. @RequestMapping("/pojoParamForJson")
    2. @ResponseBody
    3. public String pojoParamForJson(@RequestBody User user){
    4. System.out.println("pojo(json)参数传递 user ==> "+user);
    5. return "{'module':'pojo for json param'}";
    6. }

    @PathVariable用于接收路径参数,REST风格。使用{参数名称}描述路径参数

    1. @Controller
    2. public class UserController {
    3. //设置当前请求方法为GET,表示REST风格中的查询操作
    4. @RequestMapping(value = "/users/{id}" ,method = RequestMethod.GET)
    5. @ResponseBody
    6. public String getById(@PathVariable Integer id){
    7. System.out.println("user getById..."+id);
    8. return "{'module':'user getById'}";
    9. }
    10. }

    应用

    8 验证码功能

    8.1 腾讯云短信服务

    8.1.1 介绍

    因为我腾讯云有备案的域名,所以就用腾讯云,当然用阿里云也可以。

    短信服务:

    img

    img

    三大应用场景:

    img

    img

    8.1.2 步骤

    0.进入网址:

    短信文本短信通知短信营销短信验证码短信 - 腾讯云

    img

    1.短信签名:

    img

    img

    2.创建正文模板

    img

    img

    img

    4、创建应用,获取SDKAppID

    img

    5、获取secreteid和secretekey,身份唯一标识

    img

    6、腾讯云自动生成发送验证码的java代码

    先进入短信服务,点击云产品–云api

    img

    选择发送短信(SMS)

    登录 - 腾讯云

    img 填入发送短信的相关参数

    img

    在maven工程中接入短信服务,pom.xml导入依赖

    1. <dependency>
    2. <groupId>com.tencentcloudapigroupId>
    3. <artifactId>tencentcloud-sdk-javaartifactId>
    4. <version>4.0.11version>
    5. dependency>

    测试类

    1. import com.tencentcloudapi.common.Credential;
    2. import com.tencentcloudapi.common.profile.ClientProfile;
    3. import com.tencentcloudapi.common.profile.HttpProfile;
    4. import com.tencentcloudapi.common.exception.TencentCloudSDKException;
    5. import com.tencentcloudapi.sms.v20210111.SmsClient;
    6. import com.tencentcloudapi.sms.v20210111.models.*;
    7. public class SendSms
    8. {
    9. public static void main(String [] args) {
    10. try{
    11. // 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
    12. // 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
    13. Credential cred = new Credential("SecretId", "SecretKey");
    14. // 实例化一个http选项,可选的,没有特殊需求可以跳过
    15. HttpProfile httpProfile = new HttpProfile();
    16. httpProfile.setEndpoint("sms.tencentcloudapi.com");
    17. // 实例化一个client选项,可选的,没有特殊需求可以跳过
    18. ClientProfile clientProfile = new ClientProfile();
    19. clientProfile.setHttpProfile(httpProfile);
    20. // 实例化要请求产品的client对象,clientProfile是可选的
    21. SmsClient client = new SmsClient(cred, "", clientProfile);
    22. // 实例化一个请求对象,每个接口都会对应一个request对象
    23. SendSmsRequest req = new SendSmsRequest();
    24. // 返回的resp是一个SendSmsResponse的实例,与请求对象对应
    25. SendSmsResponse resp = client.SendSms(req);
    26. // 输出json格式的字符串回包
    27. System.out.println(SendSmsResponse.toJsonString(resp));
    28. } catch (TencentCloudSDKException e) {
    29. System.out.println(e.toString());
    30. }
    31. }
    32. }

    8.2 手机验证码登录

    8.2.1 数据模型

    user用户表,没有用户名密码,因为是使用验证码登录

    img

    img

    8.2.2 环境准备(user实体类、dao、Service、工具类)

    1. @Data
    2. public class User implements Serializable {
    3. private static final long serialVersionUID = 1L;
    4. @JsonFormat(shape = JsonFormat.Shape.STRING)
    5. private Long id;
    6. //姓名
    7. private String name;
    8. //手机号
    9. private String phone;
    10. //性别 0 女 1 男
    11. private String sex;
    12. //身份证号
    13. private String idNumber;
    14. //头像
    15. private String avatar;
    16. //状态 0:禁用,1:正常
    17. private Integer status;
    18. }

    1. @Mapper
    2. public interface UserMapper extends BaseMapper{
    3. }
    4. public interface UserService extends IService {
    5. }
    6. @Service
    7. public class UserServiceImpl extends ServiceImpl implements UserService{
    8. }

    工具类:

    1. public class SMSUtils {
    2. /**
    3. * 发送短信
    4. * @param signName 签名
    5. * @param templateCode 模板
    6. * @param phoneNumbers 手机号
    7. * @param param 参数
    8. */
    9. public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
    10. DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
    11. IAcsClient client = new DefaultAcsClient(profile);
    12. SendSmsRequest request = new SendSmsRequest();
    13. request.setSysRegionId("cn-hangzhou");
    14. request.setPhoneNumbers(phoneNumbers);
    15. request.setSignName(signName);
    16. request.setTemplateCode(templateCode);
    17. request.setTemplateParam("{\"code\":\""+param+"\"}");
    18. try {
    19. SendSmsResponse response = client.getAcsResponse(request);
    20. System.out.println("短信发送成功");
    21. }catch (ClientException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }

    1. public class ValidateCodeUtils {
    2. /**
    3. * 随机生成验证码
    4. * @param length 长度为4位或者6位
    5. * @return
    6. */
    7. public static Integer generateValidateCode(int length){
    8. Integer code =null;
    9. if(length == 4){
    10. code = new Random().nextInt(9999);//生成随机数,最大为9999
    11. if(code < 1000){
    12. code = code + 1000;//保证随机数为4位数字
    13. }
    14. }else if(length == 6){
    15. code = new Random().nextInt(999999);//生成随机数,最大为999999
    16. if(code < 100000){
    17. code = code + 100000;//保证随机数为6位数字
    18. }
    19. }else{
    20. throw new RuntimeException("只能生成4位或6位数字验证码");
    21. }
    22. return code;
    23. }
    24. /**
    25. * 随机生成指定长度字符串验证码
    26. * @param length 长度
    27. * @return
    28. */
    29. public static String generateValidateCode4String(int length){
    30. Random rdm = new Random();
    31. String hash1 = Integer.toHexString(rdm.nextInt());
    32. String capstr = hash1.substring(0, length);
    33. return capstr;
    34. }
    35. }

    8.2.3 修改登录拦截过滤器

    主要放行登录和验证码请求路径,session中有user则放行:

    1. /**
    2. * 检查用户是否已经完成登录
    3. */
    4. @WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
    5. @Slf4j
    6. public class LoginCheckFilter implements Filter{
    7. //路径匹配器,支持通配符
    8. public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    9. @Override
    10. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    11. HttpServletRequest request = (HttpServletRequest) servletRequest;
    12. HttpServletResponse response = (HttpServletResponse) servletResponse;
    13. //1、获取本次请求的URI
    14. String requestURI = request.getRequestURI();// /backend/index.html
    15. log.info("拦截到请求:{}",requestURI);
    16. //定义不需要处理的请求路径
    17. String[] urls = new String[]{
    18. "/employee/login",
    19. "/employee/logout",
    20. "/backend/**",
    21. "/front/**",
    22. "/common/**",
    23. "/user/sendMsg",
    24. "/user/login"
    25. };
    26. //2、判断本次请求是否需要处理
    27. boolean check = check(urls, requestURI);
    28. //3、如果不需要处理,则直接放行
    29. if(check){
    30. log.info("本次请求{}不需要处理",requestURI);
    31. filterChain.doFilter(request,response);
    32. return;
    33. }
    34. //session里有employee则已登录,放行。针对于后端
    35. if(request.getSession().getAttribute("employee") != null){
    36. log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
    37. Long empId = (Long) request.getSession().getAttribute("employee");
    38. BaseContext.setCurrentId(empId);
    39. filterChain.doFilter(request,response);
    40. return;
    41. }
    42. //session里有user则已登录,放行。针对于前端
    43. if(request.getSession().getAttribute("user") != null){
    44. log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
    45. Long userId = (Long) request.getSession().getAttribute("user");
    46. BaseContext.setCurrentId(userId);
    47. filterChain.doFilter(request,response);
    48. return;
    49. }
    50. log.info("用户未登录");
    51. //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
    52. response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
    53. return;
    54. }
    55. /**
    56. * 路径匹配,检查本次请求是否需要放行
    57. * @param urls
    58. * @param requestURI
    59. * @return
    60. */
    61. public boolean check(String[] urls,String requestURI){
    62. for (String url : urls) {
    63. boolean match = PATH_MATCHER.match(url, requestURI);
    64. if(match){
    65. return true;
    66. }
    67. }
    68. return false;
    69. }
    70. }

    8.2.4 模拟发送验证码,UserController

     这里随机生成的验证码是保存在HttpSession中的,后面会改造为将验证码缓存在Redis中,具体参考下面文章的10.2缓存本地验证码 :

    瑞吉外卖项目笔记+踩坑2——缓存、读写分离优化_瑞吉外卖 webpack打包_vincewm的博客-CSDN博客

    请求:

    img

    代码:

    1. @RestController
    2. @RequestMapping("/user")
    3. @Slf4j
    4. public class UserController {
    5. @Autowired
    6. private UserService userService;
    7. /**
    8. * 发送手机短信验证码
    9. * @param user
    10. * @return
    11. */
    12. @PostMapping("/sendMsg")
    13. public R sendMsg(@RequestBody User user, HttpSession session){
    14. //获取手机号
    15. String phone = user.getPhone();
    16. if(StringUtils.isNotEmpty(phone)){
    17. //生成随机的4位验证码
    18. String code = ValidateCodeUtils.generateValidateCode(4).toString();
    19. log.info("code={}",code);
    20. //调用阿里云提供的短信服务API完成发送短信
    21. //SMSUtils.sendMessage("瑞吉外卖","",phone,code);
    22. //需要将生成的验证码保存到Session
    23. session.setAttribute(phone,code);
    24. return R.success("手机验证码短信发送成功");
    25. }
    26. return R.error("短信发送失败");
    27. }
    28. }

    8.2.5 前端登录

    请求:

    img

    代码:

    未登录用户自动注册

    1. @PostMapping("/login")
    2. public R login(@RequestBody Map map, HttpSession session){
    3. log.info(map.toString());
    4. //获取手机号
    5. String phone = map.get("phone").toString();
    6. //获取验证码
    7. String code = map.get("code").toString();
    8. //从Session中获取保存的验证码
    9. Object codeInSession = session.getAttribute(phone);
    10. //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
    11. if(codeInSession != null && codeInSession.equals(code)){
    12. //如果能够比对成功,说明登录成功
    13. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    14. queryWrapper.eq(User::getPhone,phone);
    15. User user = userService.getOne(queryWrapper);
    16. if(user == null){
    17. //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
    18. user = new User();
    19. user.setPhone(phone);
    20. user.setStatus(1);
    21. userService.save(user);
    22. }
    23. session.setAttribute("user",user.getId());
    24. return R.success(user);
    25. }
    26. return R.error("登录失败");
    27. }

    坑点:要记得把code和手机号都用String类型,特别是存在Session中。

    测试:

    点击获取验证码,将后台生成的验证码复制粘贴到前端验证码框,登陆成功。

    9 移动端功能

    9.1 用户地址簿增删改查

    9.1.1 分析

    需求分析:

    imgimg数据模型:

    img

    label标签是能选择“家” 、“学校”等标签。is-default为1时是默认地址,默认0。

    9.1.2 AddressBook 实体类、dao、Service

    1. /**
    2. * 地址簿
    3. */
    4. @Data
    5. public class AddressBook implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //用户id
    10. @JsonFormat(shape = JsonFormat.Shape.STRING)
    11. private Long userId;
    12. //收货人
    13. private String consignee;
    14. //手机号
    15. private String phone;
    16. //性别 0 女 1 男
    17. private String sex;
    18. //省级区划编号
    19. private String provinceCode;
    20. //省级名称
    21. private String provinceName;
    22. //市级区划编号
    23. private String cityCode;
    24. //市级名称
    25. private String cityName;
    26. //区级区划编号
    27. private String districtCode;
    28. //区级名称
    29. private String districtName;
    30. //详细地址
    31. private String detail;
    32. //标签
    33. private String label;
    34. //是否默认 0 否 1是
    35. private Integer isDefault;
    36. //创建时间
    37. @TableField(fill = FieldFill.INSERT)
    38. private LocalDateTime createTime;
    39. //更新时间
    40. @TableField(fill = FieldFill.INSERT_UPDATE)
    41. private LocalDateTime updateTime;
    42. //创建人
    43. @JsonFormat(shape = JsonFormat.Shape.STRING)
    44. @TableField(fill = FieldFill.INSERT)
    45. private Long createUser;
    46. //修改人
    47. @TableField(fill = FieldFill.INSERT_UPDATE)
    48. @JsonFormat(shape = JsonFormat.Shape.STRING)
    49. private Long updateUser;
    50. //是否删除
    51. private Integer isDeleted;
    52. }

    1. @Mapper
    2. public interface AddressBookMapper extends BaseMapper {
    3. }
    4. public interface AddressBookService extends IService {
    5. }
    6. @Service
    7. public class AddressBookServiceImpl extends ServiceImpl implements AddressBookService {
    8. }

    9.1.3 controller

    1. /**
    2. * 地址簿管理
    3. */
    4. @Slf4j
    5. @RestController
    6. @RequestMapping("/addressBook")
    7. public class AddressBookController {
    8. @Autowired
    9. private AddressBookService addressBookService;
    10. /**
    11. * 新增
    12. */
    13. @PostMapping
    14. public R save(@RequestBody AddressBook addressBook) {
    15. addressBook.setUserId(BaseContext.getCurrentId());
    16. log.info("addressBook:{}", addressBook);
    17. addressBookService.save(addressBook);
    18. return R.success(addressBook);
    19. }
    20. /**
    21. * 设置默认地址
    22. */
    23. @PutMapping("default")
    24. public R setDefault(@RequestBody AddressBook addressBook) {
    25. //先把所有地址is_default改成0
    26. log.info("addressBook:{}", addressBook);
    27. LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
    28. wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
    29. wrapper.set(AddressBook::getIsDefault, 0);
    30. //SQL:update address_book set is_default = 0 where user_id = ?
    31. addressBookService.update(wrapper);
    32. //再把这个地址设为默认
    33. addressBook.setIsDefault(1);
    34. //SQL:update address_book set is_default = 1 where id = ?
    35. addressBookService.updateById(addressBook);
    36. return R.success(addressBook);
    37. }
    38. /**
    39. * 根据id查询地址
    40. */
    41. @GetMapping("/{id}")
    42. public R get(@PathVariable Long id) {
    43. AddressBook addressBook = addressBookService.getById(id);
    44. if (addressBook != null) {
    45. return R.success(addressBook);
    46. } else {
    47. return R.error("没有找到该对象");
    48. }
    49. }
    50. /**
    51. * 查询默认地址
    52. */
    53. @GetMapping("default")
    54. public R getDefault() {
    55. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    56. queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
    57. queryWrapper.eq(AddressBook::getIsDefault, 1);
    58. //SQL:select * from address_book where user_id = ? and is_default = 1
    59. AddressBook addressBook = addressBookService.getOne(queryWrapper);
    60. if (null == addressBook) {
    61. return R.error("没有找到该对象");
    62. } else {
    63. return R.success(addressBook);
    64. }
    65. }
    66. /**
    67. * 查询指定用户的全部地址
    68. */
    69. @GetMapping("/list")
    70. public R> list(AddressBook addressBook) {
    71. addressBook.setUserId(BaseContext.getCurrentId());
    72. log.info("addressBook:{}", addressBook);
    73. //条件构造器
    74. LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
    75. queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
    76. queryWrapper.orderByDesc(AddressBook::getUpdateTime);
    77. //SQL:select * from address_book where user_id = ? order by update_time desc
    78. return R.success(addressBookService.list(queryWrapper));
    79. }
    80. }

    9.2 菜品展示

    img

    img

    9.2.1 前端购物车请求设为假数据

    因为菜品展示页面会有购物车请求影响展示,先给购物车请求假数据:

    img

    /front/api/main.api

    1. //获取购物车内商品的集合
    2. function cartListApi(data) {
    3. return $axios({
    4. // 'url': '/shoppingCart/list',
    5. 'url': '/front/cartData.json',
    6. 'method': 'get',
    7. params:{...data}
    8. })
    9. }

    /front/cartData.json

    1. {
    2. "code": 1,
    3. "msg": null,
    4. "data": [],
    5. "map": {}
    6. }

    这时候把菜品和套餐展示写了后,再访问首页*****http://localhost/front/index.html*****就有了数据

    9.2.2 套餐和菜品按分类展示

    现在用户端主页的菜品展示是没有味道显示的,因为之前controller条件查询(添加套餐时按分类查询菜品id)传参是Dish,现在要换成DishDto。

    DishController

    img

    img

    1. @GetMapping("/list")
    2. public R<List<DishDto>> list(Dish dish){
    3. //构造查询条件
    4. LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
    5. queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
    6. //添加条件,查询状态为1(起售状态)的菜品
    7. queryWrapper.eq(Dish::getStatus,1);
    8. //添加排序条件
    9. queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
    10. List<Dish> list = dishService.list(queryWrapper);
    11. List<DishDto> dishDtoList = list.stream().map((item) -> {
    12. DishDto dishDto = new DishDto();
    13. BeanUtils.copyProperties(item,dishDto);
    14. Long categoryId = item.getCategoryId();//分类id
    15. //根据id查询分类对象
    16. Category category = categoryService.getById(categoryId);
    17. if(category != null){
    18. String categoryName = category.getName();
    19. dishDto.setCategoryName(categoryName);
    20. }
    21. //当前菜品的id
    22. Long dishId = item.getId();
    23. LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    24. lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
    25. //SQL:select * from dish_flavor where dish_id = ?
    26. List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
    27. dishDto.setFlavors(dishFlavorList);
    28. return dishDto;
    29. }).collect(Collectors.toList());
    30. return R.success(dishDtoList);
    31. }

    SetmealController

    img

    套餐不用选择味道之类信息,直接返回List即可。

    img

    1. @GetMapping("/list")
    2. public R<List<Setmeal>> list(Setmeal setmeal){
    3. LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    4. queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    5. queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    6. queryWrapper.orderByDesc(Setmeal::getUpdateTime);
    7. List<Setmeal> list = setmealService.list(queryWrapper);
    8. return R.success(list);
    9. }

    9.3 购物车

    9.3.1 分析

    需求分析:

    购物车是一个列表,每个数据是一个shopping_cart表的数据,里面只有一个菜品或套餐。

    imgimg

    数据模型:

    img user_id是当前购物车所属用户的id,

    9.3.2 ShoppingCart实体类、dao和Service

    1. /**
    2. * 购物车
    3. */
    4. @Data
    5. public class ShoppingCart implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //名称
    10. private String name;
    11. //用户id
    12. @JsonFormat(shape = JsonFormat.Shape.STRING)
    13. private Long userId;
    14. //菜品id
    15. @JsonFormat(shape = JsonFormat.Shape.STRING)
    16. private Long dishId;
    17. //套餐id
    18. @JsonFormat(shape = JsonFormat.Shape.STRING)
    19. private Long setmealId;
    20. //口味
    21. private String dishFlavor;
    22. //数量
    23. private Integer number;
    24. //金额
    25. private BigDecimal amount;
    26. //图片
    27. private String image;
    28. private LocalDateTime createTime;
    29. }

    1. @Mapper
    2. public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {
    3. }
    4. public interface ShoppingCartService extends IService<ShoppingCart> {
    5. }
    6. @Service
    7. public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
    8. }

    9.3.2 添加购物车

    img

    请求:

    img

    imgimg

    代码:

    注意不能直接添加购物车,要先查询此东西是否已加入购物车,如果已加入amount加1, 未加入则加入。

    1. @Slf4j
    2. @RestController
    3. @RequestMapping("/shoppingCart")
    4. public class ShoppingCartController {
    5. @Autowired
    6. private ShoppingCartService shoppingCartService;
    7. /**
    8. * 添加购物车
    9. * @param shoppingCart
    10. * @return
    11. */
    12. @PostMapping("/add")
    13. public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
    14. log.info("购物车数据:{}",shoppingCart);
    15. //设置用户id,指定当前是哪个用户的购物车数据
    16. Long currentId = BaseContext.getCurrentId();
    17. shoppingCart.setUserId(currentId);
    18. Long dishId = shoppingCart.getDishId();
    19. LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    20. queryWrapper.eq(ShoppingCart::getUserId,currentId);
    21. if(dishId != null){
    22. //添加到购物车的是菜品
    23. queryWrapper.eq(ShoppingCart::getDishId,dishId);
    24. }else{
    25. //添加到购物车的是套餐
    26. queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
    27. }
    28. //查询当前菜品或者套餐是否在购物车中
    29. //SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
    30. ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
    31. if(cartServiceOne != null){
    32. //如果已经存在,就在原来数量基础上加一
    33. Integer number = cartServiceOne.getNumber();
    34. cartServiceOne.setNumber(number + 1);
    35. shoppingCartService.updateById(cartServiceOne);
    36. }else{
    37. //如果不存在,则添加到购物车,数量默认就是一
    38. shoppingCart.setNumber(1);
    39. shoppingCart.setCreateTime(LocalDateTime.now());
    40. shoppingCartService.save(shoppingCart);
    41. cartServiceOne = shoppingCart;
    42. }
    43. return R.success(cartServiceOne);
    44. }
    45. }

    9.3.3 查看购物车

    img

    首先前端改回请求路径:

    1. //获取购物车内商品的集合
    2. function cartListApi(data) {
    3. return $axios({
    4. 'url': '/shoppingCart/list',
    5. // 'url': '/front/cartData.json',
    6. 'method': 'get',
    7. params:{...data}
    8. })
    9. }

    代码:

    坑点:要查询当前用户的购物车

    1. /**
    2. * 查看购物车
    3. * @return
    4. */
    5. @GetMapping("/list")
    6. public R<List<ShoppingCart>> list(){
    7. log.info("查看购物车...");
    8. LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    9. queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    10. queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
    11. List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
    12. return R.success(list);
    13. }

    9.3.4 购物车减少菜品数量

    img

    请求:

    imgimg

    代码:

    1. @PostMapping("/sub")
    2. public R<String> sub(@RequestBody ShoppingCart shoppingCart){
    3. //先查是菜品还是套餐,查当前用户的购物车
    4. Long dishId = shoppingCart.getDishId();
    5. Long setmealId = shoppingCart.getSetmealId();
    6. LambdaQueryWrapper<ShoppingCart> wrapper=new LambdaQueryWrapper<>();
    7. wrapper.eq(dishId!=null,ShoppingCart::getDishId,dishId);
    8. wrapper.eq(setmealId!=null,ShoppingCart::getSetmealId,setmealId);
    9. wrapper.eq(ShoppingCart::getUserId,BaseContext.getThreadId());
    10. ShoppingCart one = shoppingCartService.getOne(wrapper);
    11. if(one.getNumber()>1){
    12. //数量大于1,减1
    13. one.setNumber(one.getNumber()-1);
    14. shoppingCartService.updateById(one);
    15. return R.success("减少成功");
    16. }else{
    17. //数量为1,,在减少后为0,删除商品
    18. shoppingCartService.removeById(one);
    19. return R.success("这个商品没了");
    20. }
    21. }

    9.3.5 清空购物车

    img

    请求:

    img

    代码:

    1. /**
    2. * 清空购物车
    3. * @return
    4. */
    5. @DeleteMapping("/clean")
    6. public R<String> clean(){
    7. //SQL:delete from shopping_cart where user_id = ?
    8. LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    9. queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    10. shoppingCartService.remove(queryWrapper);
    11. return R.success("清空购物车成功");
    12. }

    9.4 下单

    9.4.1 分析

    img

    需求:个人用户无法开发支付功能,所以付款时候保存数据库即可。

    订单表order数据模型:

    img

    • status是订单状态,默认为1,1待付款,2待派送,3已派送,4已完成,5已取消。
    • checkout_time是结账时间。
    • pay_method支付方式,1微信2支付宝
    • amount金额
    • remark备注
    • consignee收货人名

    明细表order_detail数据模型

    img

    9.4.2 Order,OrderDetail实体类、dao、Service、controller

    没什么新的东西,跟前面一样。注意点是@JsonFormat防止js丢失精度

    1. /**
    2. * 订单
    3. */
    4. @Data
    5. //一定要注意表名不能是order
    6. public class Orders implements Serializable {
    7. private static final long serialVersionUID = 1L;
    8. @JsonFormat(shape = JsonFormat.Shape.STRING)
    9. private Long id;
    10. //订单号
    11. private String number;
    12. //订单状态 1待付款,2待派送,3已派送,4已完成,5已取消
    13. private Integer status;
    14. //下单用户id
    15. @JsonFormat(shape = JsonFormat.Shape.STRING)
    16. private Long userId;
    17. //地址id
    18. @JsonFormat(shape = JsonFormat.Shape.STRING)
    19. private Long addressBookId;
    20. //下单时间
    21. private LocalDateTime orderTime;
    22. //结账时间
    23. private LocalDateTime checkoutTime;
    24. //支付方式 1微信,2支付宝
    25. private Integer payMethod;
    26. //实收金额
    27. private BigDecimal amount;
    28. //备注
    29. private String remark;
    30. //用户名
    31. private String userName;
    32. //手机号
    33. private String phone;
    34. //地址
    35. private String address;
    36. //收货人
    37. private String consignee;
    38. }

    1. /**
    2. * 订单明细
    3. */
    4. @Data
    5. public class OrderDetail implements Serializable {
    6. private static final long serialVersionUID = 1L;
    7. @JsonFormat(shape = JsonFormat.Shape.STRING)
    8. private Long id;
    9. //名称
    10. private String name;
    11. //订单id
    12. @JsonFormat(shape = JsonFormat.Shape.STRING)
    13. private Long orderId;
    14. //菜品id
    15. @JsonFormat(shape = JsonFormat.Shape.STRING)
    16. private Long dishId;
    17. //套餐id
    18. @JsonFormat(shape = JsonFormat.Shape.STRING)
    19. private Long setmealId;
    20. //口味
    21. private String dishFlavor;
    22. //数量
    23. private Integer number;
    24. //金额
    25. private BigDecimal amount;
    26. //图片
    27. private String image;
    28. }

    1. @Mapper
    2. public interface OrderMapper extends BaseMapper<Orders> {
    3. }
    4. @Mapper
    5. public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
    6. }
    7. public interface OrderService extends IService<Orders> {
    8. }
    9. public interface OrderDetailService extends IService<OrderDetail> {
    10. }
    11. @Service
    12. @Slf4j
    13. public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
    14. }
    15. @Service
    16. public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {
    17. }

    1. @RestController
    2. @RequestMapping("order")
    3. @Slf4j
    4. public class OrderController {
    5. @Autowired
    6. private OrderService orderService;
    7. }
    8. @RestController
    9. @RequestMapping("/orderDetail")
    10. @Slf4j
    11. public class OrderDetailController {
    12. @Autowired
    13. private OrderDetailService orderDetailService;
    14. }

    9.4.3 提交订单

    img

    请求:

    img

    img

    步骤:

    img

    代码:

    注意:

    • 下单不需要传套菜信息,因为可以通过线程里用户id从购物车数据库查。外卖项目每次购买方法都是提交购物车,不需要像商城那样购物车存一堆不买的东西。
    • 下单的难点是既要保存订单信息,还要保存订单详情页,所以要新写个submit业务方法。
    • 使用mp的IdWorker.getId()生成雪花算法的订单id。
    • 查询当前用户购物车数据后要遍历计算总金额,总金额类型AtomicInteger原子整型,可以保证线程安全。addAndGet是先加再获取,getAndAdd是先获取再加。

    OrderServiceImpl

    1. @Transactional
    2. public void submit(Orders orders) {
    3. //获得当前用户id
    4. Long userId = BaseContext.getCurrentId();
    5. //查询当前用户的购物车数据
    6. LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
    7. wrapper.eq(ShoppingCart::getUserId,userId);
    8. List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);
    9. if(shoppingCarts == null || shoppingCarts.size() == 0){
    10. throw new CustomException("购物车为空,不能下单");
    11. }
    12. //查询用户数据
    13. User user = userService.getById(userId);
    14. //查询地址数据
    15. Long addressBookId = orders.getAddressBookId();
    16. AddressBook addressBook = addressBookService.getById(addressBookId);
    17. if(addressBook == null){
    18. throw new CustomException("用户地址信息有误,不能下单");
    19. }
    20. //用IdWorker设置订单号
    21. long orderId = IdWorker.getId();//订单号
    22. //AtomicInteger原子整型,可以保证线程安全。addAndGet是先加再获取,getAndAdd是先获取再加。
    23. AtomicInteger amount = new AtomicInteger(0);
    24. //购物车数据加工成订单细节
    25. List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
    26. OrderDetail orderDetail = new OrderDetail();
    27. orderDetail.setOrderId(orderId);
    28. orderDetail.setNumber(item.getNumber());
    29. orderDetail.setDishFlavor(item.getDishFlavor());
    30. orderDetail.setDishId(item.getDishId());
    31. orderDetail.setSetmealId(item.getSetmealId());
    32. orderDetail.setName(item.getName());
    33. orderDetail.setImage(item.getImage());
    34. orderDetail.setAmount(item.getAmount());
    35. //叠加金额,对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数的操作。
    36. //BigDecimal的intValue()方法把BigDecimal类型转为int型。addAndGet相当于x=x+y
    37. amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
    38. return orderDetail;
    39. }).collect(Collectors.toList());
    40. orders.setId(orderId);
    41. orders.setOrderTime(LocalDateTime.now());
    42. orders.setCheckoutTime(LocalDateTime.now());
    43. orders.setStatus(2);
    44. orders.setAmount(new BigDecimal(amount.get()));//总金额
    45. orders.setUserId(userId);
    46. orders.setNumber(String.valueOf(orderId));
    47. orders.setUserName(user.getName());
    48. orders.setConsignee(addressBook.getConsignee());
    49. orders.setPhone(addressBook.getPhone());
    50. orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
    51. + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
    52. + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
    53. + (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
    54. //向订单表插入数据,一条数据
    55. this.save(orders);
    56. //向订单明细表插入数据,多条数据
    57. orderDetailService.saveBatch(orderDetails);
    58. //清空购物车数据
    59. shoppingCartService.remove(wrapper);
    60. }

    BigDecimal和double区别:和对于不需要任何准确计算精度的数字可以直接使用float或double,但是如果需要精确计算的结果,则必须使用BigDecimal类,而且使用BigDecimal类也可以进行大数的操作。 intValue()方法把BigDecimal类型转为int型

    OrderController

    1. /**
    2. * 用户下单
    3. * @param orders
    4. * @return
    5. */
    6. @PostMapping("/submit")
    7. public R<String> submit(@RequestBody Orders orders){
    8. log.info("订单数据:{}",orders);
    9. orderService.submit(orders);
    10. return R.success("下单成功");
    11. }

    坑点:表名不能是order!表名不能是order!表名不能是order!

  • 相关阅读:
    08-RabbitMQ使用中的常见问题
    进大厂必须要会的单元测试
    软硬件实现二选一逻辑器
    2023-09-18 LeetCode每日一题(打家劫舍 III)
    “智能语音指令解析“ 基于NLP与语音识别的工单关键信息提取
    Bug:elementUI样式不起作用、Vue引入组件报错not found等(Vue+ElementUI问题汇总)
    【Python 千题 —— 基础篇】进制转换:十进制转十六进制
    集成hibeaver的血泪史 -- Ambiguous method overloading for method java.io.File#<init>
    Kubernetes技术--k8s核心技术持久化存储
    【番杰的小技巧笔记】如何通过嘉立创免费3D打印
  • 原文地址:https://blog.csdn.net/qq_40991313/article/details/126697138