一、新增套餐
1.1 需求分析
套餐就是菜品的集合。
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
1.2 数据模型
新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:
表 | 说明 | 备注 |
---|---|---|
setmeal | 套餐表 | 存储套餐的基本信息 |
setmeal_dish | 套餐菜品关系表 | 存储套餐关联的菜品的信息(一个套餐可以关联多个菜品) |
两张表具体的表结构如下:
1). 套餐表setmeal
在该表中,套餐名称name字段是不允许重复的,在建表时,已经创建了唯一索引。
2). 套餐菜品关系表setmeal_dish
在该表中,菜品的名称name,菜品的原价price 实际上都是冗余字段,因为我们在这张表中存储了菜品的ID(dish_id),根据该ID我们就可以查询出name,price的数据信息,而这里我们又存储了name,price,这样的话,我们在后续的查询展示操作中,就不需要再去查询数据库获取菜品名称和原价了,这样可以简化我们的操作。
1.3 准备工作
1). 实体类 SetmealDish
所属包: com.itheima.reggie.entity
1 | import com.baomidou.mybatisplus.annotation.FieldFill; |
2). DTO SetmealDto
该数据传输对象DTO,主要用于封装页面在新增套餐时传递过来的json格式的数据,其中包含套餐的基本信息,还包含套餐关联的菜品集合。
所属包: com.itheima.reggie.dto
1 | import com.itheima.reggie.entity.Setmeal; |
3). Mapper接口 SetmealDishMapper
所属包: com.itheima.reggie.mapper
1 | /** |
4). 业务层接口 SetmealDishService
所属包: com.itheima.reggie.service
1 | /** |
5). 业务层实现类 SetmealDishServiceImpl
所属包: com.itheima.reggie.service.impl
1 | /** |
6). 控制层 SetmealController
套餐管理的相关业务,我们都统一在 SetmealController 中进行统一处理操作。
所属包: com.itheima.reggie.service.impl
1 | /** |
1.4 前端页面分析
前端页面和服务端的交互过程:
点击新建套餐按钮,访问页面(backend/page/combo/add.html),页面加载发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中(==已实现==)
访问页面(backend/page/combo/add.html),页面加载时发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中(==已实现==)
当点击添加菜品窗口左侧菜单的某一个分类, 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
页面发送请求进行图片上传,请求服务端将图片保存到服务器(==已实现==)
页面发送请求进行图片下载,将上传的图片进行回显(==已实现==)
点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端
经过上述的页面解析及流程分析,我们发送这里需要发送的请求有5个,分别是 :
A. 根据传递的参数,查询套餐分类列表
B. 根据传递的参数,查询菜品分类列表
C. 图片上传
D. 图片下载展示
E. 根据菜品分类ID,查询菜品列表
F. 保存套餐信息
而对于以上的前4个功能我们都已经实现, 所以我们接下来需要开发的功能主要是最后两项, 具体的请求信息如下:
1). 根据分类ID查询菜品列表
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /dish/list |
请求参数 | ?categoryId=1397844263642378242 |
2). 保存套餐信息
请求 | 说明 |
---|---|
请求方式 | POST |
请求路径 | /setmeal |
请求参数 | json格式数据 |
传递的json格式数据如下:
1 | { |
1.5 代码实现
1.根据分类查询菜品
在当前的需求中,我们只需要根据页面传递的菜品分类的ID(categoryId)来查询菜品列表即可,我们可以直接定义一个DishController的方法,声明一个Long类型的categoryId,这样做是没问题的。
但是考虑到该方法的拓展性,我们在这里定义方法时,通过Dish这个实体来接收参数。
在DishController中定义方法list,接收Dish类型的参数:
在查询时,需要根据菜品分类categoryId进行查询,并且还要限定菜品的状态为起售状态(status为1),然后对查询的结果进行排序。
1 | //根据条件查询对应菜品数据 |
测试:
成功查询到数据
2.保存套餐
在进行套餐信息保存时,前端提交的数据,不仅包含套餐的基本信息,还包含套餐关联的菜品列表数据 setmealDishes。
所以这个时候我们使用Setmeal就不能完成参数的封装了,我们需要在Setmeal的基本属性的基础上,再扩充一个属性 setmealDishes 来接收页面传递的套餐关联的菜品列表,所以要使用SetmealDto来完成这个需求。
1). SetmealController中定义方法save,新增套餐
在该Controller的方法中,我们不仅需要保存套餐的基本信息,还需要保存套餐关联的菜品数据,所以我们需要再该方法中调用业务层方法,完成两块数据的保存。
页面传递的数据是json格式,需要在方法形参前面加上@RequestBody注解, 完成参数封装。
1 |
|
2). SetmealService中定义方法saveWithDish
1 | /** |
3). SetmealServiceImpl实现方法saveWithDish
具体逻辑:
A. 保存套餐基本信息
B. 获取套餐关联的菜品集合,并为集合中的每一个元素赋值套餐ID(setmealId)
C. 批量保存套餐关联的菜品集合
代码:
1 |
|
4).测试
套餐添加成功
套餐和菜品关联表添加成功
二、套餐分页查询
2.1 需求分析
系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
在进行套餐数据的分页查询时,除了传递分页参数以外,还可以传递一个可选的条件(套餐名称)。
查询返回的字段中,包含套餐的基本信息之外,还有一个套餐的分类名称,在查询时,需要关联查询这个字段。
2.2 前端页面分析
前端页面和服务端的交互过程:
1). 访问页面(backend/page/combo/list.html),页面加载时,会自动发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
2). 在列表渲染展示时,页面发送请求,请求服务端进行图片下载,用于页面图片展示(已实现)
具体的请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /setmeal/page |
请求参数 | ?page=1&pageSize=10&name=xxx |
2.3 代码实现
1. 基本信息查询
在SetmealController中创建套餐分页查询方法。
逻辑如下:
构建分页条件对象
构建查询条件对象,如果传递了套餐名称,根据套餐名称模糊查询, 并对结果按修改时间降序排序
执行分页查询
组装数据并返回
代码实现 :
1 |
|
2.4 功能测试
可以获取到套餐分类名称categoryName并可以在列表页面展示出来 。
三、删除套餐
3.1 需求分析
在套餐管理列表页面,点击删除按钮,可以删除对应的套餐信息。也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。
注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。
3.2 前端页面分析
前端页面和服务端的交互过程:
1). 点击删除, 删除单个套餐时,页面发送ajax请求,根据套餐id删除对应套餐
2). 删除多个套餐时,页面发送ajax请求,根据提交的多个套餐id删除对应套餐
开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可,一次请求为根据ID删除,一次请求为根据ID批量删除。
观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址和请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理。
具体的请求信息如下:
请求 | 说明 |
---|---|
请求方式 | DELETE |
请求路径 | /setmeal |
请求参数 | ?ids=1423640210125656065,1423338765002256385 |
3.3 代码实现
删除套餐的流程及请求信息,我们分析完毕之后,就来完成服务端的逻辑开发。在服务端的逻辑中, 删除套餐时, 我们不仅要删除套餐, 还要删除套餐与菜品的关联关系。
1). 在SetmealService接口中定义方法removeWithDish
1 | /** |
2). 在SetmealServiceImpl中实现方法removeWithDish
1 | /** |
3).在SetmealController中创建delete方法
1 | /** |
3.4 功能测试
首先删除一个起售的套餐
测试:
然后将状态再改为0
然后删除
四、短信发送
4.1 短信服务介绍
在项目中,如果我们要实现短信发送功能,我们无需自己实现,也无需和运营商直接对接,只需要调用第三方提供的短信服务即可。
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员,并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。
常用短信服务:
阿里云
华为云
腾讯云
京东
梦网
乐信
本项目在选择短信服务的第三方服务提供商时,选择的是阿里云短信服务。
4.2 阿里云短信服务介绍
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:
场景 | 案例 |
---|---|
验证码 | APP、网站注册账号,向手机下发验证码; 登录账户、异地登录时的安全提醒; 找回密码时的安全验证; 支付认证、身份校验、手机绑定等。 |
短信通知 | 向注册用户下发系统相关信息,包括: 升级或维护、服务开通、价格调整、 订单确认、物流动态、消费确认、 支付通知等普通通知短信。 |
推广短信 | 向注册用户和潜在客户发送通知和推广信息,包括促销活动通知、业务推广等商品与活动的推广信息。增加企业产品曝光率、提高产品的知名度。 |
阿里云短信服务官方网站: https://www.aliyun.com/product/sms?spm=5176.19720258.J_8058803260.52.5c432c4a11Dcwf
4.3 代码实现
具体实现:
1). pom.xml
1 | <dependency> |
2). 将官方提供的main方法封装为一个工具类
1 | import com.aliyuncs.DefaultAcsClient; |
五、验证码登录
5.1 需求分析
为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。手机验证码登录有如下优点:
方便快捷,无需注册,直接登录
使用短信验证码作为登录凭证,无需记忆密码
安全
登录流程:
输入手机号 > 获取验证码 > 输入验证码 > 点击登录 > 登录成功
注意:通过手机验证码登录,手机号是区分不同用户的标识。
5.2 数据模型
通过手机验证码登录时,涉及的表为user表,即用户表。结构如下:
5.3 前端页面分析
在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:
在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信。
在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求。
如果服务端返回的登录成功,页面将会把当前登录用户的手机号存储在sessionStorage中,并跳转到移动的首页页面。
开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可,分别是获取短信验证码 和 登录请求,具体的请求信息如下:
1). 获取短信验证码
请求 | 说明 |
---|---|
请求方式 | POST |
请求路径 | /user/sendMsg |
请求参数 | {“phone”:”13100001111”} |
2). 登录
请求 | 说明 |
---|---|
请求方式 | POST |
请求路径 | /user/login |
请求参数 | {“phone”:”13100001111”, “code”:”1111”} |
5.4 代码开发
1. 准备工作
1). 实体类 User(直接从课程资料中导入即可)
所属包: com.itheima.reggie.entity
1 | import lombok.Data; |
2). Mapper接口 UserMapper
所属包: com.itheima.reggie.mapper
1 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
3). 业务层接口 UserService
所属包: com.itheima.reggie.service
1 | import com.baomidou.mybatisplus.extension.service.IService; |
4). 业务层实现类 UserServiceImpl
所属包: com.itheima.reggie.service.impl
1 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
5). 控制层 UserController
所属包: com.itheima.reggie.controller
1 | import com.itheima.reggie.service.UserService; |
6). 工具类SMSUtils、ValidateCodeUtils(直接从课程资料中导入即可)
所属包: com.itheima.reggie.utils
SMSUtils : 是我们上面改造的阿里云短信发送的工具类 ;
ValidateCodeUtils : 是验证码生成的工具类 ;
2.功能实现
修改LoginCheckFilter
前面我们已经完成了LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码登录时,发送的两个请求(获取验证码和登录)需要在此过滤器处理时直接放行。
对于移动的端的页面,也是用户登录之后,才可以访问的,那么这个时候就需要在 LoginCheckFilter 中进行判定,如果移动端用户已登录,我们获取到用户登录信息,存入ThreadLocal中(在后续的业务处理中,如果需要获取当前登录用户ID,直接从ThreadLocal中获取),然后放行。
增加如下逻辑:
1 | //4-2、判断登录状态,如果已登录,则直接放行 |
发送短信验证码
在UserController中创建方法,处理登录页面的请求,为指定手机号发送短信验证码,同时需要将手机号对应的验证码保存到Session,方便后续登录时进行比对。
1 | /** |
3. 验证码登录
在UserController中增加登录的方法 login,该方法的具体逻辑为:
获取前端传递的手机号和验证码
从Session中获取到手机号对应的正确的验证码
进行验证码的比对 , 如果比对失败, 直接返回错误信息
如果比对成功, 需要根据手机号查询当前用户, 如果用户不存在, 则自动注册一个新用户
将登录用户的ID存储Session中
具体代码实现:
1 | /** |
5.5 功能测试
点击获取验证码
后端接收到
登陆成功